1 /* $Id: reorg.c,v 1.6 2019/05/01 11:03:31 schwarze Exp $ */
2 /*
3  * Copyright (c) 2019 Ingo Schwarze <schwarze@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include "string.h"
18 
19 #include "node.h"
20 #include "reorg.h"
21 
22 /*
23  * The implementation of the tree reorganizer.
24  */
25 
26 static void
reorg_root(struct pnode * root,const char * sec)27 reorg_root(struct pnode *root, const char *sec)
28 {
29 	struct pnode	*date, *info, *name, *vol, *nc;
30 
31 	if (root == NULL)
32 		return;
33 
34 	/* Collect prologue information. */
35 
36 	if ((date = pnode_takefirst(root, NODE_PUBDATE)) == NULL &&
37 	    (date = pnode_takefirst(root, NODE_DATE)) == NULL) {
38 		date = pnode_alloc(NULL);
39 		pnode_alloc_text(date, "$Mdocdate" "$");
40 	}
41 	date->node = NODE_DATE;
42 	date->parent = root;
43 
44 	name = vol = NULL;
45 	if ((nc = pnode_findfirst(root, NODE_REFMETA)) != NULL) {
46 		name = pnode_takefirst(nc, NODE_REFENTRYTITLE);
47 		vol = pnode_takefirst(nc, NODE_MANVOLNUM);
48 	}
49 	if (name == NULL) {
50 		name = pnode_alloc(NULL);
51 		name->node = NODE_REFENTRYTITLE;
52 		name->parent = root;
53 		pnode_alloc_text(name,
54 		    pnode_getattr_raw(root, ATTRKEY_ID, "UNKNOWN"));
55 	}
56 	if (vol == NULL || sec != NULL) {
57 		pnode_unlink(vol);
58 		vol = pnode_alloc(NULL);
59 		vol->node = NODE_MANVOLNUM;
60 		vol->parent = root;
61 		pnode_alloc_text(vol, sec == NULL ? "1" : sec);
62 	}
63 
64 	/* Insert prologue information at the beginning. */
65 
66 	if (pnode_findfirst(root, NODE_REFNAMEDIV) == NULL &&
67 	    ((info = pnode_findfirst(root, NODE_BOOKINFO)) != NULL ||
68 	     (info = pnode_findfirst(root, NODE_REFENTRYINFO)) != NULL)) {
69 		if ((nc = pnode_takefirst(info, NODE_ABSTRACT)) != NULL)
70 			TAILQ_INSERT_HEAD(&root->childq, nc, child);
71 		if ((nc = pnode_takefirst(info, NODE_TITLE)) != NULL)
72 			TAILQ_INSERT_HEAD(&root->childq, nc, child);
73 	}
74 	TAILQ_INSERT_HEAD(&root->childq, vol, child);
75 	TAILQ_INSERT_HEAD(&root->childq, name, child);
76 	TAILQ_INSERT_HEAD(&root->childq, date, child);
77 }
78 
79 static void
reorg_refentry(struct pnode * n)80 reorg_refentry(struct pnode *n)
81 {
82 	struct pnode	*info, *meta, *nc, *title;
83 	struct pnode	*match, *later;
84 
85 	/* Collect nodes that remained behind from the prologue. */
86 
87 	meta = NULL;
88 	info = pnode_takefirst(n, NODE_BOOKINFO);
89 	if (info != NULL && TAILQ_FIRST(&info->childq) == NULL) {
90 		pnode_unlink(info);
91 		info = NULL;
92 	}
93 	if (info == NULL) {
94 		info = pnode_takefirst(n, NODE_REFENTRYINFO);
95 		if (info != NULL && TAILQ_FIRST(&info->childq) == NULL) {
96 			pnode_unlink(info);
97 			info = NULL;
98 		}
99 		if (info == NULL)
100 			info = pnode_takefirst(n, NODE_INFO);
101 		meta = pnode_takefirst(n, NODE_REFMETA);
102 		if (meta != NULL && TAILQ_FIRST(&meta->childq) == NULL) {
103 			pnode_unlink(meta);
104 			meta = NULL;
105 		}
106 	}
107 	if (info == NULL && meta == NULL)
108 		return;
109 
110 	/*
111 	 * Find the best place to put this information.
112 	 * Use the last existing AUTHORS node, if any.
113 	 * Otherwise, put it behind all standard sections that
114 	 * conventionally precede AUTHORS, and also behind any
115 	 * non-standard sections that follow the last of these,
116 	 * but before the next standard section.
117 	 */
118 
119 	match = later = NULL;
120 	TAILQ_FOREACH(nc, &n->childq, child) {
121 		switch (nc->node) {
122 		case NODE_REFENTRY:
123 		case NODE_REFNAMEDIV:
124 		case NODE_REFSYNOPSISDIV:
125 			later = NULL;
126 			continue;
127 		case NODE_APPENDIX:
128 		case NODE_INDEX:
129 			if (later == NULL)
130 				later = nc;
131 			continue;
132 		default:
133 			break;
134 		}
135 		if ((title = pnode_findfirst(nc, NODE_TITLE)) == NULL ||
136 		    (title = TAILQ_FIRST(&title->childq)) == NULL ||
137 		    title->node != NODE_TEXT)
138 			continue;
139 		if (strcasecmp(title->b, "AUTHORS") == 0 ||
140 		    strcasecmp(title->b, "AUTHOR") == 0)
141 			match = nc;
142 		else if (strcasecmp(title->b, "NAME") == 0 ||
143 		    strcasecmp(title->b, "SYNOPSIS") == 0 ||
144 		    strcasecmp(title->b, "DESCRIPTION") == 0 ||
145 		    strcasecmp(title->b, "RETURN VALUES") == 0 ||
146 		    strcasecmp(title->b, "ENVIRONMENT") == 0 ||
147 		    strcasecmp(title->b, "FILES") == 0 ||
148 		    strcasecmp(title->b, "EXIT STATUS") == 0 ||
149 		    strcasecmp(title->b, "EXAMPLES") == 0 ||
150 		    strcasecmp(title->b, "DIAGNOSTICS") == 0 ||
151 		    strcasecmp(title->b, "ERRORS") == 0 ||
152 		    strcasecmp(title->b, "SEE ALSO") == 0 ||
153 		    strcasecmp(title->b, "STANDARDS") == 0 ||
154 		    strcasecmp(title->b, "HISTORY") == 0)
155 			later = NULL;
156 		else if ((strcasecmp(title->b, "CAVEATS") == 0 ||
157 		    strcasecmp(title->b, "BUGS") == 0) &&
158 		    later == NULL)
159 			later = nc;
160 	}
161 
162 	/*
163 	 * If no AUTHORS section was found, create one from scratch,
164 	 * and insert that at the place selected earlier.
165 	 */
166 
167 	if (match == NULL) {
168 		match = pnode_alloc(NULL);
169 		match->node = NODE_SECTION;
170 		match->flags |= NFLAG_SPC;
171 		match->parent = n;
172 		nc = pnode_alloc(match);
173 		nc->node = NODE_TITLE;
174 		nc->flags |= NFLAG_SPC;
175 		nc = pnode_alloc_text(nc, "AUTHORS");
176 		nc->flags |= NFLAG_SPC;
177 		if (later == NULL)
178 			TAILQ_INSERT_TAIL(&n->childq, match, child);
179 		else
180 			TAILQ_INSERT_BEFORE(later, match, child);
181 	}
182 
183 	/*
184 	 * Dump the stuff excised at the beginning
185 	 * into this AUTHORS section.
186 	 */
187 
188 	if (info != NULL)
189 		TAILQ_INSERT_TAIL(&match->childq, info, child);
190 	if (meta != NULL)
191 		TAILQ_INSERT_TAIL(&match->childq, meta, child);
192 }
193 
194 static void
default_title(struct pnode * n,const char * title)195 default_title(struct pnode *n, const char *title)
196 {
197 	struct pnode	*nc;
198 
199 	if (n->parent == NULL)
200 		return;
201 
202 	TAILQ_FOREACH(nc, &n->childq, child)
203 		if (nc->node == NODE_TITLE)
204 			return;
205 
206 	nc = pnode_alloc(NULL);
207 	nc->node = NODE_TITLE;
208 	nc->parent = n;
209 	TAILQ_INSERT_HEAD(&n->childq, nc, child);
210 	pnode_alloc_text(nc, title);
211 }
212 
213 static void
reorg_function(struct pnode * n)214 reorg_function(struct pnode *n)
215 {
216 	struct pnode	*nc;
217 	size_t		 sz;
218 
219 	if ((nc = TAILQ_FIRST(&n->childq)) != NULL &&
220 	    nc->node == NODE_TEXT &&
221 	    TAILQ_NEXT(nc, child) == NULL &&
222 	    (sz = strlen(nc->b)) > 2 &&
223 	    nc->b[sz - 2] == '(' && nc->b[sz - 1] == ')')
224 		nc->b[sz - 2] = '\0';
225 }
226 
227 static void
reorg_recurse(struct pnode * n)228 reorg_recurse(struct pnode *n)
229 {
230 	struct pnode	*nc;
231 
232 	if (n == NULL)
233 		return;
234 
235 	switch (n->node) {
236 	case NODE_ABSTRACT:
237 		default_title(n, "Abstract");
238 		n->node = NODE_SECTION;
239 		break;
240 	case NODE_APPENDIX:
241 		if (n->parent == NULL)
242 			reorg_refentry(n);
243 		default_title(n, "Appendix");
244 		break;
245 	case NODE_CAUTION:
246 		default_title(n, "Caution");
247 		n->node = NODE_NOTE;
248 		break;
249 	case NODE_FUNCTION:
250 		reorg_function(n);
251 		break;
252 	case NODE_LEGALNOTICE:
253 		default_title(n, "Legal Notice");
254 		n->node = NODE_SIMPLESECT;
255 		break;
256 	case NODE_NOTE:
257 		default_title(n, "Note");
258 		break;
259 	case NODE_PREFACE:
260 		if (n->parent == NULL)
261 			reorg_refentry(n);
262 		default_title(n, "Preface");
263 		n->node = NODE_SECTION;
264 		break;
265 	case NODE_REFENTRY:
266 		reorg_refentry(n);
267 		break;
268 	case NODE_SECTION:
269 		if (n->parent == NULL)
270 			reorg_refentry(n);
271 		/* FALLTHROUGH */
272 	case NODE_SIMPLESECT:
273 		default_title(n, "Untitled");
274 		break;
275 	case NODE_TIP:
276 		default_title(n, "Tip");
277 		n->node = NODE_NOTE;
278 		break;
279 	case NODE_WARNING:
280 		default_title(n, "Warning");
281 		n->node = NODE_NOTE;
282 		break;
283 	default:
284 		break;
285 	}
286 
287 	TAILQ_FOREACH(nc, &n->childq, child)
288 		reorg_recurse(nc);
289 }
290 
291 void
ptree_reorg(struct ptree * tree,const char * sec)292 ptree_reorg(struct ptree *tree, const char *sec)
293 {
294 	reorg_root(tree->root, sec);
295 	reorg_recurse(tree->root);
296 }
297