/* grecs - Gray's Extensible Configuration System Copyright (C) 2007-2022 Sergey Poznyakoff Grecs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Grecs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Grecs. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include static struct type_string { char *nominative; char *genitive; } type_string[] = { /* TRANSLATORS: The msgids below are data types in direct (nominative) and indirect (most often, genitive) cases. The indirect case is used only in the construct "list %s" (e.g., for "number" data type, this makes "list of numbers"). Please, translate it so that it forms a correct sentence in this context. */ [grecs_type_void] = { N_("void"), NULL }, [grecs_type_string] = { N_("string"), N_("of strings") }, [grecs_type_short] = { N_("number"), N_("of numbers") }, [grecs_type_ushort] = { N_("number"), N_("of numbers") }, [grecs_type_int] = { N_("number"), N_("of numbers") }, [grecs_type_uint] = { N_("number"), N_("of numbers") }, [grecs_type_long] = { N_("number"), N_("of numbers") }, [grecs_type_ulong] = { N_("number"), N_("of numbers") }, [grecs_type_size] = { N_("number"), N_("of numbers") }, [grecs_type_time] = { N_("time"), N_("of times") }, [grecs_type_bool] = { N_("boolean"), N_("of booleans") }, [grecs_type_ipv4] = { N_("IPv4"), N_("of IPv4") }, [grecs_type_cidr] = { N_("CIDR"), N_("of CIDR") }, [grecs_type_host] = { N_("hostname"), N_("of hostnames") }, [grecs_type_sockaddr] = { N_("sockaddr"), N_("of sockaddr") }, [grecs_type_section] = { N_("section"), NULL }, [grecs_type_null] = { N_("null"), NULL }, }; const char * grecs_data_type_string(enum grecs_data_type type) { if (type >= 0 && type <= sizeof(type_string) / sizeof(type_string[0]) && type_string[type].nominative) { return type_string[type].nominative; } return "UNKNOWN?"; } const char * grecs_data_type_genitive(enum grecs_data_type type) { if (type >= 0 && type <= sizeof(type_string) / sizeof(type_string[0]) && type_string[type].genitive) { return type_string[type].genitive; } return "UNKNOWN?"; } static void format_level(unsigned level, FILE *stream) { while (level--) fprintf(stream, " "); } void grecs_print_docstring(const char *docstring, unsigned level, FILE *stream) { size_t len = strlen(docstring); int width = 78 - level * 2; if (width < 0) { width = 78; level = 0; } while (len) { size_t seglen; const char *p; for (seglen = 0, p = docstring; p < docstring + width && *p; p++) { if (*p == '\n') { seglen = p - docstring; break; } if (isspace(*p)) seglen = p - docstring; } if (seglen == 0 || *p == 0) seglen = p - docstring; format_level(level, stream); fprintf(stream, "# "); fwrite(docstring, seglen, 1, stream); fputc('\n', stream); len -= seglen; docstring += seglen; if (*docstring == '\n') { docstring++; len--; } else while (*docstring && isspace(*docstring)) { docstring++; len--; } } } void grecs_print_simple_statement(struct grecs_keyword *kwp, unsigned level, FILE *stream) { const char *argstr; if (kwp->flags & GRECS_INAC) grecs_print_docstring(N_("Disabled;"), level, stream); if (kwp->docstring) grecs_print_docstring(kwp->docstring, level, stream); format_level(level, stream); if (kwp->argname) argstr = kwp->argname; else argstr = N_("arg"); if (argstr[0] == 0) fprintf(stream, "%s;\n", kwp->ident); else if (strchr("<[", argstr[0])) fprintf(stream, "%s %s;\n", kwp->ident, gettext(argstr)); else if (argstr[0] == '\'') fprintf(stream, "%s %s;\n", kwp->ident, argstr + 1); else if (strchr (argstr, ':')) fprintf(stream, "%s <%s>;\n", kwp->ident, gettext(argstr)); else { fprintf(stream, "%s <%s: ", kwp->ident, gettext(argstr)); if (kwp->flags & GRECS_LIST) /* TRANSLATORS: The %s in this message will be expanded to a data type in indirect case (see above). The two must form a grammatically correct sentence. */ fprintf(stream, _("list %s"), gettext(grecs_data_type_genitive(kwp->type))); else fprintf(stream, "%s", gettext(grecs_data_type_string(kwp->type))); fprintf(stream, ">;\n"); } } void grecs_print_block_statement(struct grecs_keyword *kwp, unsigned level, FILE *stream) { if (kwp->docstring) grecs_print_docstring(kwp->docstring, level, stream); format_level(level, stream); fprintf(stream, "%s", kwp->ident); if (kwp->argname && kwp->argname[0]) { if (kwp->argname[0] == '\'') fprintf(stream, " %s", kwp->argname + 1); else fprintf(stream, " <%s>", gettext(kwp->argname)); } fprintf(stream, " {\n"); grecs_print_statement_array(kwp->kwd, 0, level + 1, stream); format_level(level, stream); fprintf(stream, "}\n"); } void grecs_print_statement_array(struct grecs_keyword *kwp, unsigned n, unsigned level, FILE *stream) { if (!kwp) { return; } for (; kwp->ident; kwp++, n++) { if (kwp->flags & GRECS_HIDDEN) continue; if (n) fputc('\n', stream); if (kwp->type == grecs_type_section) grecs_print_block_statement(kwp, level, stream); else grecs_print_simple_statement(kwp, level, stream); } } void grecs_format_locus(grecs_locus_t *locus, struct grecs_format_closure *clos) { if (locus) { char *str = NULL; size_t size = 0; if (locus->beg.col == 0) grecs_asprintf(&str, &size, "%s:%u", locus->beg.file, locus->beg.line); else if (strcmp(locus->beg.file, locus->end.file)) grecs_asprintf(&str, &size, "%s:%u.%u-%s:%u.%u", locus->beg.file, locus->beg.line, locus->beg.col, locus->end.file, locus->end.line, locus->end.col); else if (locus->beg.line != locus->end.line) grecs_asprintf(&str, &size, "%s:%u.%u-%u.%u", locus->beg.file, locus->beg.line, locus->beg.col, locus->end.line, locus->end.col); else if (locus->beg.col != locus->end.col) grecs_asprintf(&str, &size, "%s:%u.%u-%u", locus->beg.file, locus->beg.line, locus->beg.col, locus->end.col); else grecs_asprintf(&str, &size, "%s:%u.%u", locus->beg.file, locus->beg.line, locus->beg.col); clos->fmtfun(str, clos->data); free(str); } } void grecs_format_node_path(struct grecs_node *node, int flags, struct grecs_format_closure *clos) { char delim[2] = "."; if (!node) { clos->fmtfun("NULL", clos->data); return; } if (node->up) grecs_format_node_path(node->up, flags, clos); if (node->type == grecs_node_root) return; if (flags & _GRECS_NODE_MASK_DELIM) delim[0] = flags & _GRECS_NODE_MASK_DELIM; clos->fmtfun(delim, clos->data); clos->fmtfun(node->ident, clos->data); if (node->type == grecs_node_block && !GRECS_VALUE_EMPTY_P(node->v.value)) { clos->fmtfun("=", clos->data); grecs_format_value(node->v.value, flags|GRECS_NODE_FLAG_QUOTE, clos); } } void grecs_format_value(struct grecs_value *val, int flags, struct grecs_format_closure *clos) { int i; struct grecs_list_entry *ep; size_t clen; int need_quote; if (!val) return; switch (val->type) { case GRECS_TYPE_STRING: clen = wordsplit_c_quoted_length(val->v.string, flags & GRECS_NODE_FLAG_QUOTE_HEX, &need_quote); if (flags & GRECS_NODE_FLAG_QUOTE) need_quote = 1; else if (flags & GRECS_NODE_FLAG_NOQUOTE) need_quote = 0; if (need_quote) { char *cbuf = grecs_malloc(clen + 1); wordsplit_c_quote_copy(cbuf, val->v.string, flags & GRECS_NODE_FLAG_QUOTE_HEX); cbuf[clen] = 0; clos->fmtfun("\"", clos->data); clos->fmtfun(cbuf, clos->data); clos->fmtfun("\"", clos->data); grecs_free(cbuf); } else clos->fmtfun(val->v.string, clos->data); break; case GRECS_TYPE_LIST: clos->fmtfun("(", clos->data); for (ep = val->v.list->head; ep; ep = ep->next) { grecs_format_value(ep->data, flags, clos); if (ep->next) clos->fmtfun(", ", clos->data); } clos->fmtfun(")", clos->data); break; case GRECS_TYPE_ARRAY: for (i = 0; i < val->v.arg.c; i++) { if (i) clos->fmtfun(" ", clos->data); grecs_format_value(val->v.arg.v[i], flags, clos); } } } int grecs_format_node(struct grecs_node *node, int flags, struct grecs_format_closure *clos) { const char *delim_str = NULL; if (!(flags & _GRECS_NODE_MASK_OUTPUT)) { errno = EINVAL; return 1; } if (!node) { clos->fmtfun("NULL", clos->data); return 0; } switch (node->type) { case grecs_node_root: case grecs_node_block: if (flags & GRECS_NODE_FLAG_DESCEND) { for (node = node->down; node; node = node->next) { grecs_format_node(node, flags, clos); if (node->next) clos->fmtfun("\n", clos->data); } break; } case grecs_node_stmt: if (flags & GRECS_NODE_FLAG_LOCUS) { grecs_locus_t *locus; if (flags & GRECS_NODE_FLAG_PATH) { if (flags & GRECS_NODE_FLAG_VALUE) locus = &node->locus; else locus = &node->idloc; } else if (flags & GRECS_NODE_FLAG_VALUE) locus = &node->v.value->locus; else locus = &node->locus; grecs_format_locus(locus, clos); delim_str = ": "; } if (flags & GRECS_NODE_FLAG_PATH) { if (delim_str) clos->fmtfun(delim_str, clos->data); grecs_format_node_path(node, flags, clos); delim_str = ": "; } if (flags & GRECS_NODE_FLAG_VALUE) { if (delim_str) clos->fmtfun(delim_str, clos->data); grecs_format_value(node->v.value, flags, clos); } } return 0; } static int txtacc_fmt(const char *str, void *data) { struct grecs_txtacc *acc = data; grecs_txtacc_grow(acc, str, strlen(str)); return 0; } void grecs_txtacc_format_value(struct grecs_value *val, int flags, struct grecs_txtacc *acc) { struct grecs_format_closure clos = { txtacc_fmt, acc }; grecs_format_value(val, flags, &clos); } static int file_fmt(const char *str, void *data) { fputs(str, (FILE*)data); return 0; } void grecs_print_locus(grecs_locus_t *locus, FILE *fp) { struct grecs_format_closure clos = { file_fmt, fp }; grecs_format_locus(locus, &clos); } void grecs_print_node_path(struct grecs_node *node, int flag, FILE *fp) { struct grecs_format_closure clos = { file_fmt, fp }; grecs_format_node_path(node, flag, &clos); } void grecs_print_value(struct grecs_value *val, int flags, FILE *fp) { struct grecs_format_closure clos = { file_fmt, fp }; grecs_format_value(val, flags, &clos); } int grecs_print_node(struct grecs_node *node, int flags, FILE *fp) { struct grecs_format_closure clos = { file_fmt, fp }; return grecs_format_node(node, flags, &clos); }