1 /* dir.c -- how to build a special "dir" node from "localdir" files.
2
3 Copyright 1993-2023 Free Software Foundation, Inc.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 Originally written by Brian Fox. */
19
20 #include "info.h"
21 #include "scan.h"
22 #include "filesys.h"
23 #include "tilde.h"
24
25 static void add_menu_to_node (char *contents, size_t size, NODE *node);
26 static void insert_text_into_node (NODE *node, long start,
27 char *text, int textlen);
28
29 static NODE *dir_node = 0;
30
31 static NODE *build_dir_node (void);
32
33 /* Return composite directory node. Return value should be freed by caller,
34 but none of its fields should be. */
35 NODE *
36 get_dir_node (void)
37 {
38 NODE *node;
39
40 if (!dir_node)
41 dir_node = build_dir_node ();
42
43 node = xmalloc (sizeof (NODE));
44 *node = *dir_node;
45
46 return node;
47 }
48
49 static char *dir_contents;
50
51 static NODE *
52 build_dir_node (void)
53 {
54 char *this_dir;
55 int path_index = 0;
56
57 NODE *node;
58
59 node = info_create_node ();
60 node->nodename = xstrdup ("Top");
61 node->fullpath = xstrdup ("dir");
62 node->contents = xstrdup (
63
64 "File: dir, Node: Top, This is the top of the INFO tree.\n"
65 "\n"
66 "This is the Info main menu (aka directory node).\n"
67 "A few useful Info commands:\n"
68 "\n"
69 " 'q' quits;\n"
70 " 'H' lists all Info commands;\n"
71 " 'h' starts the Info tutorial;\n"
72 " 'mTexinfo RET' visits the Texinfo manual, etc.\n"
73
74 );
75
76 node->nodelen = strlen (node->contents);
77
78 for (this_dir = infopath_first (&path_index); this_dir;
79 this_dir = infopath_next (&path_index))
80 {
81 char *result;
82 char *fullpath;
83 int len;
84 size_t filesize;
85 struct stat finfo;
86 int compressed;
87 char *contents;
88
89 /* Space for an appended compressed file extension, like ".gz". */
90 #define PADDING "XXXXXXXXX"
91
92 len = xasprintf (&fullpath, "%s/dir%s", this_dir, PADDING);
93 fullpath[len - strlen(PADDING)] = '\0';
94
95 result = info_check_compressed (fullpath, &finfo);
96 if (!result)
97 {
98 free (fullpath);
99 continue;
100 }
101
102 contents = filesys_read_info_file (fullpath, &filesize,
103 &finfo, &compressed);
104 if (contents)
105 {
106 add_menu_to_node (contents, filesize, node);
107 free (contents);
108 }
109
110 free (fullpath);
111 }
112
113 node->flags |= N_IsDir;
114 dir_contents = node->contents;
115 scan_node_contents (node, 0, 0);
116 return node;
117 }
118
119 /* Given CONTENTS and NODE, add the menu found in CONTENTS to the menu
120 found in NODE->contents. SIZE is the total size of CONTENTS. */
121 static void
122 add_menu_to_node (char *contents, size_t size, NODE *node)
123 {
124 SEARCH_BINDING contents_binding, fb_binding;
125 long contents_offset, fb_offset;
126
127 contents_binding.buffer = contents;
128 contents_binding.start = 0;
129 contents_binding.end = size;
130 contents_binding.flags = S_FoldCase | S_SkipDest;
131
132 fb_binding.buffer = node->contents;
133 fb_binding.start = 0;
134 fb_binding.end = node->nodelen;
135 fb_binding.flags = S_FoldCase | S_SkipDest;
136
137 /* Move to the start of the menus in CONTENTS and NODE. */
138 if (search_forward (INFO_MENU_LABEL, &contents_binding, &contents_offset)
139 != search_success)
140 /* If there is no menu in CONTENTS, quit now. */
141 return;
142
143 /* There is a menu in CONTENTS, and contents_offset points to the first
144 character following the menu starter string. Skip all whitespace
145 and newline characters. */
146 contents_offset += skip_whitespace_and_newlines (contents + contents_offset);
147
148 /* If there is no menu in NODE, make one. */
149 if (search_forward (INFO_MENU_LABEL, &fb_binding, &fb_offset)
150 != search_success)
151 {
152 fb_binding.start = node->nodelen;
153
154 insert_text_into_node
155 (node, fb_binding.start, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL));
156
157 fb_binding.buffer = node->contents;
158 fb_binding.start = 0;
159 fb_binding.end = node->nodelen;
160 if (search_forward (INFO_MENU_LABEL, &fb_binding, &fb_offset)
161 != search_success)
162 abort ();
163 }
164
165 /* CONTENTS_OFFSET and FB_OFFSET point to the starts of the menus that
166 appear in their respective buffers. Add the remainder of CONTENTS
167 to the end of NODE's menu. */
168 fb_binding.start = fb_offset;
169 fb_offset = find_node_separator (&fb_binding);
170 if (fb_offset != -1)
171 fb_binding.start = fb_offset;
172 else
173 fb_binding.start = fb_binding.end;
174
175 /* Leave exactly one blank line between directory entries. */
176 {
177 int num_found = 0;
178
179 while ((fb_binding.start > 0) &&
180 (whitespace_or_newline (fb_binding.buffer[fb_binding.start - 1])))
181 {
182 num_found++;
183 fb_binding.start--;
184 }
185
186 /* Optimize if possible. */
187 if (num_found >= 2)
188 {
189 fb_binding.buffer[fb_binding.start++] = '\n';
190 fb_binding.buffer[fb_binding.start++] = '\n';
191 }
192 else
193 {
194 /* Do it the hard way. */
195 insert_text_into_node (node, fb_binding.start, "\n\n", 2);
196 fb_binding.start += 2;
197 }
198 }
199
200 /* Insert the new menu. */
201 insert_text_into_node
202 (node, fb_binding.start, contents + contents_offset, size - contents_offset);
203 }
204
205 static void
206 insert_text_into_node (NODE *node, long start, char *text, int textlen)
207 {
208 char *contents;
209 long end;
210
211 end = node->nodelen;
212
213 contents = xmalloc (node->nodelen + textlen + 1);
214 memcpy (contents, node->contents, start);
215 memcpy (contents + start, text, textlen);
216 memcpy (contents + start + textlen, node->contents + start, end - start + 1);
217 free (node->contents);
218 node->contents = contents;
219 node->nodelen += textlen;
220 }
221
222 /* Return directory entry. Return value should not be freed or modified. */
223 REFERENCE *
224 lookup_dir_entry (char *label, int sloppy)
225 {
226 REFERENCE *entry;
227
228 if (!dir_node)
229 dir_node = build_dir_node ();
230
231 entry = info_get_menu_entry_by_label (dir_node, label, sloppy);
232
233 return entry;
234 }
235
236 /* Look up entry in "dir" in search directory. Return
237 value is a pointer to a newly allocated REFERENCE. */
238 REFERENCE *
239 dir_entry_of_infodir (char *label, char *searchdir)
240 {
241 char *dir_fullpath;
242 int len;
243 char *result;
244
245 struct stat dummy;
246 char *entry_fullpath;
247
248 NODE *dir_node;
249 REFERENCE *entry;
250
251 if (IS_ABSOLUTE(searchdir))
252 len = xasprintf (&dir_fullpath, "%s/dir%s", searchdir, PADDING);
253 else
254 len = xasprintf (&dir_fullpath, "./%s/dir%s", searchdir, PADDING);
255 dir_fullpath[len - strlen(PADDING)] = '\0';
256
257 result = info_check_compressed (dir_fullpath, &dummy);
258 if (!result)
259 {
260 free (dir_fullpath);
261 return 0;
262 }
263
264 dir_node = info_get_node (dir_fullpath, "Top");
265 free (dir_fullpath);
266 entry = info_get_menu_entry_by_label (dir_node, label, 1);
267 if (!entry || !entry->filename)
268 {
269 free_history_node (dir_node);
270 return 0;
271 /* A dir entry with no filename is unlikely, but not impossible. */
272 }
273
274 entry = info_copy_reference (entry);
275 entry_fullpath = info_add_extension (searchdir, entry->filename, &dummy);
276 if (entry_fullpath)
277 {
278 free (entry->filename);
279 entry->filename = entry_fullpath;
280 }
281
282 free_history_node (dir_node);
283 return entry;
284 }
285