#include <inttypes.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#ifdef USE_STRCOMPAT
#include "strcompat.h"
#endif
typedef struct usage {
char *flag;
char *longflag;
char *text;
char *arg;
xmlNode *descnode;
char *functype;
char *funcname;
struct usage *funcargs;
struct usage *optionlist;
int optional;
struct usage *next;
int nextwithsamename;
int emitted;
} *usage_t;
#define MAXCOMMANDS 100
usage_t usage_head[MAXCOMMANDS], usage_tail[MAXCOMMANDS];
char commandnames[MAXCOMMANDS][MAXNAMLEN];
int seen_name = 0;
int seen_usage = 0;
int multi_command_syntax = 0;
int funccount = 0;
char *striplines(char *line);
int safe_asprintf(char **ret, const char *format, ...) __attribute__((format (printf, 2, 3)));
#define MAX(a, b) ((a<b) ? b : a)
#define MIN(a, b) ((a>b) ? b : a)
void xml2man(xmlNode *root, char *output_filename, int append_section_number);
void parseUsage(xmlNode *node, int pos);
char *xs(int count);
int force_write = 0;
void writeOptionsSub(FILE *fp, xmlNode *description, int lwc);
char *formattext(char *text, int textcontainer);
void strip_dotmxml(char *filename)
{
char *last = &filename[strlen(filename)-5];
if (!strcmp(last, ".mxml")) *last = '\0';
}
int main(int argc, char *argv[])
{
xmlDocPtr dp;
xmlNode *root;
char output_filename[MAXNAMLEN];
int append_section_number;
int realargs = argc;
int argoffset = 0;
bzero(usage_head, (sizeof(usage_t) * MAXCOMMANDS));
bzero(usage_tail, (sizeof(usage_t) * MAXCOMMANDS));
if (argc <= 1) {
fprintf(stderr, "xml2man: No arguments given.\n");
exit(-1);
}
#ifdef LIBXML_TEST_VERSION
LIBXML_TEST_VERSION;
#endif
if (!strcmp(argv[1], "-f")) {
force_write = 1;
realargs--;
argoffset++;
}
if (realargs >= 2) {
if (!(dp = xmlParseFile(argv[1+argoffset]))) {
perror(argv[0+argoffset]);
fprintf(stderr, "xml2man: could not parse XML file\n");
exit(-1);
}
} else {
char *buf = malloc(1024 * sizeof(char));
int bufpos = 0;
int bufsize = 1024;
while (1) {
char line[1026]; int len;
if (fgets(line, 1024, stdin) == NULL) break;
len = strlen(line);
while ((bufpos + len + 2) >= bufsize) {
bufsize *= 2;
buf = realloc(buf, bufsize);
}
strlcat(&buf[bufpos], line, bufsize);
bufpos += len;
}
dp = xmlParseMemory(buf, bufpos+1);
}
root = xmlDocGetRootElement(dp);
if (realargs >= 3) {
int len = MAX(strlen(argv[2+argoffset]), MAXNAMLEN-1);
strncpy(output_filename, argv[2+argoffset], len);
output_filename[len] = '\0';
append_section_number = 0;
} else if (realargs >= 2) {
int len = MAX(strlen(argv[1+argoffset]), MAXNAMLEN-4);
strncpy(output_filename, argv[1+argoffset], len);
output_filename[len] = '\0';
strip_dotmxml(output_filename);
append_section_number = 1;
} else {
output_filename[0] = 0;
append_section_number = 0;
}
xml2man(root, output_filename, append_section_number);
xmlFreeDoc(dp);
xmlCleanupParser();
return 0;
}
char *malloccat(const char *string1, const char *string2)
{
char *ret = NULL;
safe_asprintf(&ret, "%s%s", string1, string2);
if (!ret) { fprintf(stderr, "Out of memory.\n"); exit(1); }
return ret;
}
char *textmatching(char *name, xmlNode *cur, int missing_ok, char *debugstring);
xmlNode *nodematching(char *name, xmlNode *cur);
xmlNode **nodesmatching(char *name, xmlNode *cur);
void writeData(FILE *fp, xmlNode *node);
void writeUsage(FILE *fp, xmlNode *description);
int writeUsageSub(FILE *fp, int showname, usage_t myusagehead, char *name_or_empty, char *optional_separator);
void printUsageOptionList(usage_t cur, FILE *fp, char *starting, char *separator);
void xml2man(xmlNode *root, char *output_filename, int append_section_number)
{
int section;
xmlNode **othertopics;
xmlNode *names, *usage, *retvals, *env, *files, *examples, *diags, *errs;
xmlNode *seeAlso, *conformingTo, *history, *description, *bugs;
char *docdate = "January 1, 9999";
char *doctitle = "UNKNOWN MANPAGE";
char *os = "";
char *osversion = "";
char *temp;
FILE *fp;
temp = textmatching("section", root->children, 0, NULL);
if (temp) section = atoi(temp);
else { fprintf(stderr, "Assuming section 1.\n"); section = 1; }
temp = textmatching("docdate", root->children, 0, NULL);
if (temp) docdate = temp;
temp = textmatching("doctitle", root->children, 0, NULL);
if (temp) doctitle = temp;
temp = textmatching("os", root->children, 1, NULL);
if (temp) os = temp;
temp = textmatching("osversion", root->children, 1, NULL);
if (temp) osversion = temp;
names = nodematching("names", root->children);
usage = nodematching("usage", root->children);
if (usage) seen_usage = 1;
retvals = nodematching("returnvalues", root->children);
env = nodematching("environment", root->children);
files = nodematching("files", root->children);
examples = nodematching("examples", root->children);
diags = nodematching("diagnostics", root->children);
errs = nodematching("errors", root->children);
seeAlso = nodematching("seealso", root->children);
conformingTo = nodematching("conformingto", root->children);
history = nodematching("history", root->children);
description = nodematching("description", root->children);
bugs = nodematching("bugs", root->children);
othertopics = nodesmatching("topic", root->children);
if (usage) { parseUsage(usage->children, 0); }
if (!strlen(output_filename)) {
fp = stdout;
} else {
if (append_section_number) {
snprintf(output_filename, MAXNAMLEN, "%s.%d", output_filename, section);
}
if (!force_write && ((fp = fopen(output_filename, "r")))) {
fprintf(stderr, "error: file %s exists.\n", output_filename);
fclose(fp);
exit(-1);
} else {
if (!(fp = fopen(output_filename, "w"))) {
fprintf(stderr, "error: could not create file %s\n", output_filename);
exit(-1);
}
}
}
fprintf(fp, ".\\\" Automatically generated from mdocxml\n");
fprintf(fp, ".Dd %s\n", formattext(docdate, 1));
fprintf(fp, ".Dt \"%s\" %d\n", formattext(doctitle, 1), section);
fprintf(fp, ".Os \"%s\" ", formattext(os, 1));
fprintf(fp, "\"%s\"\n", formattext(osversion, 1));
writeData(fp, names);
writeUsage(fp, description);
writeData(fp, retvals);
writeData(fp, env);
writeData(fp, files);
writeData(fp, examples);
writeData(fp, diags);
writeData(fp, errs);
{
xmlNode **pos = othertopics;
while (pos && *pos) {
writeData(fp, *pos);
pos++;
}
}
free(othertopics);
writeData(fp, seeAlso);
writeData(fp, conformingTo);
writeData(fp, history);
writeData(fp, bugs);
if (strlen(output_filename)) {
fclose(fp);
}
}
xmlNode *nodematching(char *name, xmlNode *cur)
{
while (cur) {
if (!cur->name) break;
if (!strcmp((char *)cur->name, name)) break;
cur = cur->next;
}
return cur;
}
xmlNode **nodesmatching(char *name, xmlNode *cur)
{
xmlNode **buf = malloc(sizeof(xmlNode) * 10);
int size = 10, count=0;
while (cur) {
if (!cur->name) continue;
if (!strcmp((char *)cur->name, name)) {
if (count == (size - 1)) {
xmlNode **buf2;
buf2 = realloc(buf, sizeof(xmlNode) * size * 2);
if (!buf2) {
buf[count] = NULL;
return buf;
}
size *= 2;
}
buf[count++] = cur;
}
cur = cur->next;
}
buf[count] = NULL;
return buf;
}
char *textmatching(char *name, xmlNode *node, int missing_ok, char *debugstr)
{
xmlNode *cur = nodematching(name, node);
char *ret = NULL;
if (!cur) {
if (!missing_ok) {
fprintf(stderr, "Invalid or missing contents for %s.\n", debugstr ? debugstr : name);
}
} else if (cur && cur->children && cur->children->content) {
ret = (char *)cur->children->content;
} else if (!strcmp(name, "text")) {
ret = (char *)cur->content;
} else {
if (!missing_ok) {
fprintf(stderr, "Missing/invalid contents for %s.\n", debugstr ? debugstr : name);
} else {
return NULL;
}
}
return ret;
}
enum states
{
kGeneral = 0,
kNames = 1,
kRetval = 2,
kMan = 3,
};
void writeData_sub(FILE *fp, xmlNode *node, int state, int textcontainer, int next, int seendt);
void writeData(FILE *fp, xmlNode *node)
{
writeData_sub(fp, node, 0, 0, 0, 0);
}
void dodtguts(FILE *fp, xmlNode *parent, char *initial_add)
{
xmlNode *node = parent->children;
char *add = initial_add;
int skip_one = 0;
if (strlen(initial_add)) skip_one = 1;
while (node) {
if (!strcmp((char *)node->name, "tt")) {
fprintf(fp, " Dl "); dodtguts(fp, node, ""); add=" Li ";
} else if (!strcmp((char *)node->name, "code")) {
dodtguts(fp, node, "");
} else if (!strcmp((char *)node->name, "arg")) {
fprintf(fp, " Ar "); dodtguts(fp, node, ""); add=" Li ";
} else if (!strcmp((char *)node->name, "path")) {
fprintf(fp, " Pa "); dodtguts(fp, node, ""); add=" Li ";
} else if (!strcmp((char *)node->name, "var")) {
fprintf(fp, " Va "); dodtguts(fp, node, ""); add=" Li ";
} else if (!strcmp((char *)node->name, "function")) {
fprintf(fp, " Fn "); dodtguts(fp, node, ""); add=" Li ";
} else if (!strcmp((char *)node->name, "symbol")) {
fprintf(fp, " Sy "); dodtguts(fp, node, ""); add=" Li ";
} else if (!strcmp((char *)node->name, "command")) {
fprintf(fp, " Nm "); dodtguts(fp, node, ""); add=" Li ";
} else if (!strcmp((char *)node->name, "text")) {
char *ptr;
char *x = formattext(striplines((char *)node->content), 1);
int found = 0;
for (ptr = x; *ptr; ptr++) {
if (*ptr != ' ' && *ptr != '\t') {
found=1;
break;
}
}
if (found) {
if (skip_one) skip_one=0;
else {
fprintf(fp, "%s", add);
add = "";
}
}
fprintf(fp, "%s", x);
}
node = node->next;
}
}
xmlNode *lasttextnode(xmlNode *node)
{
xmlNode *nextnode = NULL;
if (!node) return NULL;
nextnode = lasttextnode(node->next);
if (nextnode) return nextnode;
if (!strcmp((char *)node->name, "text")) return node;
return NULL;
}
void writeData_sub(FILE *fp, xmlNode *node, int state, int textcontainer, int next, int seendt)
{
int oldtextcontainer = textcontainer;
int oldstate = state;
int drop_children = 0;
int localdebug = 0;
char *xreftail = NULL;
char *tail = NULL;
if (!node) return;
if (localdebug && node->content) {
printf("NODE CONTENT: %s\n", node->content);
}
if (node->next && !strcmp((char *)node->next->name, "text") && strcmp((char *)node->name, "url")) {
char *tmp = node->next->content ? (char *)node->next->content : "";
char *end;
char *nexttext;
xmlNode *searchnode;
if (tmp) {
searchnode = lasttextnode(node->children);
if (searchnode) {
char *sntext;
size_t alloc_len;
while (tmp[0] == '\n' || tmp[0] == '\r') tmp++;
end = tmp;
while (end[0] == '.' || end[0] == ',') end++;
nexttext = malloc(end - tmp + sizeof(char));
memcpy(nexttext, tmp, end-tmp);
nexttext[(end-tmp)/sizeof(char)] = '\0';
if (localdebug) printf("Appending \"%s\" to \"%s\"\n", nexttext, searchnode->content);
if (!strcmp((char *)node->name, "manpage")) {
safe_asprintf(&xreftail, "%s", nexttext);
if (!xreftail) { fprintf(stderr, "Out of memory.\n"); exit(1); }
} else {
alloc_len = ((strlen((char *)searchnode->content) + strlen(nexttext) + 2) * sizeof(char));
sntext = malloc(alloc_len);
strlcpy(sntext, (char *)searchnode->content, alloc_len);
strlcat(sntext, nexttext, alloc_len);
if (localdebug) printf("new text \"%s\"\n", sntext);
free(searchnode->content);
searchnode->content = (unsigned char *)sntext;
}
tmp = strdup(end);
free(node->next->content);
node->next->content = (unsigned char *)tmp;
}
}
}
if (!strcmp((char *)node->name, "docdate")) {
if (localdebug) printf("docdate\n");
writeData_sub(fp, node->next, state, 0, 1, seendt);
return;
} else if (!strcmp((char *)node->name, "doctitle")) {
if (localdebug) printf("doctitle\n");
writeData_sub(fp, node->next, state, 0, 1, seendt);
return;
} else if (!strcmp((char *)node->name, "section")) {
if (localdebug) printf("section\n");
if (state == kMan) {
char *childtext = textmatching("text", node->children, 1, "man section element");
char *pos = childtext;
char *tailcontents = NULL;
while (pos && *pos && *pos != ',' && *pos != '.') pos++;
if (pos && *pos) {
tailcontents = strdup(pos);
*pos = '\0';
}
if (localdebug) fprintf(stderr, "TAILCONTENTS: %s\n", tailcontents);
fprintf(fp, " %s", formattext(childtext, textcontainer));
if (tailcontents) fprintf(fp, " %s", tailcontents);
tail = " ";
if (tailcontents) {
*pos = *tailcontents;
free(tailcontents);
}
drop_children = 1;
} else {
writeData_sub(fp, node->next, state, 0, 1, seendt);
return;
}
} else if (!strcmp((char *)node->name, "desc")) {
if (localdebug) printf("desc\n");
if (state == kNames && node->children) {
fprintf(fp, ".Nd ");
}
} else if (!strcmp((char *)node->name, "names")) {
if (localdebug) printf("names\n");
state = kNames;
fprintf(fp, ".Sh NAME\n");
} else if (!strcmp((char *)node->name, "name")) {
if (localdebug) printf("name\n");
if (state == kNames) {
if (seen_name) {
fprintf(fp, ".Pp\n");
}
fprintf(fp, ".Nm ");
textcontainer = 1;
seen_name = 1;
} else {
char *tmp = textmatching("text", node->children, 0, "name tag");
fprintf(fp, ".Nm%s%s\n", (tmp ? " " : ""), formattext(tmp ? tmp : "", textcontainer));
if (tmp) { textcontainer = 0; }
}
} else if (!strcmp((char *)node->name, "usage")) {
if (localdebug) printf("usage\n");
textcontainer = 0;
drop_children = 1;
} else if (!strcmp((char *)node->name, "flag")) {
if (textcontainer) {
fprintf(fp, ".Fl "); dodtguts(fp, node, "\n"); drop_children = 1;
tail = "\n";
} else {
if (localdebug) printf("flag\n");
fprintf(fp, ".Pa "); dodtguts(fp, node, "\n"); drop_children = 1;
}
} else if (!strcmp((char *)node->name, "arg")) {
if (localdebug) printf("arg\n");
fprintf(fp, ".Pa "); dodtguts(fp, node, "\n"); drop_children = 1;
} else if (!strcmp((char *)node->name, "returnvalues")) {
if (localdebug) printf("returnvalues\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh RETURN VALUES\n");
} else if (!strcmp((char *)node->name, "environment")) {
if (localdebug) printf("environment\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh ENVIRONMENT\n");
} else if (!strcmp((char *)node->name, "files")) {
if (localdebug) printf("files\n");
textcontainer = 0;
fprintf(fp, ".Sh FILES\n");
fprintf(fp, ".Bl -tag -width indent\n");
tail = ".El\n";
} else if (!strcmp((char *)node->name, "file")) {
if (localdebug) printf("file\n");
textcontainer = 1;
fprintf(fp, ".It Pa ");
} else if (!strcmp((char *)node->name, "examples")) {
if (localdebug) printf("example\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh EXAMPLES\n");
} else if (!strcmp((char *)node->name, "diagnostics")) {
if (localdebug) printf("diagnostics\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh DIAGNOSTICS\n");
} else if (!strcmp((char *)node->name, "errors")) {
if (localdebug) printf("errors\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh ERRORS\n");
} else if (!strcmp((char *)node->name, "seealso")) {
if (localdebug) printf("seealso\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh SEE ALSO\n");
} else if (!strcmp((char *)node->name, "conformingto")) {
if (localdebug) printf("conformingto\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh CONFORMING TO\n");
} else if (!strcmp((char *)node->name, "description")) {
if (localdebug) printf("description\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh DESCRIPTION\n");
} else if (!strcmp((char *)node->name, "history")) {
if (localdebug) printf("history\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh HISTORY\n");
} else if (!strcmp((char *)node->name, "bugs")) {
if (localdebug) printf("bugs\n");
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh BUGS\n");
} else if (!strcmp((char *)node->name, "topic")) {
if (localdebug) printf("topic\n");
xmlNode *topicnode = nodematching("name", node->children);
char *topicname = "unknown";
if (topicnode) {
xmlUnlinkNode(topicnode);
topicname = textmatching("text", topicnode->children, 1, "topic name");
} else {
fprintf(stderr, "Missing name tag for topic. Usage is <topic><name>Topic Name</name>...\n");
}
state = kRetval;
textcontainer = 1;
fprintf(fp, ".Sh %s\n", formattext(topicname, textcontainer));
if (topicnode) xmlFreeNode(topicnode);
} else if (!strcmp((char *)node->name, "indent")) {
if (localdebug) printf("indent\n");
if (textcontainer) {
fprintf(fp, " ");
}
} else if (!strcmp((char *)node->name, "p")) {
if (localdebug) printf("p (paragraph)\n");
tail = ".Pp\n";
} else if (!strcmp((char *)node->name, "blockquote")) {
if (localdebug) printf("blockquote\n");
fprintf(fp, ".Bd -ragged -offset indent\n");
tail = ".Ed\n.Bd -ragged -offset indent\n.Ed\n";
} else if (!strcmp((char *)node->name, "dl")) {
if (localdebug) printf("dl\n");
int minwidth = 6;
xmlNode *ddnode = node->children;
if (localdebug) printf("DL\n");
textcontainer = 0;
for ( ; ddnode ; ddnode = ddnode->next) {
char *childtext;
if (strcmp((char *)ddnode->name, "dd")) continue;
childtext = textmatching("text", ddnode->children, 1, "dd element");
if (!childtext) {
if (ddnode->children) {
childtext = textmatching("text", ddnode->children->children, 1, "dd element");
}
}
if (childtext) {
minwidth = MIN(minwidth, strlen(childtext));
}
}
fprintf(fp, ".Bl -tag -width %s\n", xs(minwidth-1));
tail = ".El\n.Pp\n";
} else if (!strcmp((char *)node->name, "dt")) {
if (localdebug) printf("dt\n");
textcontainer = 0;
drop_children = 1;
seendt = 1;
fprintf(fp, ".It ");
dodtguts(fp, node, "");
tail = "\n";
} else if (!strcmp((char *)node->name, "dd")) {
if (localdebug) printf("dd (definition)\n");
if (localdebug) printf("DD\n");
if (!seendt) {
if (localdebug) printf(" NO DT\n");
fprintf(fp, ".It \"\"\n");
}
textcontainer = 1;
tail = "";
} else if (!strcmp((char *)node->name, "tt")) {
if (localdebug) printf("tt (typewriter)\n");
fprintf(fp, ".Dl ");
} else if (!strcmp((char *)node->name, "ul")) {
if (localdebug) printf("ul (list)\n");
fprintf(fp, ".Bl -bullet\n");
tail = ".El\n.Pp\n";
} else if (!strcmp((char *)node->name, "ol")) {
if (localdebug) printf("ol (list)\n");
fprintf(fp, ".Bl -enum\n");
tail = ".El\n.Pp\n";
} else if (!strcmp((char *)node->name, "li")) {
if (localdebug) printf("li (list)\n");
fprintf(fp, ".It\n");
} else if (!strcmp((char *)node->name, "literal")) {
if (localdebug) printf("literal\n");
fprintf(fp, ".Li ");
} else if (!strcmp((char *)node->name, "code")) {
if (localdebug) printf("code\n");
fprintf(fp, ".Li ");
} else if (!strcmp((char *)node->name, "path")) {
if (localdebug) printf("path\n");
fprintf(fp, ".Pa ");
} else if (!strcmp((char *)node->name, "u") || !strcmp((char *)node->name, "u")) {
if (localdebug) printf("u (underline)/em (emphasis)\n");
fprintf(fp, ".Em ");
} else if (!strcmp((char *)node->name, "var")) {
if (localdebug) printf("var\n");
fprintf(fp, ".Va ");
} else if (!strcmp((char *)node->name, "function")) {
if (localdebug) printf("function\n");
fprintf(fp, ".Fn ");
} else if (!strcmp((char *)node->name, "symbol")) {
if (localdebug) printf("symbol\n");
fprintf(fp, ".Sy ");
} else if (!strcmp((char *)node->name, "url")) {
char *childtext = formattext(node->children ? (node->children->content ? (char *)node->children->content : "") : "", textcontainer);
char *nexttext = node->next ? (char *)node->next->content : "";
fprintf(fp, "<%s>", childtext);
while (nexttext && *nexttext && (*nexttext == '.' || *nexttext == ',')) {
fprintf(fp, "%c", *nexttext);
nexttext++;
}
drop_children = 1;
} else if (!strcmp((char *)node->name, "b")) {
if (localdebug) printf("b (bold)\n");
fprintf(fp, ".Sy ");
} else if (!strcmp((char *)node->name, "subcommand")) {
if (localdebug) printf("subcommand\n");
fprintf(fp, ".Nm ");
} else if (!strcmp((char *)node->name, "command")) {
if (localdebug) printf("command\n");
fprintf(fp, ".Nm ");
} else if (!strcasecmp((char *)node->name, "br")) {
if (localdebug) printf("br (break)\n");
fprintf(fp, ".br\n");
} else if (!strcmp((char *)node->name, "manpage")) {
if (localdebug) printf("manpage\n");
fprintf(fp, ".Xr ");
state = kMan;
tail = "\n";
textcontainer = 2;
} else if (!strcmp((char *)node->name, "text")) {
if (localdebug) printf("text node\n");
if (textcontainer) {
if (localdebug) printf("WILL PRINT %s\n", node->content);
char *stripped_text = striplines((char *)node->content);
if (strlen(stripped_text)) {
while (stripped_text[0] == '.' || stripped_text[0] == ',') stripped_text++;
fprintf(fp, "%s%s", formattext(stripped_text, textcontainer), (state == kMan ? "" : "\n"));
}
} else {
if (localdebug) printf("NOT PRINTING %s\n", node->content);
}
} else {
fprintf(stderr, "unknown field %s\n", node->name);
}
if (!drop_children) {
writeData_sub(fp, node->children, state, textcontainer, 1, seendt);
}
textcontainer = oldtextcontainer;
state = oldstate;
if (xreftail) {
fprintf(fp, " %s", xreftail);
}
if (tail) {
fprintf(fp, "%s", tail);
}
if (next) {
writeData_sub(fp, node->next, state, textcontainer, 1, seendt);
}
}
void write_funcargs(FILE *fp, usage_t cur)
{
for (; cur; cur = cur->next) {
fprintf(fp, ".It Ar \"%s\"", formattext(cur->arg ? cur->arg : "", 1));
fprintf(fp, "\n");
if (cur->descnode && cur->descnode->children) {
writeData_sub(fp, cur->descnode->children, 0, 1, 1, 0);
} }
}
char *xs(int count)
{
static char *buffer = NULL;
if (buffer) free(buffer);
buffer = malloc((count+1) * sizeof(char));
if (buffer) {
int i;
for (i=0; i<count; i++) buffer[i] = 'X';
buffer[count] = '\0';
}
return buffer;
}
void writeUsage(FILE *fp, xmlNode *description)
{
int lwc, pos;
char *name_or_empty = NULL;
usage_t cur;
lwc = 6;
if (seen_usage) {
fprintf(fp, ".Sh SYNOPSIS\n");
for (pos = 0; pos < (multi_command_syntax ? multi_command_syntax : 1); pos++) {
if (multi_command_syntax) {
name_or_empty = commandnames[pos];
}
for (cur = usage_head[pos]; cur; cur = cur->next) {
int len;
len = 0;
if (cur->flag) len += strlen(cur->flag) + 2;
if (cur->longflag) len += strlen(cur->longflag) + 3;
if (cur->flag && cur->longflag) len += 4;
if (cur->arg) len += strlen(cur->arg) + 1;
if (len > lwc) lwc = len;
}
if (lwc < 4) lwc = 4;
writeUsageSub(fp, 1, usage_head[pos], name_or_empty, "");
}
}
writeData(fp, description);
writeOptionsSub(fp, description, lwc);
}
int writeUsageSub(FILE *fp, int showname, usage_t myusagehead, char *name_or_empty, char *optional_separator)
{
usage_t cur;
int first;
char dot = '.';
int nonl = 0;
int lastnonl = 0;
int prevwasliteral = 0;
first = 1;
for (cur = myusagehead; cur; cur = cur->next) {
int isliteral = 0;
char *optiontag=".";
if (!showname || lastnonl) {
dot = ' ';
optiontag = " ";
} else {
dot = '.';
}
if (cur->flag || cur->longflag) {
uint64_t flaglen = cur->flag ? strlen(cur->flag) : 0;
uint64_t longflaglen = cur->longflag ? strlen(cur->longflag) : 0;
if (first && showname) fprintf(fp, ".Nm%s%s\n", (name_or_empty ? " " : ""), formattext(name_or_empty ? name_or_empty : "", 1));
else if (!first) fprintf(fp, "%s", optional_separator);
if (flaglen) {
fprintf(fp, "%c%s%sFl %s", dot, (cur->optional?"Op ":""), longflaglen ? "{ " : "", formattext(cur->flag, 1));
dot = ' ';
}
if (cur->longflag && strlen(cur->longflag)) {
fprintf(fp, "%c%s%sFl -%s%s", dot, flaglen ? " | " : "", flaglen ? "" : (cur->optional?"Op ":""), formattext(cur->longflag, 1), flaglen ? " Li }" : "");
dot = ' ';
}
first = 0;
optiontag="";
}
if (cur->arg) {
if (first && showname) {
fprintf(fp, "%cNm%s%s\n", dot, (name_or_empty ? " " : ""), formattext(name_or_empty ? name_or_empty : "", 1));
dot = ' ';
} else if (!first) fprintf(fp, "%s", optional_separator);
fprintf(fp, "%c%sAr %s", dot, (cur->optional?"Op ":""), formattext(cur->arg, 1));
first = 0;
optiontag="";
}
if (cur->text) {
int notsymbol = 0;
if (cur->text[0] >= 'a' && cur->text[0] <= 'z') notsymbol = 1;
if (cur->text[0] >= 'A' && cur->text[0] <= 'Z') notsymbol = 1;
if (strlen(optional_separator)) notsymbol = 0;
if (!first && !showname) fprintf(fp, " Li ");
else if (prevwasliteral) fprintf(fp, " Li ");
else fprintf(fp, ".Li ");
if (!first) fprintf(fp, "%s", optional_separator);
fprintf(fp, "%s", formattext(cur->text, 1));
isliteral = 1;
if (notsymbol) {
optiontag="\n";
} else {
nonl = 1;
}
}
if (cur->optionlist) {
if (first) { fprintf(fp, ".Nm%s%s\n", (name_or_empty ? " " : ""), formattext(name_or_empty ? name_or_empty : "", 1)); first = 0; }
else {
if (!first) {
if (!(cur->flag || cur->longflag || cur->arg || cur->text)) {
fprintf(fp, "%s", optional_separator);
} else fprintf(fp, " ");
}
}
printUsageOptionList(cur, fp, optiontag, (cur->flag || cur->longflag || cur->arg) ? " " : " | ");
isliteral = 0;
}
if (cur->functype) {
usage_t arg;
fprintf(fp, ".Ft %s\n", formattext(cur->functype, 1));
fprintf(fp, ".Fn \"%s\" ", formattext(cur->funcname, 1));
for (arg = cur->funcargs; arg; arg = arg->next) {
fprintf(fp, "\"%s\" ", formattext(arg->arg, 1));
}
isliteral = 0;
} else if (cur->funcargs) {
usage_t arg;
for (arg = cur->funcargs; arg; arg = arg->next) {
fprintf(fp, " %sAr %s", (arg->optional?"Op ":""), formattext(arg->arg, 1));
}
isliteral = 0;
}
if (showname && !nonl) {
fprintf(fp, "\n");
isliteral = 0;
}
lastnonl = nonl;
prevwasliteral = isliteral;
nonl = 0;
}
if (lastnonl && showname) {
fprintf(fp, "\n");
}
return 0;
}
int writeOptionsSubWithObject(FILE *fp, usage_t obj, int pos, int topfirst, int lwc, int noheading);
void writeOptionsSub(FILE *fp, xmlNode *description, int lwc)
{
int topfirst;
int pos;
if (multi_command_syntax) {
int outerpos, innerpos;
for (outerpos = 0; outerpos < multi_command_syntax; outerpos++) {
usage_head[outerpos]->emitted = 0;
usage_head[outerpos]->nextwithsamename = 0;
for (innerpos = outerpos + 1; innerpos < (multi_command_syntax ? multi_command_syntax : 1); innerpos++) {
if (!strcmp(commandnames[outerpos], commandnames[innerpos])) {
usage_head[outerpos]->nextwithsamename = innerpos;
break;
}
}
}
}
topfirst = 1;
for (pos = 0; pos < (multi_command_syntax ? multi_command_syntax : 1); pos++) {
if (!usage_head[pos]) continue;
if (usage_head[pos]->emitted) continue;
topfirst = writeOptionsSubWithObject(fp, usage_head[pos], pos, topfirst, lwc, usage_head[pos]->nextwithsamename ? 2 : 0);
int temppos = usage_head[pos]->nextwithsamename;
while (temppos) {
topfirst = writeOptionsSubWithObject(fp, usage_head[temppos], temppos, topfirst, lwc, usage_head[temppos]->nextwithsamename ? 3 : 1);
usage_head[temppos]->emitted = 1;
temppos = usage_head[temppos]->nextwithsamename;
}
}
}
int writeOptionsSubWithObject(FILE *fp, usage_t obj, int pos, int topfirst, int lwc, int noheading)
{
int first;
char *name_or_empty = NULL;
usage_t cur;
if (multi_command_syntax) {
name_or_empty = commandnames[pos];
}
if (noheading == 1 || noheading == 3) first = 0;
else first = 1;
for (cur = usage_head[pos]; cur; cur = cur->next) {
if (cur->funcargs && !cur->flag && !cur->longflag) {
if (first) {
if (topfirst) { fprintf(fp, ".Sh PARAMETERS\n"); }
first = 0; topfirst = 0;
} else {
fprintf(fp, ".El\n.Pp\n");
}
fprintf(fp, "The parameters %s%s%sare as follows:\n", (name_or_empty ? "for\n.Sy " : ""), formattext(name_or_empty ? name_or_empty : "", 1), (name_or_empty ? " Li " : ""));
fprintf(fp, ".Bl -tag -width %s\n", xs(lwc));
write_funcargs(fp, cur->funcargs);
continue;
}
if (!cur->descnode) continue;
if (first) {
if (topfirst) { fprintf(fp, ".Sh OPTIONS\n"); }
fprintf(fp, "The available options %s%s%sare as follows:\n", (name_or_empty ? "for\n.Sy " : ""), formattext(name_or_empty ? name_or_empty : "", 1), (name_or_empty ? " Li " : ""));
fprintf(fp, ".Bl -tag -width %s\n", xs(lwc));
first = 0; topfirst = 0;
}
fprintf(fp, ".It");
if (cur->flag) { fprintf(fp, " Fl %s", formattext(cur->flag, 1)); }
if (cur->longflag) { fprintf(fp, "%s Fl -%s", cur->flag ? " Li or" : "", formattext(cur->longflag, 1)); }
if (cur->arg) {
fprintf(fp, " Ar \"%s\"", formattext(cur->arg, 1));
}
fprintf(fp, "\n");
if (cur->descnode && cur->descnode->children) {
writeData_sub(fp, cur->descnode->children, 0, 1, 1, 0);
} }
if (noheading < 2) {
if (!first) { fprintf(fp, ".El\n"); }
}
return topfirst;
}
void printUsageOptionList(usage_t cur, FILE *fp, char *starting, char *separator)
{
int optional = cur->optional;
fprintf(fp, "%s", formattext(starting, 1));
if (starting[strlen(starting)-1] == '\n') fprintf(fp, ".");
if (optional) fprintf(fp, "Op ");
writeUsageSub(fp, 0, cur->optionlist, "", separator);
}
char *propstring(char *name, struct _xmlAttr *prop)
{
for (; prop; prop=prop->next) {
if (!strcmp((char *)prop->name, name)) {
if (prop->children && prop->children->content) {
return (char *)prop->children->content;
}
}
}
return NULL;
}
int propval(char *name, struct _xmlAttr *prop)
{
char *ps = propstring(name, prop);
if (!ps) {
return 0;
}
return atoi(ps);
}
usage_t getflagargs(xmlNode *node)
{
usage_t head = NULL, tail = NULL;
usage_t newnode;
while (node) {
if (strcmp((char *)node->name, "arg")) { node = node->next; continue; }
if (!(newnode = malloc(sizeof(struct usage)))) return NULL;
newnode->flag = NULL;
newnode->longflag = NULL;
newnode->arg = textmatching("text", node->children, 0, "flag argument");
newnode->descnode = NULL;
newnode->optional = propval("optional", node->properties);
newnode->functype = NULL;
newnode->funcname = NULL;
newnode->funcargs = NULL;
newnode->next = NULL;
if (!head) {
head = newnode;
tail = newnode;
} else {
tail->next = newnode;
tail = newnode;
}
node = node->next;
}
return head;
}
void parseUsageSub(xmlNode *node, int pos, int drop_first_text);
void parseUsage(xmlNode *node, int pos) {
parseUsageSub(node, pos, 1);
}
void parseUsageSub(xmlNode *node, int pos, int drop_first_text)
{
usage_t flag_or_arg = NULL;
if (!node) return;
if (!strcmp((char *)node->name, "text") || !strcmp((char *)node->name, "type") ||
!strcmp((char *)node->name, "name")) {
char *ptr;
char *x = striplines((char *)node->content);
int found = 0;
for (ptr = x; *ptr; ptr++) {
if (*ptr != ' ' && *ptr != '\t') {
found=1;
break;
}
}
if (!found || drop_first_text) {
parseUsageSub(node->next, pos, 0);
return;
}
}
if (!strcmp((char *)node->name, "command")) {
int i = 0;
while (node) {
char *name;
if (strcmp((char *)node->name, "command")) {
node = node->next;
continue;
}
name = propstring("name", node->properties);
if (name) {
strlcpy(commandnames[i], name, MAXNAMLEN);
} else {
fprintf(stderr, "WARNING: command has no name\n");
}
parseUsageSub(node->children, i, 0);
multi_command_syntax = i+1;
node = node->next;
if ((++i >= MAXCOMMANDS) && node) {
fprintf(stderr, "MAXCOMMANDS reached.\n");
break;
}
}
return;
}
if (strcmp((char *)node->name, "desc")) {
flag_or_arg = (usage_t)malloc(sizeof(struct usage));
if (!flag_or_arg) return;
if (!usage_head[pos]) {
usage_head[pos] = flag_or_arg;
usage_tail[pos] = flag_or_arg;
} else {
usage_tail[pos]->next = flag_or_arg;
usage_tail[pos] = flag_or_arg;
}
}
if (!strcmp((char *)node->name, "optionlist")) {
char *tempstring;
flag_or_arg->flag = NULL;
flag_or_arg->longflag = NULL;
flag_or_arg->optionlist = NULL;
flag_or_arg->text = NULL;
flag_or_arg->arg = NULL;
flag_or_arg->descnode = NULL;
flag_or_arg->optional = 1;
tempstring = propstring("optional", node->properties);
if (tempstring) {
flag_or_arg->optional = atoi(tempstring);
}
flag_or_arg->functype = NULL;
flag_or_arg->funcname = NULL;
flag_or_arg->funcargs = NULL;
flag_or_arg->next = NULL;
parseUsageSub(node->children, pos, 0);
flag_or_arg->optionlist = flag_or_arg->next;
usage_tail[pos] = flag_or_arg;
flag_or_arg->next = NULL;
} else if (!strcmp((char *)node->name, "subcommand") || !strcmp((char *)node->name, "literal") || !strcmp((char *)node->name, "text")) {
char *dbstr = malloccat((char *)node->name, " tag");
int istext = 0;
if (!strcmp((char *)node->name, "text")) {
istext = 1;
}
flag_or_arg->flag = NULL;
flag_or_arg->longflag = NULL;
flag_or_arg->arg = NULL;
flag_or_arg->optionlist = NULL;
if (istext) {
flag_or_arg->text = strdup(striplines((char *)node->content));
} else {
flag_or_arg->text = strdup(striplines(textmatching("text", node->children, 0, dbstr)));
}
free(dbstr);
flag_or_arg->descnode = NULL;
flag_or_arg->optional = propval("optional", node->properties);
flag_or_arg->functype = NULL;
flag_or_arg->funcname = NULL;
flag_or_arg->funcargs = NULL;
flag_or_arg->next = NULL;
if (!istext) {
parseUsage(node->children, pos);
flag_or_arg->optionlist = flag_or_arg->next;
usage_tail[pos] = flag_or_arg;
}
flag_or_arg->next = NULL;
} else if (!strcmp((char *)node->name, "arg")) {
xmlNode *tempnode = nodematching("arg", node->children);
flag_or_arg->flag = NULL;
flag_or_arg->longflag = NULL;
flag_or_arg->optionlist = NULL;
flag_or_arg->text = NULL;
flag_or_arg->arg = strdup(striplines(textmatching("text", node->children, 0, "arg tag")));
flag_or_arg->descnode = nodematching("desc", node->children);
flag_or_arg->optional = propval("optional", node->properties);
flag_or_arg->functype = NULL;
flag_or_arg->funcname = NULL;
flag_or_arg->funcargs = NULL;
flag_or_arg->next = NULL;
if (tempnode) {
parseUsage(tempnode, pos);
flag_or_arg->optionlist = flag_or_arg->next;
usage_tail[pos] = flag_or_arg;
flag_or_arg->next = NULL;
}
} else if (!strcmp((char *)node->name, "desc")) {
} else if (!strcmp((char *)node->name, "flag")) {
flag_or_arg->flag = textmatching("text", node->children, 1, "flag tag");
flag_or_arg->longflag = textmatching("long", node->children, 1, "long flag tag");
if (!flag_or_arg->flag && !flag_or_arg->longflag) {
fprintf(stderr, "Invalid or missing contents for flag tag.\n");
}
flag_or_arg->optionlist = NULL;
flag_or_arg->text = NULL;
flag_or_arg->arg = NULL;
flag_or_arg->descnode = nodematching("desc", node->children);
flag_or_arg->optional = propval("optional", node->properties);
flag_or_arg->functype = NULL;
flag_or_arg->funcname = NULL;
flag_or_arg->funcargs = getflagargs(node->children);
flag_or_arg->next = NULL;
} else if (!strcmp((char *)node->name, "func")) {
flag_or_arg->flag = NULL;
flag_or_arg->longflag = NULL;
flag_or_arg->optionlist = NULL;
flag_or_arg->text = NULL;
flag_or_arg->arg = NULL;
flag_or_arg->descnode = NULL;
flag_or_arg->optional = 0;
flag_or_arg->functype = strdup(striplines(textmatching("type", node->children, 0, "func type")));
flag_or_arg->funcname = strdup(striplines(textmatching("name", node->children, 0, "func name")));
flag_or_arg->next = NULL;
parseUsage(node->children, pos);
if (++funccount > 1) {
strncpy(commandnames[multi_command_syntax], flag_or_arg->funcname, MAXNAMLEN-1);
commandnames[multi_command_syntax][MAXNAMLEN-1] = '\0';
multi_command_syntax++;
}
flag_or_arg->funcargs = flag_or_arg->next;
usage_tail[pos] = flag_or_arg;
flag_or_arg->next = NULL;
} else {
fprintf(stderr, "UNKNOWN NODE NAME: %s\n", node->name);
flag_or_arg->flag = NULL;
flag_or_arg->longflag = NULL;
flag_or_arg->optionlist = NULL;
flag_or_arg->text = NULL;
flag_or_arg->arg = NULL;
flag_or_arg->descnode = NULL;
flag_or_arg->optional = 1;
flag_or_arg->functype = NULL;
flag_or_arg->funcname = NULL;
flag_or_arg->funcargs = NULL;
flag_or_arg->next = NULL;
flag_or_arg->optionlist = NULL;
flag_or_arg->next = NULL;
}
parseUsageSub(node->next, pos, 0);
}
enum stripstate
{
kSOL = 1,
kText = 2
};
char *striplines(char *line)
{
static char *ptr = NULL;
char *pos;
char *linepos;
int state = 0;
if (!line) return "";
linepos = line;
if (ptr) free(ptr);
ptr = malloc((strlen(line) + 1) * sizeof(char));
state = kSOL;
for (pos = ptr; (*linepos); linepos++,pos++) {
switch(state) {
case kSOL:
if (*linepos == ' ' || *linepos == '\n' || *linepos == '\r' ||
*linepos == '\t') { pos--; continue; }
case kText:
if (*linepos == '\n' || *linepos == '\r') {
state = kSOL;
*pos = ' ';
} else {
state = kText;
*pos = *linepos;
}
}
}
*pos = '\0';
return ptr;
}
int checkcurword(char *word, int textcontainer)
{
if (textcontainer == 2) return 0;
if (*word == '.') return 1;
if (strlen(word) == 1 && (!(word[0] >= '0' && word[0] <= '9'))) return 1;
if (strlen(word) == 2 && (!(word[0] >= '0' && word[0] <= '9')) && (!(word[1] >= '1' && word[1] <= '9'))) return 1;
return 0;
}
char *formattext(char *text, int textcontainer)
{
static char *result = NULL;
char *pos;
char *curword;
char *temp;
int iskey;
char *space = "";
if (result) free(result);
result = calloc(1,1);
curword = calloc(1,1);
for (pos = text; *pos; pos++) {
iskey = checkcurword(curword, textcontainer);
switch (*pos) {
case ' ':
temp = result;
result = NULL;
safe_asprintf(&result, "%s%s%s%s", temp, space, iskey ? "\\&" : "", curword);
if (!result) { fprintf(stderr, "Out of memory.\n"); exit(1); }
free(temp);
free(curword);
curword = calloc(1,1);
space = " ";
break;
case '\\':
temp = result;
result = NULL;
safe_asprintf(&result, "%s%s%s%s\\e", temp, space, iskey ? "\\&" : "", curword);
if (!result) { fprintf(stderr, "Out of memory.\n"); exit(1); }
free(temp);
free(curword);
curword = calloc(1,1);
space = " ";
break;
default:
temp = curword;
curword = NULL;
safe_asprintf(&curword, "%s%c", temp, *pos);
if (!curword) { fprintf(stderr, "Out of memory.\n"); exit(1); }
free(temp);
}
}
iskey = checkcurword(curword, textcontainer);
temp = result;
result = NULL;
safe_asprintf(&result, "%s%s%s%s", temp, space, iskey ? "\\&" : "", curword);
if (!result) { fprintf(stderr, "Out of memory.\n"); exit(1); }
free(temp);
free(curword);
return result;
}
int safe_asprintf(char **ret, const char *format, ...)
{
va_list ap;
int retval;
va_start(ap, format);
retval = vasprintf(ret, format, ap);
if (ret && (retval < 0)) {
*ret = NULL;
}
return retval;
}