Implement synopsis for help output
[alexxy/gromacs.git] / src / gromacs / commandline / cmdlinehelpwriter.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2010,2011,2012,2013,2014, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::CommandLineHelpWriter.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_commandline
41  */
42 #include "cmdlinehelpwriter.h"
43
44 #include <cstring>
45
46 #include <algorithm>
47 #include <string>
48
49 #include <boost/scoped_ptr.hpp>
50
51 #include "gromacs/commandline/cmdlinehelpcontext.h"
52 #include "gromacs/commandline/shellcompletions.h"
53 #include "gromacs/onlinehelp/helpformat.h"
54 #include "gromacs/onlinehelp/helpwritercontext.h"
55 #include "gromacs/options/basicoptions.h"
56 #include "gromacs/options/filenameoption.h"
57 #include "gromacs/options/options.h"
58 #include "gromacs/options/optionsvisitor.h"
59 #include "gromacs/options/timeunitmanager.h"
60 #include "gromacs/utility/exceptions.h"
61 #include "gromacs/utility/file.h"
62 #include "gromacs/utility/stringutil.h"
63
64 namespace gmx
65 {
66
67 namespace
68 {
69
70 //! \addtogroup module_commandline
71 //! \{
72
73 /********************************************************************
74  * DescriptionsFormatter
75  */
76
77 class DescriptionsFormatter : public OptionsVisitor
78 {
79     public:
80         /*! \brief
81          * Creates a new description formatter.
82          *
83          * \param[in] context   Help context to use to write the help.
84          */
85         explicit DescriptionsFormatter(const HelpWriterContext &context)
86             : context_(context), title_(NULL), bDidOutput_(false)
87         {
88         }
89
90         //! Formats all section descriptions from a given options.
91         void format(const Options &options, const char *title)
92         {
93             title_      = title;
94             bDidOutput_ = false;
95             visitSubSection(options);
96             if (bDidOutput_)
97             {
98                 context_.outputFile().writeLine();
99             }
100         }
101
102         //! Formats the description for a single subsection and handles recursion.
103         virtual void visitSubSection(const Options &section);
104         // This method is not used and never called.
105         virtual void visitOption(const OptionInfo & /*option*/) {}
106
107     private:
108         const HelpWriterContext &context_;
109         const char              *title_;
110         bool                     bDidOutput_;
111
112         GMX_DISALLOW_COPY_AND_ASSIGN(DescriptionsFormatter);
113 };
114
115 void DescriptionsFormatter::visitSubSection(const Options &section)
116 {
117     if (!section.description().empty())
118     {
119         if (bDidOutput_)
120         {
121             context_.outputFile().writeLine();
122         }
123         else if (title_ != NULL)
124         {
125             context_.writeTitle(title_);
126         }
127         // TODO: Print title for the section?
128         context_.writeTextBlock(section.description());
129         bDidOutput_ = true;
130     }
131
132     OptionsIterator iterator(section);
133     iterator.acceptSubSections(this);
134 }
135
136 /********************************************************************
137  * OptionsFormatterInterface
138  */
139
140 /*! \brief
141  * Interface for output format specific formatting of options.
142  *
143  * \see OptionsFilter
144  */
145 class OptionsFormatterInterface
146 {
147     public:
148         virtual ~OptionsFormatterInterface() {}
149
150         //! Formats a single option option.
151         virtual void formatOption(const OptionInfo &option) = 0;
152 };
153
154 /********************************************************************
155  * OptionsFilter
156  */
157
158 /*! \brief
159  * Output format independent processing of options.
160  *
161  * Together with code in CommandLineHelpWriter::writeHelp(), this class
162  * implements the common logic for writing out the help.
163  * An object implementing the OptionsFormatterInterface must be provided to the
164  * constructor, and does the actual formatting that is specific to the output
165  * format.
166  */
167 class OptionsFilter : public OptionsVisitor
168 {
169     public:
170         //! Specifies the type of output that formatSelected() produces.
171         enum FilterType
172         {
173             eSelectFileOptions,
174             eSelectOtherOptions
175         };
176
177         /*! \brief
178          * Creates a new filtering object.
179          *
180          * Does not throw.
181          */
182         OptionsFilter()
183             : formatter_(NULL), filterType_(eSelectOtherOptions),
184               bShowHidden_(false)
185         {
186         }
187
188         //! Sets whether hidden options will be shown.
189         void setShowHidden(bool bShowHidden)
190         {
191             bShowHidden_ = bShowHidden;
192         }
193
194         //! Formats selected options using the formatter.
195         void formatSelected(FilterType                 type,
196                             OptionsFormatterInterface *formatter,
197                             const Options             &options);
198
199         virtual void visitSubSection(const Options &section);
200         virtual void visitOption(const OptionInfo &option);
201
202     private:
203         OptionsFormatterInterface      *formatter_;
204         FilterType                      filterType_;
205         bool                            bShowHidden_;
206
207         GMX_DISALLOW_COPY_AND_ASSIGN(OptionsFilter);
208 };
209
210 void OptionsFilter::formatSelected(FilterType                 type,
211                                    OptionsFormatterInterface *formatter,
212                                    const Options             &options)
213 {
214     formatter_  = formatter;
215     filterType_ = type;
216     visitSubSection(options);
217 }
218
219 void OptionsFilter::visitSubSection(const Options &section)
220 {
221     OptionsIterator iterator(section);
222     iterator.acceptSubSections(this);
223     iterator.acceptOptions(this);
224 }
225
226 void OptionsFilter::visitOption(const OptionInfo &option)
227 {
228     if (!bShowHidden_ && option.isHidden())
229     {
230         return;
231     }
232     if (option.isType<FileNameOptionInfo>())
233     {
234         if (filterType_ == eSelectFileOptions)
235         {
236             formatter_->formatOption(option);
237         }
238         return;
239     }
240     if (filterType_ == eSelectOtherOptions)
241     {
242         formatter_->formatOption(option);
243         return;
244     }
245 }
246
247 /********************************************************************
248  * CommonFormatterData
249  */
250
251 class CommonFormatterData
252 {
253     public:
254         explicit CommonFormatterData(const char *timeUnit)
255             : timeUnit(timeUnit)
256         {
257         }
258
259         const char             *timeUnit;
260 };
261
262 /********************************************************************
263  * Helper functions
264  */
265
266 //! Formats option name and value.
267 void formatOptionNameAndValue(const OptionInfo &option, std::string *name,
268                               std::string *value)
269 {
270     *name  = option.name();
271     *value = "<" + option.type() + ">";
272     if (option.isType<FileNameOptionInfo>())
273     {
274         // TODO: Make these work also for other option types.
275         if (option.maxValueCount() != 1)
276         {
277             *value += " [...]";
278         }
279         if (option.minValueCount() == 0)
280         {
281             *value = "[" + *value + "]";
282         }
283     }
284     if (option.isType<BooleanOptionInfo>())
285     {
286         *name = "[no]" + *name;
287         // Old command-line parser doesn't accept any values for these.
288         // value = "[" + value + "]";
289         value->clear();
290     }
291 }
292
293 //! Formats the default option value as a string.
294 std::string
295 defaultOptionValue(const OptionInfo &option)
296 {
297     if (option.valueCount() == 0
298         || (option.valueCount() == 1 && option.formatValue(0).empty()))
299     {
300         return option.formatDefaultValueIfSet();
301     }
302     else
303     {
304         std::string result;
305         for (int i = 0; i < option.valueCount(); ++i)
306         {
307             if (i != 0)
308             {
309                 result.append(" ");
310             }
311             result.append(option.formatValue(i));
312         }
313         return result;
314     }
315 }
316
317 //! Formats the flags for a file option as a string.
318 std::string
319 fileOptionFlagsAsString(const FileNameOptionInfo &option, bool bAbbrev)
320 {
321     std::string type;
322     if (option.isInputOutputFile())
323     {
324         type = bAbbrev ? "In/Out" : "Input/Output";
325     }
326     else if (option.isInputFile())
327     {
328         type = "Input";
329     }
330     else if (option.isOutputFile())
331     {
332         type = "Output";
333     }
334     if (!option.isRequired())
335     {
336         type += bAbbrev ? ", Opt." : ", Optional";
337     }
338     if (option.isLibraryFile())
339     {
340         type += bAbbrev ? ", Lib." : ", Library";
341     }
342     return type;
343 }
344
345 //! Formats the description for an option as a string.
346 std::string
347 descriptionWithOptionDetails(const CommonFormatterData &common,
348                              const OptionInfo          &option)
349 {
350     std::string             description(option.formatDescription());
351
352     const FloatOptionInfo  *floatOption  = option.toType<FloatOptionInfo>();
353     const DoubleOptionInfo *doubleOption = option.toType<DoubleOptionInfo>();
354     if ((floatOption != NULL && floatOption->isTime())
355         || (doubleOption != NULL && doubleOption->isTime()))
356     {
357         // TODO: It could be nicer to have this in basicoptions.cpp.
358         description = replaceAll(description, "%t", common.timeUnit);
359     }
360
361     return description;
362 }
363
364 /********************************************************************
365  * OptionsSynopsisFormatter
366  */
367
368 /*! \brief
369  * Formatter implementation for synopsis.
370  */
371 class SynopsisFormatter : public OptionsFormatterInterface
372 {
373     public:
374         //! Creates a helper object for formatting the synopsis.
375         explicit SynopsisFormatter(const HelpWriterContext &context)
376             : context_(context), lineLength_(0), indent_(0), currentLength_(0)
377         {
378         }
379
380         //! Starts formatting the synopsis.
381         void start(const char *name);
382         //! Finishes formatting the synopsis.
383         void finish();
384
385         virtual void formatOption(const OptionInfo &option);
386
387     private:
388         const HelpWriterContext &context_;
389         int                      lineLength_;
390         int                      indent_;
391         int                      currentLength_;
392
393         GMX_DISALLOW_COPY_AND_ASSIGN(SynopsisFormatter);
394 };
395
396 void SynopsisFormatter::start(const char *name)
397 {
398     currentLength_ = std::strlen(name) + 1;
399     indent_        = std::min(currentLength_, 13);
400     File &file = context_.outputFile();
401     switch (context_.outputFormat())
402     {
403         case eHelpOutputFormat_Console:
404             lineLength_ = 78;
405             file.writeString(name);
406             break;
407         case eHelpOutputFormat_Man:
408             lineLength_ = 70;
409             file.writeString(name);
410             break;
411         case eHelpOutputFormat_Html:
412             lineLength_ = 78;
413             file.writeLine("<pre>");
414             file.writeString(name);
415             break;
416         default:
417             GMX_THROW(NotImplementedError("Synopsis formatting not implemented for this output format"));
418     }
419 }
420
421 void SynopsisFormatter::finish()
422 {
423     File &file = context_.outputFile();
424     file.writeLine();
425     if (context_.outputFormat() == eHelpOutputFormat_Html)
426     {
427         file.writeLine("</pre>");
428     }
429     file.writeLine();
430 }
431
432 void SynopsisFormatter::formatOption(const OptionInfo &option)
433 {
434     std::string name, value;
435     formatOptionNameAndValue(option, &name, &value);
436     std::string fullOptionText(" [-" + name);
437     if (!value.empty())
438     {
439         fullOptionText.append(" ");
440         fullOptionText.append(value);
441     }
442     fullOptionText.append("]");
443     const int   totalLength = fullOptionText.size();
444
445     if (context_.outputFormat() == eHelpOutputFormat_Html)
446     {
447         value = replaceAll(value, "<", "&lt;");
448         value = replaceAll(value, ">", "&gt;");
449     }
450
451     File &file = context_.outputFile();
452     currentLength_ += totalLength;
453     if (currentLength_ >= lineLength_)
454     {
455         file.writeString(formatString("\n%*c", indent_ - 1, ' '));
456         currentLength_ = indent_ - 1 + totalLength;
457     }
458     file.writeString(fullOptionText);
459 }
460
461 /********************************************************************
462  * OptionsListFormatter
463  */
464
465 /*! \brief
466  * Formatter implementation for help export.
467  */
468 class OptionsListFormatter : public OptionsFormatterInterface
469 {
470     public:
471         //! Creates a helper object for formatting options.
472         OptionsListFormatter(const HelpWriterContext   &context,
473                              const CommonFormatterData &common,
474                              bool                       bConsole);
475
476         //! Initiates a new section in the options list.
477         void startSection(const char *title)
478         {
479             title_      = title;
480             bDidOutput_ = false;
481         }
482         //! Finishes a section in the options list.
483         void finishSection()
484         {
485             if (bDidOutput_)
486             {
487                 context_.writeOptionListEnd();
488                 context_.outputFile().writeLine();
489             }
490         }
491
492         virtual void formatOption(const OptionInfo &option);
493
494     private:
495         void writeSectionStartIfNecessary()
496         {
497             if (!bDidOutput_)
498             {
499                 if (title_ != NULL)
500                 {
501                     context_.writeTitle(title_);
502                 }
503                 context_.writeOptionListStart();
504             }
505             bDidOutput_ = true;
506         }
507
508         const HelpWriterContext               &context_;
509         const CommonFormatterData             &common_;
510         boost::scoped_ptr<TextTableFormatter>  consoleFormatter_;
511         const char                            *title_;
512         bool                                   bDidOutput_;
513
514         GMX_DISALLOW_COPY_AND_ASSIGN(OptionsListFormatter);
515 };
516
517 OptionsListFormatter::OptionsListFormatter(
518         const HelpWriterContext   &context,
519         const CommonFormatterData &common,
520         bool                       bConsole)
521     : context_(context), common_(common), title_(NULL), bDidOutput_(false)
522 {
523     if (bConsole)
524     {
525         consoleFormatter_.reset(new TextTableFormatter());
526         consoleFormatter_->setFirstColumnIndent(1);
527         consoleFormatter_->setFoldLastColumnToNextLine(4);
528         consoleFormatter_->addColumn(NULL, 6, false);
529         consoleFormatter_->addColumn(NULL, 8, false);
530         consoleFormatter_->addColumn(NULL, 10, false);
531         consoleFormatter_->addColumn(NULL, 50, true);
532     }
533 }
534
535 void OptionsListFormatter::formatOption(const OptionInfo &option)
536 {
537     writeSectionStartIfNecessary();
538     std::string name, value;
539     formatOptionNameAndValue(option, &name, &value);
540     std::string defaultValue(defaultOptionValue(option));
541     std::string info;
542     if (!defaultValue.empty())
543     {
544         info = "(" + defaultValue + ")";
545     }
546     const FileNameOptionInfo *fileOption = option.toType<FileNameOptionInfo>();
547     if (fileOption != NULL)
548     {
549         const bool bAbbrev = (context_.outputFormat() == eHelpOutputFormat_Console);
550         if (!info.empty())
551         {
552             info.append(" ");
553         }
554         info.append("(");
555         info.append(fileOptionFlagsAsString(*fileOption, bAbbrev));
556         info.append(")");
557     }
558     std::string description(descriptionWithOptionDetails(common_, option));
559     if (context_.outputFormat() == eHelpOutputFormat_Console)
560     {
561         consoleFormatter_->clear();
562         consoleFormatter_->addColumnLine(0, "-" + name);
563         consoleFormatter_->addColumnLine(1, value);
564         if (!info.empty())
565         {
566             consoleFormatter_->addColumnLine(2, info);
567         }
568         consoleFormatter_->addColumnHelpTextBlock(3, context_, description);
569         context_.outputFile().writeString(consoleFormatter_->formatRow());
570     }
571     else
572     {
573         if (!info.empty())
574         {
575             value.append(" ");
576             value.append(info);
577         }
578         context_.writeOptionItem("-" + name, value, description);
579     }
580 }
581
582 //! \}
583
584 }   // namespace
585
586 /********************************************************************
587  * CommandLineHelpWriter::Impl
588  */
589
590 /*! \internal \brief
591  * Private implementation class for CommandLineHelpWriter.
592  *
593  * \ingroup module_commandline
594  */
595 class CommandLineHelpWriter::Impl
596 {
597     public:
598         //! Sets the Options object to use for generating help.
599         explicit Impl(const Options &options);
600
601         //! Options object to use for generating help.
602         const Options          &options_;
603         //! Time unit to show in descriptions.
604         std::string             timeUnit_;
605         //! Whether to write descriptions to output.
606         bool                    bShowDescriptions_;
607 };
608
609 CommandLineHelpWriter::Impl::Impl(const Options &options)
610     : options_(options), timeUnit_(TimeUnitManager().timeUnitAsString()),
611       bShowDescriptions_(false)
612 {
613 }
614
615 /********************************************************************
616  * CommandLineHelpWriter
617  */
618
619 CommandLineHelpWriter::CommandLineHelpWriter(const Options &options)
620     : impl_(new Impl(options))
621 {
622 }
623
624 CommandLineHelpWriter::~CommandLineHelpWriter()
625 {
626 }
627
628 CommandLineHelpWriter &CommandLineHelpWriter::setShowDescriptions(bool bSet)
629 {
630     impl_->bShowDescriptions_ = bSet;
631     return *this;
632 }
633
634 CommandLineHelpWriter &CommandLineHelpWriter::setTimeUnitString(const char *timeUnit)
635 {
636     impl_->timeUnit_ = timeUnit;
637     return *this;
638 }
639
640 void CommandLineHelpWriter::writeHelp(const CommandLineHelpContext &context)
641 {
642     if (context.isCompletionExport())
643     {
644         context.shellCompletionWriter().writeModuleCompletions(
645                 context.moduleDisplayName(), impl_->options_);
646         return;
647     }
648     const HelpWriterContext &writerContext = context.writerContext();
649     const bool               bConsole
650         = (writerContext.outputFormat() == eHelpOutputFormat_Console);
651     OptionsFilter            filter;
652     filter.setShowHidden(context.showHidden());
653
654     {
655         writerContext.writeTitle("Synopsis");
656         SynopsisFormatter synopsisFormatter(writerContext);
657         synopsisFormatter.start(context.moduleDisplayName());
658         filter.formatSelected(OptionsFilter::eSelectFileOptions,
659                               &synopsisFormatter, impl_->options_);
660         filter.formatSelected(OptionsFilter::eSelectOtherOptions,
661                               &synopsisFormatter, impl_->options_);
662         synopsisFormatter.finish();
663     }
664
665     if (impl_->bShowDescriptions_)
666     {
667         DescriptionsFormatter descriptionFormatter(writerContext);
668         descriptionFormatter.format(impl_->options_, "Description");
669     }
670     CommonFormatterData    common(impl_->timeUnit_.c_str());
671     OptionsListFormatter   formatter(writerContext, common, bConsole);
672     formatter.startSection("File Options");
673     filter.formatSelected(OptionsFilter::eSelectFileOptions,
674                           &formatter, impl_->options_);
675     formatter.finishSection();
676     formatter.startSection("Options");
677     filter.formatSelected(OptionsFilter::eSelectOtherOptions,
678                           &formatter, impl_->options_);
679     formatter.finishSection();
680 }
681
682 } // namespace gmx