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