1d306f4a86d9d825b94d93575e308e6d52e690aa
[alexxy/gromacs.git] / src / gromacs / commandline / wman.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2012,2013, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37 #include "gromacs/commandline/wman.h"
38
39 #include <cstdio>
40 #include <cstring>
41
42 #include <string>
43
44 #include "gromacs/commandline/cmdlinehelpcontext.h"
45 #include "gromacs/fileio/filenm.h"
46 #include "gromacs/utility/exceptions.h"
47 #include "gromacs/utility/file.h"
48 #include "gromacs/utility/gmxassert.h"
49 #include "gromacs/utility/stringutil.h"
50
51 #include "gmx_fatal.h"
52 #include "string2.h"
53 #include "smalloc.h"
54
55 /* The source code in this file should be thread-safe.
56  * Please keep it that way. */
57
58 static std::string check(const char *s, const gmx::HelpWriterContext &context)
59 {
60     return context.substituteMarkupAndWrapToString(gmx::TextLineWrapperSettings(), s);
61 }
62
63 static std::string check(const char *s, const gmx::CommandLineHelpContext &context)
64 {
65     return check(s, context.writerContext());
66 }
67
68 #define FLAG_SET(flag, mask) ((flag &mask) == mask)
69 /* Return a string describing the file type in flag.
70  * flag should the flag field of a filenm struct.
71  * You have to provide a buffer and buffer length in which
72  * the result will be written. The returned pointer is just
73  * a pointer to this buffer.
74  */
75 static char *fileopt(unsigned long flag, char buf[])
76 {
77     char tmp[256];
78
79     if (FLAG_SET(flag, ffRW))
80     {
81         sprintf(tmp, "In/Out");
82     }
83     else if (FLAG_SET(flag, ffREAD))
84     {
85         sprintf(tmp, "Input");
86     }
87     else if (FLAG_SET(flag, ffWRITE))
88     {
89         sprintf(tmp, "Output");
90     }
91     else
92     {
93         sprintf(tmp, "Dunno");
94     }
95
96     if (FLAG_SET(flag, ffOPT))
97     {
98         strcat(tmp, ", Opt");
99         if (FLAG_SET(flag, ffSET))
100         {
101             strcat(tmp, "!");
102         }
103         else
104         {
105             strcat(tmp, ".");
106         }
107     }
108     if (FLAG_SET(flag, ffLIB))
109     {
110         strcat(tmp, ", Lib.");
111     }
112     if (FLAG_SET(flag, ffMULT))
113     {
114         strcat(tmp, ", Mult.");
115     }
116
117     sprintf(buf, "%s", tmp);
118
119     return buf;
120 }
121
122 #define OPTLEN 4
123 #define NAMELEN 14
124 static void pr_fns(FILE *fp, int nf, const t_filenm tfn[])
125 {
126     int    i, f;
127     size_t j;
128     char   buf[256], *wbuf, opt_buf[32];
129
130     fprintf(fp, "%6s %12s  %-12s %s\n", "Option", "Filename", "Type",
131             "Description");
132     fprintf(fp,
133             "------------------------------------------------------------\n");
134     for (i = 0; (i < nf); i++)
135     {
136         for (f = 0; (f < tfn[i].nfiles); f++)
137         {
138             sprintf(buf, "%4s %14s  %-12s ", (f == 0) ? tfn[i].opt : "",
139                     tfn[i].fns[f], (f == 0) ? fileopt(tfn[i].flag, opt_buf)
140                     : "");
141             if (f < tfn[i].nfiles - 1)
142             {
143                 fprintf(fp, "%s\n", buf);
144             }
145         }
146         if (tfn[i].nfiles > 0)
147         {
148             strcat(buf, ftp2desc(tfn[i].ftp));
149             if ((strlen(tfn[i].opt) > OPTLEN)
150                 && (strlen(tfn[i].opt) <= ((OPTLEN + NAMELEN)
151                                            - strlen(tfn[i].fns[tfn[i].nfiles - 1]))))
152             {
153                 for (j = strlen(tfn[i].opt); j < strlen(buf)
154                      - (strlen(tfn[i].opt) - OPTLEN) + 1; j++)
155                 {
156                     buf[j] = buf[j + strlen(tfn[i].opt) - OPTLEN];
157                 }
158             }
159             wbuf = wrap_lines(buf, 78, 35, FALSE);
160             fprintf(fp, "%s\n", wbuf);
161             sfree(wbuf);
162         }
163     }
164     fprintf(fp, "\n");
165     fflush(fp);
166 }
167 #undef OPTLEN
168 #undef NAMELEN
169
170 /* name to print in help info for command line arguments
171  * (defined in enum in readinp.h) */
172 static const char *get_arg_desc(int type)
173 {
174     const char *const argtp[etNR] = {
175         "int", "step", "real", "time", "string", "bool", "vector", "enum"
176     };
177     return argtp[type];
178 }
179
180 /* Return the value of pa in the provided buffer buf, of size sz.
181  * The return value is also a pointer to buf.
182  */
183 static char *pa_val(t_pargs *pa, char buf[], int sz)
184 {
185     real r;
186     char buf_str[1256]; buf_str[0] = '\0';
187
188     buf[0] = '\0';
189
190     GMX_RELEASE_ASSERT(sz >= 255, "Buffer must be at least 255 chars");
191
192     switch (pa->type)
193     {
194         case etINT:
195             sprintf(buf, "%-d", *(pa->u.i));
196             break;
197         case etINT64:
198             sprintf(buf, "%" GMX_PRId64, *(pa->u.is));
199             break;
200         case etTIME:
201         case etREAL:
202             r = *(pa->u.r);
203             sprintf(buf_str, "%-6g", r);
204             strcpy(buf, buf_str);
205             break;
206         case etBOOL:
207             sprintf(buf, "%-6s", *(pa->u.b) ? "yes" : "no");
208             break;
209         case etSTR:
210             if (*(pa->u.c))
211             {
212                 if (strlen(*(pa->u.c)) >= (size_t)sz)
213                 {
214                     gmx_fatal(FARGS, "Argument too long: \"%d\"\n", *(pa->u.c));
215                 }
216                 else
217                 {
218                     strcpy(buf, *(pa->u.c));
219                 }
220             }
221             break;
222         case etENUM:
223             strcpy(buf, *(pa->u.c));
224             break;
225         case etRVEC:
226             sprintf(buf, "%g %g %g", (*pa->u.rv)[0],
227                     (*pa->u.rv)[1],
228                     (*pa->u.rv)[2]);
229             break;
230     }
231     return buf;
232 }
233
234 #define OPTLEN 12
235 #define TYPELEN 6
236 #define LONGSTR 1024
237 static char *pargs_print_line(t_pargs *pa, const gmx::HelpWriterContext &context)
238 {
239     char buf[LONGSTR], *buf2, *tmp;
240
241     snew(buf2, LONGSTR+strlen(pa->desc));
242     snew(tmp, LONGSTR+strlen(pa->desc));
243
244     if (pa->type == etBOOL)
245     {
246         sprintf(buf, "-[no]%s", pa->option+1);
247     }
248     else
249     {
250         strcpy(buf, pa->option);
251     }
252     std::string desc = check(pa->desc, context);
253     if ((int)strlen(buf) > ((OPTLEN+TYPELEN)-std::max((int)strlen(get_arg_desc(pa->type)), 4)))
254     {
255         sprintf(buf2, "%s %-6s %-6s  %-s\n",
256                 buf, get_arg_desc(pa->type), pa_val(pa, tmp, LONGSTR-1),
257                 desc.c_str());
258     }
259     else if (strlen(buf) > OPTLEN)
260     {
261         /* so type can be 3 or 4 char's, this fits in the %4s */
262         sprintf(buf2, "%-14s %-4s %-6s  %-s\n",
263                 buf, get_arg_desc(pa->type), pa_val(pa, tmp, LONGSTR-1),
264                 desc.c_str());
265     }
266     else
267     {
268         sprintf(buf2, "%-12s %-6s %-6s  %-s\n",
269                 buf, get_arg_desc(pa->type), pa_val(pa, tmp, LONGSTR-1),
270                 desc.c_str());
271     }
272     sfree(tmp);
273
274     tmp = wrap_lines(buf2, 78, 28, FALSE);
275
276     sfree(buf2);
277
278     return tmp;
279 }
280 #undef OPTLEN
281 #undef TYPELEN
282 #undef LONGSTR
283
284 static void print_pargs(FILE *fp, int npargs, t_pargs pa[],
285                         const gmx::HelpWriterContext &context)
286 {
287     if (npargs > 0)
288     {
289         fprintf(fp, "%-12s %-6s %-6s  %-s\n",
290                 "Option", "Type", "Value", "Description");
291         fprintf(fp, "------------------------------------------------------\n");
292         for (int i = 0; i < npargs; i++)
293         {
294             char *wdesc = pargs_print_line(&pa[i], context);
295             fprintf(fp, "%s", wdesc);
296             sfree(wdesc);
297         }
298         fprintf(fp, "\n");
299     }
300 }
301
302 static void write_nroffman(FILE *out,
303                            int nldesc, const char **desc,
304                            int nfile, t_filenm *fnm,
305                            int npargs, t_pargs *pa,
306                            int nbug, const char **bugs,
307                            const gmx::CommandLineHelpContext &context)
308 {
309     int  i;
310     char tmp[256];
311
312     fprintf(out, ".SH SYNOPSIS\n");
313     fprintf(out, "\\f3%s\\fP\n", context.moduleDisplayName());
314
315     /* command line arguments */
316     if (nfile > 0)
317     {
318         for (i = 0; (i < nfile); i++)
319         {
320             fprintf(out, ".BI \"%s\" \" %s \"\n",
321                     check(fnm[i].opt, context).c_str(),
322                     check(fnm[i].fns[0], context).c_str());
323         }
324     }
325     if (npargs > 0)
326     {
327         for (i = 0; (i < npargs); i++)
328         {
329             if (pa[i].type == etBOOL)
330             {
331                 fprintf(out, ".BI \"\\-[no]%s\" \"\"\n",
332                         check(pa[i].option+1, context).c_str());
333             }
334             else
335             {
336                 fprintf(out, ".BI \"%s\" \" %s \"\n",
337                         check(pa[i].option, context).c_str(),
338                         check(get_arg_desc(pa[i].type), context).c_str());
339             }
340         }
341     }
342
343     /* description */
344     if (nldesc > 0)
345     {
346         fprintf(out, ".SH DESCRIPTION\n");
347         for (i = 0; (i < nldesc); i++)
348         {
349             fprintf(out, "\\&%s\n", check(desc[i], context).c_str());
350         }
351     }
352
353     /* FILES */
354     if (nfile > 0)
355     {
356         fprintf(out, ".SH FILES\n");
357         for (i = 0; (i < nfile); i++)
358         {
359             fprintf(out, ".BI \"%s\" \" %s\" \n.B %s\n %s \n\n",
360                     check(fnm[i].opt, context).c_str(),
361                     check(fnm[i].fns[0], context).c_str(),
362                     check(fileopt(fnm[i].flag, tmp), context).c_str(),
363                     check(ftp2desc(fnm[i].ftp), context).c_str());
364         }
365     }
366
367     /* other options */
368     fprintf(out, ".SH OTHER OPTIONS\n");
369     if (npargs > 0)
370     {
371         for (i = 0; (i < npargs); i++)
372         {
373             if (pa[i].type == etBOOL)
374             {
375                 fprintf(out, ".BI \"\\-[no]%s\"  \"%s\"\n %s\n\n",
376                         check(pa[i].option+1, context).c_str(),
377                         check(pa_val(&(pa[i]), tmp, 255), context).c_str(),
378                         check(pa[i].desc, context).c_str());
379             }
380             else
381             {
382                 fprintf(out, ".BI \"%s\"  \" %s\" \" %s\" \n %s\n\n",
383                         check(pa[i].option, context).c_str(),
384                         check(get_arg_desc(pa[i].type), context).c_str(),
385                         check(pa_val(&(pa[i]), tmp, 255), context).c_str(),
386                         check(pa[i].desc, context).c_str());
387             }
388         }
389     }
390
391     if (nbug > 0)
392     {
393         fprintf(out, ".SH KNOWN PROBLEMS\n");
394         for (i = 0; (i < nbug); i++)
395         {
396             fprintf(out, "\\- %s\n\n", check(bugs[i], context).c_str());
397         }
398     }
399 }
400
401 static void
402 print_tty_formatted(FILE *out, int nldesc, const char **desc,
403                     const gmx::HelpWriterContext &context)
404 {
405     char *buf;
406     int   buflen, i;
407
408     buflen = 80*nldesc;
409     snew(buf, buflen);
410     for (i = 0; (i < nldesc); i++)
411     {
412         if ((strlen(buf) > 0) &&
413             (buf[strlen(buf)-1] != ' ') && (buf[strlen(buf)-1] != '\n'))
414         {
415             strcat(buf, " ");
416         }
417         std::string temp = check(desc[i], context);
418         if (strlen(buf) + temp.length() >= (size_t)(buflen-2))
419         {
420             buflen += temp.length();
421             srenew(buf, buflen);
422         }
423         strcat(buf, temp.c_str());
424     }
425     /* Make lines of at most 79 characters */
426     char *temp = wrap_lines(buf, 78, 0, FALSE);
427     fprintf(out, "%s\n", temp);
428     sfree(temp);
429     sfree(buf);
430 }
431
432 static void write_ttyman(FILE *out,
433                          int nldesc, const char **desc,
434                          int nfile, t_filenm *fnm,
435                          int npargs, t_pargs *pa,
436                          int nbug, const char **bugs,
437                          const gmx::HelpWriterContext &context)
438 {
439     int   i;
440     char *tmp;
441
442     if (nldesc > 0)
443     {
444         fprintf(out, "DESCRIPTION\n-----------\n");
445         print_tty_formatted(out, nldesc, desc, context);
446     }
447     if (nbug > 0)
448     {
449         fprintf(out, "\n");
450         fprintf(out, "KNOWN PROBLEMS\n----------\n");
451         for (i = 0; i < nbug; i++)
452         {
453             snew(tmp, strlen(bugs[i])+3);
454             strcpy(tmp, "* ");
455             strcpy(tmp+2, check(bugs[i], context).c_str());
456             fprintf(out, "%s\n", wrap_lines(tmp, 78, 2, FALSE));
457             sfree(tmp);
458         }
459     }
460     if (nfile > 0)
461     {
462         fprintf(out, "\n");
463         pr_fns(out, nfile, fnm);
464     }
465     if (npargs > 0)
466     {
467         print_pargs(out, npargs, pa, context);
468     }
469 }
470
471 static void pr_html_files(FILE *out, int nfile, t_filenm fnm[],
472                           const gmx::HelpWriterContext &context)
473 {
474     int  i;
475     char link[10], tmp[255];
476
477     fprintf(out,
478             "<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2>\n"
479             "<TR>"
480             "<TH>option</TH>"
481             "<TH>filename</TH>"
482             "<TH>type</TH>"
483             "<TH>description</TH>"
484             "</TR>\n");
485
486     for (i = 0; (i < nfile); i++)
487     {
488         strcpy(link, ftp2ext(fnm[i].ftp));
489         if (strcmp(link, "???") == 0)
490         {
491             strcpy(link, "files");
492         }
493         fprintf(out,
494                 "<TR>"
495                 "<TD ALIGN=RIGHT> <b><tt>%s</tt></b> </TD>"
496                 "<TD ALIGN=RIGHT> <tt><a href=\"%s.html\">%12s</a></tt> </TD>"
497                 "<TD> %s </TD>"
498                 "<TD> %s </TD>"
499                 "</TR>\n",
500                 fnm[i].opt, link, fnm[i].fns[0], fileopt(fnm[i].flag, tmp),
501                 check(ftp2desc(fnm[i].ftp), context).c_str());
502     }
503     fprintf(out, "</TABLE>\n");
504 }
505
506 static void write_htmlman(FILE *out,
507                           int nldesc, const char **desc,
508                           int nfile, t_filenm *fnm,
509                           int npargs, t_pargs *pa,
510                           int nbug, const char **bugs,
511                           const gmx::HelpWriterContext &context)
512 {
513     int  i;
514     char tmp[255];
515
516     if (nldesc > 0)
517     {
518         fprintf(out, "<H3>Description</H3>\n<p>\n");
519         for (i = 0; (i < nldesc); i++)
520         {
521             fprintf(out, "%s\n", check(desc[i], context).c_str());
522         }
523     }
524     if (nfile > 0)
525     {
526         fprintf(out, "<P>\n");
527         fprintf(out, "<H3>Files</H3>\n");
528         pr_html_files(out, nfile, fnm, context);
529     }
530     if (npargs > 0)
531     {
532         fprintf(out, "<P>\n");
533         fprintf(out, "<H3>Other options</H3>\n");
534         fprintf(out,
535                 "<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2>\n"
536                 "<TR>"
537                 "<TH>option</TH>"
538                 "<TH>type</TH>"
539                 "<TH>default</TH>"
540                 "<TH>description</TH>"
541                 "</TR>\n");
542         for (i = 0; (i < npargs); i++)
543         {
544             fprintf(out,
545                     "<TR>"
546                     "<TD ALIGN=RIGHT> <b><tt>%s%s</tt></b> </TD>"
547                     "<TD ALIGN=RIGHT> %s </TD>"
548                     "<TD ALIGN=RIGHT> <tt>%s</tt> </TD>"
549                     "<TD> %s </TD>"
550                     "</TD>\n",
551                     (pa[i].type == etBOOL) ? "-[no]" : "-", pa[i].option+1,
552                     get_arg_desc(pa[i].type), pa_val(&(pa[i]), tmp, 255),
553                     check(pa[i].desc, context).c_str());
554         }
555         fprintf(out, "</TABLE>\n");
556     }
557     if (nbug > 0)
558     {
559         fprintf(out, "<P>\n");
560         fprintf(out, "<H3>Known problems</H3>\n");
561         fprintf(out, "<UL>\n");
562         for (i = 0; (i < nbug); i++)
563         {
564             fprintf(out, "<LI>%s\n", check(bugs[i], context).c_str());
565         }
566         fprintf(out, "</UL>\n");
567     }
568 }
569
570 void write_man(const gmx::CommandLineHelpContext &context,
571                int nldesc, const char **desc,
572                int nfile, t_filenm *fnm,
573                int npargs, t_pargs *pa,
574                int nbug, const char **bugs)
575 {
576     FILE       *out     = context.writerContext().outputFile().handle();
577     const bool  bHidden = context.showHidden();
578
579     int         npar;
580     t_pargs    *par;
581
582     if (bHidden)
583     {
584         npar = npargs;
585         par  = pa;
586     }
587     else
588     {
589         snew(par, npargs);
590         npar = 0;
591         for (int i = 0; i < npargs; i++)
592         {
593             if (!is_hidden(&pa[i]))
594             {
595                 par[npar] = pa[i];
596                 npar++;
597             }
598         }
599     }
600
601     switch (context.writerContext().outputFormat())
602     {
603         case gmx::eHelpOutputFormat_Man:
604             write_nroffman(out, nldesc, desc, nfile, fnm, npar, par, nbug, bugs,
605                            context);
606             break;
607         case gmx::eHelpOutputFormat_Console:
608             write_ttyman(out, nldesc, desc, nfile, fnm, npar, par, nbug, bugs,
609                          context.writerContext());
610             break;
611         case gmx::eHelpOutputFormat_Html:
612             write_htmlman(out, nldesc, desc, nfile, fnm, npar, par, nbug, bugs,
613                           context.writerContext());
614             break;
615         default:
616             GMX_THROW(gmx::NotImplementedError("Help format not implemented"));
617     }
618
619     if (!bHidden)
620     {
621         sfree(par);
622     }
623 }