1
// docs.cpp: ingame command documentation system
6
void renderdocsection(void *menu, bool init);
8
extern hashtable<const char *, ident> *idents;
12
char *token, *desc, *values;
15
docargument() : token(NULL), desc(NULL), values(NULL) {};
26
char *name, *ident, *url, *article;
28
docref() : name(NULL), ident(NULL), url(NULL), article(NULL) {}
40
char *code, *explanation;
42
docexample() : code(NULL), explanation(NULL) {}
52
char *alias, *name, *desc;
54
dockey() : alias(NULL), name(NULL), desc(NULL) {}
66
vector<docargument> arguments;
68
vector<docref> references;
69
vector<docexample> examples;
72
docident() : name(NULL), desc(NULL) {}
83
vector<docident *> idents;
86
docsection() : name(NULL) {};
93
vector<docsection> sections;
94
hashtable<const char *, docident> docidents; // manage globally instead of a section tree to ensure uniqueness
95
docsection *lastsection = NULL;
96
docident *lastident = NULL;
98
void adddocsection(char *name)
101
docsection &s = sections.add();
102
s.name = newstring(name);
103
s.menu = addmenu(s.name, NULL, true, renderdocsection);
107
void adddocident(char *name, char *desc)
109
if(!name || !desc || !lastsection) return;
110
name = newstring(name);
111
docident &c = docidents[name];
112
lastsection->idents.add(&c);
114
c.desc = newstring(desc);
118
void adddocargument(char *token, char *desc, char *values, char *vararg)
120
if(!lastident || !token || !desc) return;
121
docargument &a = lastident->arguments.add();
122
a.token = newstring(token);
123
a.desc = newstring(desc);
124
a.values = values && strlen(values) ? newstring(values) : NULL;
125
a.vararg = vararg && atoi(vararg) == 1 ? true : false;
128
void adddocremark(char *remark)
130
if(!lastident || !remark) return;
131
lastident->remarks.add(newstring(remark));
134
void adddocref(char *name, char *ident, char *url, char *article)
136
if(!lastident || !name) return;
137
docref &r = lastident->references.add();
138
r.name = newstring(name);
139
r.ident = ident && strlen(ident) ? newstring(ident) : NULL;
140
r.url = url && strlen(url) ? newstring(url) : NULL;
141
r.article = article && strlen(article) ? newstring(article) : NULL;
144
void adddocexample(char *code, char *explanation)
146
if(!lastident || !code) return;
147
docexample &e = lastident->examples.add();
148
e.code = newstring(code);
149
e.explanation = explanation && strlen(explanation) ? newstring(explanation) : NULL;
152
void adddockey(char *alias, char *name, char *desc)
154
if(!lastident || !alias) return;
155
dockey &k = lastident->keys.add();
156
k.alias = newstring(alias);
157
k.name = name && strlen(name) ? newstring(name) : NULL;
158
k.desc = desc && strlen(desc) ? newstring(desc) : NULL;
161
COMMANDN(docsection, adddocsection, ARG_1STR);
162
COMMANDN(docident, adddocident, ARG_2STR);
163
COMMANDN(docargument, adddocargument, ARG_4STR);
164
COMMANDN(docremark, adddocremark, ARG_1STR);
165
COMMANDN(docref, adddocref, ARG_3STR);
166
COMMANDN(docexample, adddocexample, ARG_2STR);
167
COMMANDN(dockey, adddockey, ARG_3STR);
169
int stringsort(const char **a, const char **b) { return strcmp(*a, *b); }
171
char *cvecstr(vector<char *> &cvec, const char *substr, int *rline = NULL)
174
loopv(cvec) if(cvec[i]) if((r = strstr(cvec[i], substr)) != NULL)
176
if(rline) *rline = i;
182
void docundone(int allidents)
184
vector<const char *> inames;
185
identnames(inames, !(allidents > 0));
186
inames.sort(stringsort);
189
docident *id = docidents.access(inames[i]);
190
if(id) // search for substrings that indicate undoneness
195
loopvj(id->remarks) srch.add(id->remarks[j]);
196
loopvj(id->arguments)
198
srch.add(id->arguments[j].token);
199
srch.add(id->arguments[j].desc);
200
srch.add(id->arguments[j].values);
202
loopvj(id->references)
204
srch.add(id->references[j].ident);
205
srch.add(id->references[j].name);
206
srch.add(id->references[j].url);
208
if(!cvecstr(srch, "TODO") && !cvecstr(srch, "UNDONE")) continue;
210
conoutf("%s", inames[i]);
216
vector<const char *> inames;
217
identnames(inames, true);
218
inames.sort(stringsort);
219
enumerate(docidents, docident, d,
221
if(!strchr(d.name, ' ') && !identexists(d.name))
222
conoutf("%s", d.name);
226
void docfind(char *search)
228
enumerate(docidents, docident, i,
233
loopvk(i.remarks) srch.add(i.remarks[k]);
237
if((r = cvecstr(srch, search, &rline)))
239
const int matchchars = 200;
241
s_strncpy(match, r-srch[rline] > matchchars/2 ? r-matchchars/2 : srch[rline], matchchars/2);
242
conoutf("%-20s%s", i.name, match);
247
char *xmlstringenc(char *d, const char *s, size_t len)
249
if(!d || !s) return NULL;
250
struct spchar { char c; char repl[8]; } const spchars[] = { {'&', "&"}, {'<', "<"}, {'>', "gt;"}, {'"', """}, {'\'', "'"}};
255
while(*sc && (size_t)(dc - d) < len - 1)
257
bool specialc = false;
258
loopi(sizeof(spchars)/sizeof(spchar)) if(spchars[i].c == *sc)
261
size_t rlen = strlen(spchars[i].repl);
262
if(dc - d + rlen <= len - 1)
264
memcpy(dc, spchars[i].repl, rlen);
269
if(!specialc) memcpy(dc++, sc, 1);
276
void docwritebaseref(char *ref, char *schemalocation, char *transformation)
278
FILE *f = openfile(path("docs/autogenerated_base_reference.xml", true), "w");
280
char desc[] = "<description>TODO: Description</description>";
282
fprintf(f, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
283
fprintf(f, "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>\n", transformation && strlen(transformation) ? transformation : "transformations/cuberef2xhtml.xslt");
284
fprintf(f, "<cuberef name=\"%s\" version=\"v0.1\" xsi:schemaLocation=\"%s\" xmlns=\"http://cubers.net/Schemas/CubeRef\">\n", ref && strlen(ref) ? ref : "Unnamed Reference", schemalocation && strlen(schemalocation) ? schemalocation : "http://cubers.net/Schemas/CubeRef schemas/cuberef.xsd");
285
fprintf(f, "\t%s\n", desc);
286
fprintf(f, "\t<sections>\n");
287
fprintf(f, "\t\t<section name=\"Main\">\n");
288
fprintf(f, "\t\t\t%s\n", desc);
289
fprintf(f, "\t\t\t<identifiers>\n");
291
static const int bases[] = { ARG_1INT, ARG_1STR, ARG_1EXP, ARG_1EXPF, ARG_1EST };
293
enumerate(*idents, ident, id,
295
if(id.type != ID_COMMAND) continue;
296
fprintf(f, "\t\t\t\t<command name=\"%s\">\n", xmlstringenc(name, id.name, _MAXDEFSTR));
297
fprintf(f, "\t\t\t\t\t%s\n", desc);
298
if(id.narg != ARG_NONE && id.narg != ARG_DOWN && id.narg != ARG_IVAL && id.narg != ARG_FVAL && id.narg != ARG_SVAL)
300
fprintf(f, "\t\t\t\t\t<arguments>\n");
301
if(id.narg == ARG_CONC || id.narg == ARG_CONCW || id.narg == ARG_VARI) fprintf(f, "\t\t\t\t\t\t<variableArgument token=\"...\" description=\"TODO\"/>\n");
305
loopj(sizeof(bases)/sizeof(bases[0]))
307
if(id.narg < bases[j]) { if(j) base = bases[j-1]; break; }
309
if(base >= 0) loopj(id.narg-base+1) fprintf(f, "\t\t\t\t\t\t<argument token=\"%c\" description=\"TODO\"/>\n", (char)(*"A")+j);
311
fprintf(f, "\t\t\t\t\t</arguments>\n");
313
fprintf(f, "\t\t\t\t</command>\n");
315
enumerate(*idents, ident, id,
317
if(id.type != ID_VAR && id.type != ID_FVAR) continue;
318
fprintf(f, "\t\t\t\t<variable name=\"%s\">\n", xmlstringenc(name, id.name, _MAXDEFSTR));
319
fprintf(f, "\t\t\t\t\t<description>TODO</description>\n");
323
fprintf(f, "\t\t\t\t\t<value %s description=\"TODO\" minValue=\"%i\" maxValue=\"%i\" defaultValue=\"%i\" %s/>\n", id.minval>id.maxval ? "" : "token=\"N\"", id.minval, id.maxval, *id.storage.i, id.minval>id.maxval ? "readOnly=\"true\"" : "");
326
fprintf(f, "\t\t\t\t\t<value %s description=\"TODO\" minValue=\"%s\" maxValue=\"%s\" defaultValue=\"%s\" %s/>\n", id.minvalf>id.maxvalf ? "" : "token=\"N\"",
327
floatstr(id.minvalf), floatstr(id.maxvalf), floatstr(*id.storage.f),
328
id.minvalf>id.maxvalf ? "readOnly=\"true\"" : "");
331
fprintf(f, "\t\t\t\t</variable>\n");
334
fprintf(f, "\t\t\t</identifiers>\n\t\t</section>\n\t</sections>\n</cuberef>\n");
338
COMMAND(docundone, ARG_1INT);
339
COMMAND(docinvalid, ARG_NONE);
340
COMMAND(docfind, ARG_1STR);
341
COMMAND(docwritebaseref, ARG_3STR);
342
VAR(docvisible, 0, 1, 1);
343
VAR(docskip, 0, 0, 1000);
345
void toggledoc() { docvisible = !docvisible; }
346
void scrolldoc(int i) { docskip += i; if(docskip < 0) docskip = 0; }
348
int numargs(char *args)
350
if(!args || !strlen(args)) return -1;
353
char *argstart = NULL;
355
for(char *t = args; *t; t++)
357
if(!argstart && *t != ' ') { argstart = t; argidx++; }
358
else if(argstart && *t == ' ') if(t-1 >= args)
362
case '[': if(*(t-1) != ']') continue; break;
363
case '"': if(*(t-1) != '"') continue; break;
372
void renderdoc(int x, int y, int doch)
374
if(!docvisible) return;
376
char *exp = getcurcommand();
377
if(!exp || *exp != '/' || strlen(exp) < 2) return;
380
size_t clen = strlen(c);
381
for(size_t i = 0; i < clen; i++) // search first matching cmd doc by stripping arguments of exp from right to left
383
char *end = c+clen-i;
384
if(!*end || *end == ' ')
387
s_strncpy(cmd, c, clen-i+1);
388
docident *ident = docidents.access(cmd);
391
vector<const char *> doclines;
393
char *label = newstringbuf(); // label
395
s_sprintf(label)("~%s", ident->name);
396
loopvj(ident->arguments)
398
s_strcat(label, " ");
399
s_strcat(label, ident->arguments[j].token);
403
doclines.add(ident->desc);
406
if(ident->arguments.length() > 0) // args
408
extern textinputbuffer cmdline;
409
char *args = strchr(c, ' ');
417
if(cmdline.pos >= args-c)
420
s_strncpy(a, args, cmdline.pos-(args-c)+1);
425
else arg = numargs(args);
427
if(arg >= 0) // multipart idents need a fixed argument offset
430
while((c = strchr(c, ' ')) && c++) arg--;
433
// fixes offset for var args
434
if(arg >= ident->arguments.length() && ident->arguments.last().vararg) arg = ident->arguments.length() - 1;
437
loopvj(ident->arguments)
439
docargument *a = &ident->arguments[j];
441
s_sprintf(doclines.add(newstringbuf()))("~\f%d%-8s%s %s%s%s", j == arg ? 4 : 5, a->token, a->desc,
442
a->values ? "(" : "", a->values ? a->values : "", a->values ? ")" : "");
448
if(ident->remarks.length()) // remarks
450
loopvj(ident->remarks) doclines.add(ident->remarks[j]);
454
if(ident->examples.length()) // examples
456
doclines.add(ident->examples.length() == 1 ? "Example:" : "Examples:");
457
loopvj(ident->examples)
459
doclines.add(ident->examples[j].code);
460
doclines.add(ident->examples[j].explanation);
465
if(ident->keys.length()) // default keys
467
doclines.add(ident->keys.length() == 1 ? "Default key:" : "Default keys:");
470
dockey &k = ident->keys[j];
471
s_sprintfd(line)("~%-10s %s", k.name ? k.name : k.alias, k.desc ? k.desc : "");
472
doclines.add(newstring(line));
477
if(ident->references.length()) // references
479
struct category { string label; string refs; }
480
categories[] = {{"related identifiers", ""} , {"web resources", ""}, {"wiki articles", ""}, {"other", ""}};
481
loopvj(ident->references)
483
docref &r = ident->references[j];
484
char *ref = r.ident ? categories[0].refs : (r.url ? categories[1].refs : (r.article ? categories[2].refs : categories[3].refs));
485
s_strcat(ref, r.name);
486
if(j < ident->references.length()-1) s_strcat(ref, ", ");
488
loopj(sizeof(categories)/sizeof(category))
490
if(!strlen(categories[j].refs)) continue;
491
s_sprintf(doclines.add(newstringbuf()))("~%s: %s", categories[j].label, categories[j].refs);
495
while(doclines.length() && !doclines.last()) doclines.pop();
497
doch -= 3*FONTH + FONTH/2;
499
int offset = min(docskip, doclines.length()-1), maxl = offset, cury = 0;
500
for(int j = offset; j < doclines.length(); j++)
502
const char *str = doclines[j];
503
int width = 0, height = FONTH;
504
if(str) text_bounds(*str=='~' ? str+1 : str, width, height, *str=='~' ? -1 : VIRTW*4/3);
505
if(cury + height > doch) break;
509
if(offset > 0 && maxl >= doclines.length())
511
for(int j = offset-1; j >= 0; j--)
513
const char *str = doclines[j];
514
int width = 0, height = FONTH;
515
if(str) text_bounds(*str=='~' ? str+1 : str, width, height, *str=='~' ? -1 : VIRTW*4/3);
516
if(cury + height > doch) break;
524
for(int j = offset; j < maxl; j++)
526
const char *str = doclines[j];
529
const char *text = *str=='~' ? str+1 : str;
530
draw_text(text, x, cury, 0xFF, 0xFF, 0xFF, 0xFF, -1, str==text ? VIRTW*4/3 : -1);
532
text_bounds(text, width, height, str==text ? VIRTW*4/3 : -1);
534
if(str!=text) delete[] str;
539
if(maxl < doclines.length()) draw_text("\f4more (F3)", x, y+doch); // footer
540
if(offset > 0) draw_text("\f4less (F2)", x, y+doch+FONTH);
541
draw_text("\f4disable doc reference (F1)", x, y+doch+2*FONTH);
548
void *docmenu = NULL;
550
struct msection { char *name; string cmd; };
552
int msectionsort(const msection *a, const msection *b)
554
return strcmp(a->name, b->name);
557
void renderdocsection(void *menu, bool init)
559
static vector<msection> msections;
560
msections.setsize(0);
564
if(sections[i].menu != menu) continue;
565
loopvj(sections[i].idents)
567
docident &id = *sections[i].idents[j];
568
msection &s = msections.add();
570
s_sprintf(s.cmd)("saycommand [/%s ]", id.name);
572
msections.sort(msectionsort);
574
loopv(msections) { menumanual(menu, msections[i].name, msections[i].cmd); }
579
struct maction { string cmd; };
581
void renderdocmenu(void *menu, bool init)
583
static vector<maction> actions;
588
maction &a = actions.add();
589
s_sprintf(a.cmd)("showmenu [%s]", sections[i].name);
590
menumanual(menu, sections[i].name, a.cmd);