Merge from vendor branch GCC:
[dragonfly.git] / contrib / texinfo / info / dir.c
1 /* dir.c -- how to build a special "dir" node from "localdir" files.
2    $Id: dir.c,v 1.7 1998/06/28 19:51:36 karl Exp $
3
4    Copyright (C) 1993, 97, 98 Free Software Foundation, Inc.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20    Written by Brian Fox (bfox@ai.mit.edu). */
21
22 #include "info.h"
23 #include "info-utils.h"
24 #include "filesys.h"
25 #include "tilde.h"
26
27 /* The "dir" node can be built from the contents of a file called "dir",
28    with the addition of the menus of every file named in the array
29    dirs_to_add which are found in INFOPATH. */
30
31 static void add_menu_to_file_buffer (), insert_text_into_fb_at_binding ();
32
33 static char *dirs_to_add[] = {
34   "dir", "localdir", (char *)NULL
35 };
36
37
38 /* Return zero if the file represented in the stat structure TEST has
39    already been seen, nonzero else.  */
40
41 typedef struct
42 {
43   unsigned long device;
44   unsigned long inode;
45 } dir_file_list_entry_type;
46
47 static int
48 new_dir_file_p (test)
49     struct stat *test;
50 {
51   static unsigned dir_file_list_len = 0;
52   static dir_file_list_entry_type *dir_file_list = NULL;
53   unsigned i;
54   
55   for (i = 0; i < dir_file_list_len; i++)
56     {
57       dir_file_list_entry_type entry;
58       entry = dir_file_list[i];
59       if (entry.device == test->st_dev && entry.inode == test->st_ino)
60         return 0;
61     }
62   
63   dir_file_list_len++;
64   dir_file_list = xrealloc (dir_file_list, 
65                         dir_file_list_len * sizeof (dir_file_list_entry_type));
66   dir_file_list[dir_file_list_len - 1].device = test->st_dev;
67   dir_file_list[dir_file_list_len - 1].inode = test->st_ino;
68   return 1;
69 }
70
71
72 void
73 maybe_build_dir_node (dirname)
74      char *dirname;
75 {
76   int path_index, update_tags;
77   char *this_dir;
78   FILE_BUFFER *dir_buffer = info_find_file (dirname);
79
80   /* If there is no "dir" in the current info path, we cannot build one
81      from nothing. */
82   if (!dir_buffer)
83     return;
84
85   /* If this directory has already been built, return now. */
86   if (dir_buffer->flags & N_CannotGC)
87     return;
88
89   /* Initialize the list we use to avoid reading the same dir file twice
90      with the dir file just found.  */
91   new_dir_file_p (&dir_buffer->finfo);
92   
93   path_index = update_tags = 0;
94
95   /* Using each element of the path, check for one of the files in
96      DIRS_TO_ADD.  Do not check for "localdir.info.Z" or anything else.
97      Only files explictly named are eligible.  This is a design decision.
98      There can be an info file name "localdir.info" which contains
99      information on the setting up of "localdir" files. */
100   while ((this_dir = extract_colon_unit (infopath, &path_index)))
101     {
102       register int da_index;
103       char *from_file;
104
105       /* Expand a leading tilde if one is present. */
106       if (*this_dir == '~')
107         {
108           char *tilde_expanded_dirname;
109
110           tilde_expanded_dirname = tilde_expand_word (this_dir);
111           if (tilde_expanded_dirname != this_dir)
112             {
113               free (this_dir);
114               this_dir = tilde_expanded_dirname;
115             }
116         }
117
118       /* For every different file named in DIRS_TO_ADD found in the
119          search path, add that file's menu to our "dir" node. */
120       for (da_index = 0; (from_file = dirs_to_add[da_index]); da_index++)
121         {
122           struct stat finfo;
123           int statable;
124           int namelen = strlen (from_file);
125           char *fullpath = xmalloc (3 + strlen (this_dir) + namelen);
126           
127           strcpy (fullpath, this_dir);
128           if (!IS_SLASH (fullpath[strlen (fullpath) - 1]))
129             strcat (fullpath, "/");
130           strcat (fullpath, from_file);
131
132           statable = (stat (fullpath, &finfo) == 0);
133
134           /* Only add this file if we have not seen it before.  */
135           if (statable && S_ISREG (finfo.st_mode) && new_dir_file_p (&finfo))
136             {
137               long filesize;
138               int compressed;
139               char *contents = filesys_read_info_file (fullpath, &filesize,
140                                                        &finfo, &compressed);
141               if (contents)
142                 {
143                   update_tags++;
144                   add_menu_to_file_buffer (contents, filesize, dir_buffer);
145                   free (contents);
146                 }
147             }
148
149           free (fullpath);
150         }
151       free (this_dir);
152     }
153
154   if (update_tags)
155     build_tags_and_nodes (dir_buffer);
156
157   /* Flag that the dir buffer has been built. */
158   dir_buffer->flags |= N_CannotGC;
159 }
160
161 /* Given CONTENTS and FB (a file buffer), add the menu found in CONTENTS
162    to the menu found in FB->contents.  Second argument SIZE is the total
163    size of CONTENTS. */
164 static void
165 add_menu_to_file_buffer (contents, size, fb)
166      char *contents;
167      long size;
168      FILE_BUFFER *fb;
169 {
170   SEARCH_BINDING contents_binding, fb_binding;
171   long contents_offset, fb_offset;
172
173   contents_binding.buffer = contents;
174   contents_binding.start = 0;
175   contents_binding.end = size;
176   contents_binding.flags = S_FoldCase | S_SkipDest;
177
178   fb_binding.buffer = fb->contents;
179   fb_binding.start = 0;
180   fb_binding.end = fb->filesize;
181   fb_binding.flags = S_FoldCase | S_SkipDest;
182
183   /* Move to the start of the menus in CONTENTS and FB. */
184   contents_offset = search_forward (INFO_MENU_LABEL, &contents_binding);
185   fb_offset = search_forward (INFO_MENU_LABEL, &fb_binding);
186
187   /* If there is no menu in CONTENTS, quit now. */
188   if (contents_offset == -1)
189     return;
190
191   /* There is a menu in CONTENTS, and contents_offset points to the first
192      character following the menu starter string.  Skip all whitespace
193      and newline characters. */
194   contents_offset += skip_whitespace_and_newlines (contents + contents_offset);
195
196   /* If there is no menu in FB, make one. */
197   if (fb_offset == -1)
198     {
199       /* Find the start of the second node in this file buffer.  If there
200          is only one node, we will be adding the contents to the end of
201          this node. */
202       fb_offset = find_node_separator (&fb_binding);
203
204       /* If not even a single node separator, give up. */
205       if (fb_offset == -1)
206         return;
207
208       fb_binding.start = fb_offset;
209       fb_binding.start +=
210         skip_node_separator (fb_binding.buffer + fb_binding.start);
211
212       /* Try to find the next node separator. */
213       fb_offset = find_node_separator (&fb_binding);
214
215       /* If found one, consider that the start of the menu.  Otherwise, the
216          start of this menu is the end of the file buffer (i.e., fb->size). */
217       if (fb_offset != -1)
218         fb_binding.start = fb_offset;
219       else
220         fb_binding.start = fb_binding.end;
221
222       insert_text_into_fb_at_binding
223         (fb, &fb_binding, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL));
224
225       fb_binding.buffer = fb->contents;
226       fb_binding.start = 0;
227       fb_binding.end = fb->filesize;
228       fb_offset = search_forward (INFO_MENU_LABEL, &fb_binding);
229       if (fb_offset == -1)
230         abort ();
231     }
232
233   /* CONTENTS_OFFSET and FB_OFFSET point to the starts of the menus that
234      appear in their respective buffers.  Add the remainder of CONTENTS
235      to the end of FB's menu. */
236   fb_binding.start = fb_offset;
237   fb_offset = find_node_separator (&fb_binding);
238   if (fb_offset != -1)
239     fb_binding.start = fb_offset;
240   else
241     fb_binding.start = fb_binding.end;
242
243   /* Leave exactly one blank line between directory entries. */
244   {
245     int num_found = 0;
246
247     while ((fb_binding.start > 0) &&
248            (whitespace_or_newline (fb_binding.buffer[fb_binding.start - 1])))
249       {
250         num_found++;
251         fb_binding.start--;
252       }
253
254     /* Optimize if possible. */
255     if (num_found >= 2)
256       {
257         fb_binding.buffer[fb_binding.start++] = '\n';
258         fb_binding.buffer[fb_binding.start++] = '\n';
259       }
260     else
261       {
262         /* Do it the hard way. */
263         insert_text_into_fb_at_binding (fb, &fb_binding, "\n\n", 2);
264         fb_binding.start += 2;
265       }
266   }
267
268   /* Insert the new menu. */
269   insert_text_into_fb_at_binding
270     (fb, &fb_binding, contents + contents_offset, size - contents_offset);
271 }
272
273 static void
274 insert_text_into_fb_at_binding (fb, binding, text, textlen)
275      FILE_BUFFER *fb;
276      SEARCH_BINDING *binding;
277      char *text;
278      int textlen;
279 {
280   char *contents;
281   long start, end;
282
283   start = binding->start;
284   end = fb->filesize;
285
286   contents = (char *)xmalloc (fb->filesize + textlen + 1);
287   memcpy (contents, fb->contents, start);
288   memcpy (contents + start, text, textlen);
289   memcpy (contents + start + textlen, fb->contents + start, end - start);
290   free (fb->contents);
291   fb->contents = contents;
292   fb->filesize += textlen;
293   fb->finfo.st_size = fb->filesize;
294 }