1 /* cfg_format.c -- convert configuration parse tree to human-readable format.
2    Copyright (C) 2007-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as
6    published by the Free Software Foundation; either version 3, or (at
7    your option) any later version.
8 
9    GNU Mailutils is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 #include <stdlib.h>
22 #include <mailutils/alloc.h>
23 #include <mailutils/stream.h>
24 #include <mailutils/error.h>
25 #include <mailutils/cfg.h>
26 #include <mailutils/wordsplit.h>
27 #include <mailutils/nls.h>
28 #include <mailutils/iterator.h>
29 #include <ctype.h>
30 
31 struct tree_print
32 {
33   int flags;
34   unsigned level;
35   mu_stream_t stream;
36   char *buf;
37   size_t bufsize;
38 };
39 
40 static void
format_level(mu_stream_t stream,int level)41 format_level (mu_stream_t stream, int level)
42 {
43   while (level--)
44     mu_stream_write (stream, "  ", 2, NULL);
45 }
46 
47 static void
format_string_value(struct tree_print * tp,const char * str)48 format_string_value (struct tree_print *tp, const char *str)
49 {
50   size_t size;
51   int quote;
52   char *p;
53 
54   size = mu_wordsplit_c_quoted_length (str, 1, &quote);
55   if (quote)
56     size += 2;
57   size++;
58   if (size > tp->bufsize)
59     {
60       p = mu_realloc (tp->buf, size);
61       tp->bufsize = size;
62       tp->buf = p;
63     }
64 
65   p = tp->buf;
66   if (quote)
67     {
68       tp->buf[0] = '"';
69       tp->buf[size-2] = '"';
70       p++;
71     }
72   tp->buf[size-1] = 0;
73   mu_wordsplit_c_quote_copy (p, str, 1);
74   mu_stream_write (tp->stream, tp->buf, size - 1, NULL);
75 }
76 
77 static void format_value (struct tree_print *tp, mu_config_value_t *val);
78 
79 static void
format_list_value(struct tree_print * tp,mu_config_value_t * val)80 format_list_value (struct tree_print *tp, mu_config_value_t *val)
81 {
82   int i;
83   mu_iterator_t itr;
84   mu_stream_write (tp->stream, "(", 1, NULL);
85   mu_list_get_iterator (val->v.list, &itr);
86 
87   for (mu_iterator_first (itr), i = 0;
88        !mu_iterator_is_done (itr); mu_iterator_next (itr), i++)
89     {
90       mu_config_value_t *p;
91       mu_iterator_current (itr, (void**)&p);
92       if (i)
93 	mu_stream_write (tp->stream, ", ", 2, NULL);
94       format_value (tp, p);
95     }
96   mu_iterator_destroy (&itr);
97   mu_stream_write (tp->stream, ")", 1, NULL);
98 }
99 
100 static void
format_array_value(struct tree_print * tp,mu_config_value_t * val)101 format_array_value (struct tree_print *tp, mu_config_value_t *val)
102 {
103   int i;
104 
105   for (i = 0; i < val->v.arg.c; i++)
106     {
107       if (i)
108 	mu_stream_write (tp->stream, " ", 1, NULL);
109       format_value (tp, &val->v.arg.v[i]);
110     }
111 }
112 
113 static void
format_value(struct tree_print * tp,mu_config_value_t * val)114 format_value (struct tree_print *tp, mu_config_value_t *val)
115 {
116   switch (val->type)
117     {
118     case MU_CFG_STRING:
119       format_string_value (tp, val->v.string);
120       break;
121 
122     case MU_CFG_LIST:
123       format_list_value (tp, val);
124       break;
125 
126     case MU_CFG_ARRAY:
127       format_array_value (tp, val);
128     }
129 }
130 
131 static void
format_path(struct tree_print * tp,const mu_cfg_node_t * node,int delim)132 format_path (struct tree_print *tp, const mu_cfg_node_t *node, int delim)
133 {
134   char c;
135 
136   if (node->parent)
137     format_path (tp, node->parent, MU_CFG_PATH_DELIM);
138 
139   mu_stream_write (tp->stream, node->tag, strlen (node->tag), NULL);
140   if (node->type == mu_cfg_node_statement && node->label)
141     {
142       mu_stream_write (tp->stream, "=\"", 2, NULL);
143       format_value (tp, node->label);
144       mu_stream_write (tp->stream, "\"", 1, NULL);
145     }
146   c = delim;
147   mu_stream_write (tp->stream, &c, 1, NULL);
148 }
149 
150 static int
format_node(const mu_cfg_node_t * node,void * data)151 format_node (const mu_cfg_node_t *node, void *data)
152 {
153   struct tree_print *tp = data;
154 
155   if ((tp->flags & MU_CF_FMT_LOCUS) && node->locus.beg.mu_file)
156     mu_stream_printf (tp->stream, "# %lu \"%s\"\n",
157 		      (unsigned long) node->locus.beg.mu_line,
158 		      node->locus.beg.mu_file);
159   format_level (tp->stream, tp->level);
160   switch (node->type)
161     {
162     case mu_cfg_node_undefined:
163       mu_stream_printf (tp->stream, "%s",
164 			_("ERROR: undefined statement"));
165       break;
166 
167     case mu_cfg_node_statement:
168       if (tp->flags & MU_CF_FMT_PARAM_PATH)
169 	return MU_CFG_ITER_OK;
170       else
171 	{
172 	  mu_stream_write (tp->stream, node->tag, strlen (node->tag), NULL);
173 	  if (node->label)
174 	    {
175 	      mu_stream_write (tp->stream, " ", 1, NULL);
176 	      format_value (tp, node->label);
177 	    }
178 	  mu_stream_write (tp->stream, " {", 2, NULL);
179 	  tp->level++;
180 	}
181       break;
182 
183     case mu_cfg_node_param:
184       if (tp->flags & MU_CF_FMT_VALUE_ONLY)
185 	format_value (tp, node->label);
186       else if (tp->flags & MU_CF_FMT_PARAM_PATH)
187 	{
188 	  format_path (tp, node, ':');
189 	  mu_stream_write (tp->stream, " ", 1, NULL);
190 	  format_value (tp, node->label);
191 	}
192       else
193 	{
194 	  mu_stream_write (tp->stream, node->tag, strlen (node->tag), NULL);
195 	  if (node->label)
196 	    {
197 	      mu_stream_write (tp->stream, " ", 1, NULL);
198 	      format_value (tp, node->label);
199 	      mu_stream_write (tp->stream, ";", 1, NULL);
200 	    }
201 	}
202       break;
203     }
204   mu_stream_write (tp->stream, "\n", 1, NULL);
205   return MU_CFG_ITER_OK;
206 }
207 
208 static int
format_node_end(const mu_cfg_node_t * node,void * data)209 format_node_end (const mu_cfg_node_t *node, void *data)
210 {
211   struct tree_print *tp = data;
212   if (!(tp->flags & MU_CF_FMT_PARAM_PATH))
213     {
214       tp->level--;
215       format_level (tp->stream, tp->level);
216       mu_stream_write (tp->stream, "};\n", 3, NULL);
217     }
218   return MU_CFG_ITER_OK;
219 }
220 
221 void
mu_cfg_format_parse_tree(mu_stream_t stream,mu_cfg_tree_t * tree,int flags)222 mu_cfg_format_parse_tree (mu_stream_t stream, mu_cfg_tree_t *tree, int flags)
223 {
224   struct mu_cfg_iter_closure clos;
225   struct tree_print t;
226 
227   t.flags = flags;
228   t.level = 0;
229   t.stream = stream;
230   t.buf = NULL;
231   t.bufsize = 0;
232   clos.beg = format_node;
233   clos.end = format_node_end;
234   clos.data = &t;
235   mu_cfg_preorder (tree->nodes, &clos);
236   free (t.buf);
237 }
238 
239 void
mu_cfg_format_node(mu_stream_t stream,const mu_cfg_node_t * node,int flags)240 mu_cfg_format_node (mu_stream_t stream, const mu_cfg_node_t *node, int flags)
241 {
242   struct tree_print t;
243 
244   if (node->type == mu_cfg_node_statement)
245     flags &= ~MU_CF_FMT_VALUE_ONLY;
246   t.flags = flags;
247   t.level = 0;
248   t.stream = stream;
249   t.buf = NULL;
250   t.bufsize = 0;
251   format_node (node, &t);
252   if (node->type == mu_cfg_node_statement)
253     {
254       struct mu_cfg_iter_closure clos;
255       clos.beg = format_node;
256       clos.end = format_node_end;
257       clos.data = &t;
258       mu_cfg_preorder (node->nodes, &clos);
259       format_node_end (node, &t);
260     }
261 }
262 
263 
264 
265 const char *
mu_c_type_string(int type)266 mu_c_type_string (int type)
267 {
268   switch (type)
269     {
270     case mu_c_string:
271       return N_("string");
272     case mu_c_short:
273     case mu_c_ushort:
274     case mu_c_int:
275     case mu_c_uint:
276     case mu_c_long:
277     case mu_c_ulong:
278     case mu_c_size:
279     case mu_c_off:
280     case mu_c_incr:
281       return N_("number");
282     case mu_c_time:
283       return N_("time");
284     case mu_c_bool:
285       return N_("boolean");
286     case mu_c_ipv4:
287       return N_("ipv4");
288     case mu_c_cidr:
289       return N_("cidr");
290     case mu_c_host:
291       return N_("host");
292     case mu_cfg_section:
293       return N_("section");
294     case mu_cfg_callback:
295       return N_("callback");
296     default:
297       break;
298     }
299   return N_("unknown");
300 }
301 
302 void
mu_cfg_format_docstring(mu_stream_t stream,const char * docstring,int level)303 mu_cfg_format_docstring (mu_stream_t stream, const char *docstring, int level)
304 {
305   size_t len = strlen (docstring);
306   int width = 78 - level * 2;
307 
308   if (width < 0)
309     {
310       width = 78;
311       level = 0;
312     }
313 
314   while (len)
315     {
316       size_t seglen;
317       const char *p;
318 
319       for (seglen = 0, p = docstring; p < docstring + width && *p; p++)
320 	{
321 	  if (*p == '\n')
322 	    {
323 	      seglen = p - docstring;
324 	      break;
325 	    }
326 	  if (isspace (*p))
327 	    seglen = p - docstring;
328 	}
329       if (seglen == 0 || *p == 0)
330 	seglen = p - docstring;
331 
332       format_level (stream, level);
333       mu_stream_write (stream, "# ", 2, NULL);
334       mu_stream_write (stream, docstring, seglen, NULL);
335       mu_stream_write (stream, "\n", 1, NULL);
336       len -= seglen;
337       docstring += seglen;
338       if (*docstring == '\n')
339 	{
340 	  docstring++;
341 	  len--;
342 	}
343       else
344 	while (*docstring && isspace (*docstring))
345 	  {
346 	    docstring++;
347 	    len--;
348 	  }
349     }
350 }
351 
352 static void
format_param(mu_stream_t stream,struct mu_cfg_param * param,int level)353 format_param (mu_stream_t stream, struct mu_cfg_param *param, int level)
354 {
355   if (param->docstring)
356     mu_cfg_format_docstring (stream, gettext (param->docstring), level);
357   format_level (stream, level);
358   if (param->argname && strchr (param->argname, ':'))
359     mu_stream_printf (stream, "%s <%s>;\n",
360 		      param->ident,
361 		      gettext (param->argname));
362   else if (MU_CFG_IS_LIST (param->type))
363     mu_stream_printf (stream, "%s <%s: list of %s>;\n",
364 		      param->ident,
365 		      gettext (param->argname ?
366 			       param->argname : N_("arg")),
367 	gettext (mu_c_type_string (MU_CFG_TYPE (param->type))));
368   else
369     mu_stream_printf (stream, "%s <%s: %s>;\n",
370 		      param->ident,
371 		      gettext (param->argname ?
372 			       param->argname : N_("arg")),
373 		      gettext (mu_c_type_string (param->type)));
374 }
375 
376 static void format_container (mu_stream_t stream, struct mu_cfg_cont *cont,
377 			      int level);
378 
379 static int
_f_helper(void * item,void * data)380 _f_helper (void *item, void *data)
381 {
382   struct tree_print *tp = data;
383   struct mu_cfg_cont *cont = item;
384   format_container (tp->stream, cont, tp->level);
385   return 0;
386 }
387 
388 static void
format_section(mu_stream_t stream,struct mu_cfg_section * sect,int level)389 format_section (mu_stream_t stream, struct mu_cfg_section *sect, int level)
390 {
391   struct tree_print c;
392   if (sect->docstring)
393     mu_cfg_format_docstring (stream, gettext (sect->docstring), level);
394   format_level (stream, level);
395   if (sect->ident)
396     {
397       mu_stream_write (stream, sect->ident, strlen (sect->ident), NULL);
398       if (sect->label)
399 	{
400 	  mu_stream_write (stream, " ", 1, NULL);
401 	  mu_stream_write (stream, sect->label, strlen (sect->label), NULL);
402 	}
403       mu_stream_write (stream, " {\n", 3, NULL);
404       c.stream = stream;
405       c.level = level + 1;
406       mu_list_foreach (sect->children, _f_helper, &c);
407       format_level (stream, level);
408       mu_stream_write (stream, "};\n\n", 4, NULL);
409     }
410   else
411     {
412       c.stream = stream;
413       c.level = level;
414       mu_list_foreach (sect->children, _f_helper, &c);
415     }
416 }
417 
418 static void
format_container(mu_stream_t stream,struct mu_cfg_cont * cont,int level)419 format_container (mu_stream_t stream, struct mu_cfg_cont *cont, int level)
420 {
421   switch (cont->type)
422     {
423     case mu_cfg_cont_section:
424       format_section (stream, &cont->v.section, level);
425       break;
426 
427     case mu_cfg_cont_param:
428       format_param (stream, &cont->v.param, level);
429       break;
430     }
431 }
432 
433 void
mu_cfg_format_container(mu_stream_t stream,struct mu_cfg_cont * cont)434 mu_cfg_format_container (mu_stream_t stream, struct mu_cfg_cont *cont)
435 {
436   format_container (stream, cont, 0);
437 }
438