0dcf90b666bbd587f4dc61fb89f7344fb63a1232
[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,2015,2016,2017, 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 "gmxpre.h"
43
44 #include "cmdlinehelpwriter.h"
45
46 #include <cstring>
47
48 #include <algorithm>
49 #include <string>
50
51 #include "gromacs/commandline/cmdlinehelpcontext.h"
52 #include "gromacs/onlinehelp/helpwritercontext.h"
53 #include "gromacs/options/basicoptions.h"
54 #include "gromacs/options/filenameoption.h"
55 #include "gromacs/options/options.h"
56 #include "gromacs/options/optionsvisitor.h"
57 #include "gromacs/options/timeunitmanager.h"
58 #include "gromacs/utility/arrayref.h"
59 #include "gromacs/utility/exceptions.h"
60 #include "gromacs/utility/stringutil.h"
61 #include "gromacs/utility/textwriter.h"
62
63 #include "shellcompletions.h"
64
65 namespace gmx
66 {
67
68 namespace
69 {
70
71 //! \addtogroup module_commandline
72 //! \{
73
74 /********************************************************************
75  * IOptionsFormatter
76  */
77
78 /*! \brief
79  * Interface for output format specific formatting of options.
80  *
81  * \see OptionsFilter
82  */
83 class IOptionsFormatter
84 {
85     public:
86         virtual ~IOptionsFormatter() {}
87
88         //! Formats a single option option.
89         virtual void formatOption(const OptionInfo &option) = 0;
90 };
91
92 /********************************************************************
93  * OptionsFilter
94  */
95
96 /*! \brief
97  * Output format independent processing of options.
98  *
99  * Together with code in CommandLineHelpWriter::writeHelp(), this class
100  * implements the common logic for writing out the help.
101  * An object implementing the IOptionsFormatter must be provided to the
102  * constructor, and does the actual formatting that is specific to the output
103  * format.
104  */
105 class OptionsFilter : public OptionsVisitor
106 {
107     public:
108         //! Specifies the type of output that formatSelected() produces.
109         enum FilterType
110         {
111             eSelectInputFileOptions,
112             eSelectInputOutputFileOptions,
113             eSelectOutputFileOptions,
114             eSelectOtherOptions
115         };
116
117         /*! \brief
118          * Creates a new filtering object.
119          *
120          * Does not throw.
121          */
122         OptionsFilter()
123             : formatter_(nullptr), filterType_(eSelectOtherOptions),
124               bShowHidden_(false)
125         {
126         }
127
128         //! Sets whether hidden options will be shown.
129         void setShowHidden(bool bShowHidden)
130         {
131             bShowHidden_ = bShowHidden;
132         }
133
134         //! Formats selected options using the formatter.
135         void formatSelected(FilterType                 type,
136                             IOptionsFormatter         *formatter,
137                             const Options             &options);
138
139         virtual void visitSection(const OptionSectionInfo &section);
140         virtual void visitOption(const OptionInfo &option);
141
142     private:
143         IOptionsFormatter              *formatter_;
144         FilterType                      filterType_;
145         bool                            bShowHidden_;
146
147         GMX_DISALLOW_COPY_AND_ASSIGN(OptionsFilter);
148 };
149
150 void OptionsFilter::formatSelected(FilterType                 type,
151                                    IOptionsFormatter         *formatter,
152                                    const Options             &options)
153 {
154     formatter_  = formatter;
155     filterType_ = type;
156     visitSection(options.rootSection());
157 }
158
159 void OptionsFilter::visitSection(const OptionSectionInfo &section)
160 {
161     OptionsIterator iterator(section);
162     iterator.acceptSections(this);
163     iterator.acceptOptions(this);
164 }
165
166 void OptionsFilter::visitOption(const OptionInfo &option)
167 {
168     if (!bShowHidden_ && option.isHidden())
169     {
170         return;
171     }
172     const FileNameOptionInfo *const fileOption = option.toType<FileNameOptionInfo>();
173     if (fileOption != nullptr && fileOption->isInputFile())
174     {
175         if (filterType_ == eSelectInputFileOptions)
176         {
177             formatter_->formatOption(option);
178         }
179         return;
180     }
181     if (fileOption != nullptr && fileOption->isInputOutputFile())
182     {
183         if (filterType_ == eSelectInputOutputFileOptions)
184         {
185             formatter_->formatOption(option);
186         }
187         return;
188     }
189     if (fileOption != nullptr && fileOption->isOutputFile())
190     {
191         if (filterType_ == eSelectOutputFileOptions)
192         {
193             formatter_->formatOption(option);
194         }
195         return;
196     }
197     if (filterType_ == eSelectOtherOptions)
198     {
199         formatter_->formatOption(option);
200         return;
201     }
202 }
203
204 /********************************************************************
205  * CommonFormatterData
206  */
207
208 class CommonFormatterData
209 {
210     public:
211         explicit CommonFormatterData(const char *timeUnit)
212             : timeUnit(timeUnit)
213         {
214         }
215
216         const char             *timeUnit;
217 };
218
219 /********************************************************************
220  * Helper functions
221  */
222
223 //! Formats option name and value.
224 void formatOptionNameAndValue(const OptionInfo &option, std::string *name,
225                               std::string *value)
226 {
227     *name  = option.name();
228     *value = "<" + option.type() + ">";
229     if (option.isType<FileNameOptionInfo>())
230     {
231         // TODO: Make these work also for other option types.
232         if (option.maxValueCount() != 1)
233         {
234             *value += " [...]";
235         }
236         if (option.minValueCount() == 0)
237         {
238             *value = "[" + *value + "]";
239         }
240     }
241     if (option.isType<BooleanOptionInfo>())
242     {
243         *name = "[no]" + *name;
244         // Old command-line parser doesn't accept any values for these.
245         // value = "[" + value + "]";
246         value->clear();
247     }
248 }
249
250 //! Formats the default option value as a string.
251 std::string defaultOptionValue(const OptionInfo &option)
252 {
253     return joinStrings(option.defaultValuesAsStrings(), " ");
254 }
255
256 //! Formats the flags for a file option as a string.
257 std::string
258 fileOptionFlagsAsString(const FileNameOptionInfo &option, bool bAbbrev)
259 {
260     std::string type;
261     if (!option.isRequired())
262     {
263         type = bAbbrev ? "Opt." : "Optional";
264     }
265     if (option.isLibraryFile())
266     {
267         if (!type.empty())
268         {
269             type.append(", ");
270         }
271         type.append(bAbbrev ? "Lib." : "Library");
272     }
273     return type;
274 }
275
276 //! Formats the description for an option as a string.
277 std::string
278 descriptionWithOptionDetails(const CommonFormatterData &common,
279                              const OptionInfo          &option)
280 {
281     std::string             description(option.formatDescription());
282
283     const FloatOptionInfo  *floatOption  = option.toType<FloatOptionInfo>();
284     const DoubleOptionInfo *doubleOption = option.toType<DoubleOptionInfo>();
285     if ((floatOption != nullptr && floatOption->isTime())
286         || (doubleOption != nullptr && doubleOption->isTime()))
287     {
288         // TODO: It could be nicer to have this in basicoptions.cpp.
289         description = replaceAll(description, "%t", common.timeUnit);
290     }
291
292     return description;
293 }
294
295 /********************************************************************
296  * OptionsSynopsisFormatter
297  */
298
299 /*! \brief
300  * Formatter implementation for synopsis.
301  */
302 class SynopsisFormatter : public IOptionsFormatter
303 {
304     public:
305         //! Creates a helper object for formatting the synopsis.
306         explicit SynopsisFormatter(const HelpWriterContext &context)
307             : context_(context), bFormatted_(false), lineLength_(0), indent_(0),
308               currentLength_(0)
309         {
310         }
311
312         //! Starts formatting the synopsis.
313         void start(const char *name);
314         //! Finishes formatting the synopsis.
315         void finish();
316
317         virtual void formatOption(const OptionInfo &option);
318
319     private:
320         const HelpWriterContext &context_;
321         bool                     bFormatted_;
322         int                      lineLength_;
323         int                      indent_;
324         int                      currentLength_;
325
326         GMX_DISALLOW_COPY_AND_ASSIGN(SynopsisFormatter);
327 };
328
329 void SynopsisFormatter::start(const char *name)
330 {
331     currentLength_ = std::strlen(name) + 1;
332     indent_        = std::min(currentLength_, 13);
333     TextWriter &file = context_.outputFile();
334     switch (context_.outputFormat())
335     {
336         case eHelpOutputFormat_Console:
337             lineLength_ = 78;
338             file.writeString(name);
339             break;
340         case eHelpOutputFormat_Rst:
341             bFormatted_ = true;
342             lineLength_ = 74;
343             indent_    += 4;
344             file.writeLine(".. parsed-literal::");
345             file.writeLine();
346             file.writeString("    ");
347             file.writeString(name);
348             break;
349         default:
350             GMX_THROW(NotImplementedError("Synopsis formatting not implemented for this output format"));
351     }
352 }
353
354 void SynopsisFormatter::finish()
355 {
356     context_.outputFile().ensureLineBreak();
357 }
358
359 void SynopsisFormatter::formatOption(const OptionInfo &option)
360 {
361     std::string name, value;
362     formatOptionNameAndValue(option, &name, &value);
363     int         totalLength = name.length() + 4;
364     std::string fullOptionText
365         = formatString(" [%s-%s", bFormatted_ ? ":strong:`" : "", name.c_str());
366     if (!value.empty())
367     {
368         fullOptionText.append(bFormatted_ ? "` :emphasis:`" : " ");
369         fullOptionText.append(value);
370         totalLength += value.length() + 1;
371     }
372     fullOptionText.append(bFormatted_ ? "`]" : "]");
373
374     TextWriter &file = context_.outputFile();
375     currentLength_ += totalLength;
376     if (currentLength_ >= lineLength_)
377     {
378         file.writeString(formatString("\n%*c", indent_ - 1, ' '));
379         currentLength_ = indent_ - 1 + totalLength;
380     }
381     file.writeString(fullOptionText);
382 }
383
384 /********************************************************************
385  * OptionsListFormatter
386  */
387
388 /*! \brief
389  * Formatter implementation for help export.
390  */
391 class OptionsListFormatter : public IOptionsFormatter
392 {
393     public:
394         //! Creates a helper object for formatting options.
395         OptionsListFormatter(const HelpWriterContext   &context,
396                              const CommonFormatterData &common,
397                              const char                *title);
398
399         //! Initiates a new section in the options list.
400         void startSection(const char *header)
401         {
402             header_     = header;
403             bDidOutput_ = false;
404         }
405         //! Finishes a section in the options list.
406         void finishSection()
407         {
408             if (bDidOutput_)
409             {
410                 context_.writeOptionListEnd();
411             }
412         }
413
414         virtual void formatOption(const OptionInfo &option);
415
416     private:
417         void writeSectionStartIfNecessary()
418         {
419             if (title_ != nullptr)
420             {
421                 context_.writeTitle(title_);
422                 title_ = nullptr;
423             }
424             if (!bDidOutput_)
425             {
426                 if (header_ != nullptr)
427                 {
428                     context_.paragraphBreak();
429                     context_.writeTextBlock(header_);
430                     context_.paragraphBreak();
431                 }
432                 context_.writeOptionListStart();
433             }
434             bDidOutput_ = true;
435         }
436
437         const HelpWriterContext               &context_;
438         const CommonFormatterData             &common_;
439         const char                            *title_;
440         const char                            *header_;
441         bool                                   bDidOutput_;
442
443         GMX_DISALLOW_COPY_AND_ASSIGN(OptionsListFormatter);
444 };
445
446 OptionsListFormatter::OptionsListFormatter(
447         const HelpWriterContext   &context,
448         const CommonFormatterData &common,
449         const char                *title)
450     : context_(context), common_(common),
451       title_(title), header_(nullptr), bDidOutput_(false)
452 {
453 }
454
455 void OptionsListFormatter::formatOption(const OptionInfo &option)
456 {
457     writeSectionStartIfNecessary();
458     std::string               name, value;
459     formatOptionNameAndValue(option, &name, &value);
460     std::string               defaultValue(defaultOptionValue(option));
461     std::string               info;
462     const FileNameOptionInfo *fileOption = option.toType<FileNameOptionInfo>();
463     if (fileOption != nullptr)
464     {
465         const bool bAbbrev = (context_.outputFormat() == eHelpOutputFormat_Console);
466         info = fileOptionFlagsAsString(*fileOption, bAbbrev);
467     }
468     std::string description(descriptionWithOptionDetails(common_, option));
469     context_.writeOptionItem("-" + name, value, defaultValue, info, description);
470 }
471
472 //! \}
473
474 }   // namespace
475
476 /********************************************************************
477  * CommandLineHelpWriter::Impl
478  */
479
480 /*! \internal \brief
481  * Private implementation class for CommandLineHelpWriter.
482  *
483  * \ingroup module_commandline
484  */
485 class CommandLineHelpWriter::Impl
486 {
487     public:
488         //! Sets the Options object to use for generating help.
489         explicit Impl(const Options &options)
490             : options_(options)
491         {
492         }
493
494         //! Format the list of known issues.
495         void formatBugs(const HelpWriterContext &context);
496
497         //! Options object to use for generating help.
498         const Options               &options_;
499         //! Help text.
500         std::string                  helpText_;
501         //! List of bugs/knows issues.
502         ArrayRef<const char *const>  bugs_;
503 };
504
505 void CommandLineHelpWriter::Impl::formatBugs(const HelpWriterContext &context)
506 {
507     if (bugs_.empty())
508     {
509         return;
510     }
511     context.writeTitle("Known Issues");
512     ArrayRef<const char *const>::const_iterator i;
513     for (i = bugs_.begin(); i != bugs_.end(); ++i)
514     {
515         const char *const       bug = *i;
516         context.writeTextBlock(formatString("* %s", bug));
517     }
518 }
519
520
521 /********************************************************************
522  * CommandLineHelpWriter
523  */
524
525 CommandLineHelpWriter::CommandLineHelpWriter(const Options &options)
526     : impl_(new Impl(options))
527 {
528 }
529
530 CommandLineHelpWriter::~CommandLineHelpWriter()
531 {
532 }
533
534 CommandLineHelpWriter &
535 CommandLineHelpWriter::setHelpText(const std::string &help)
536 {
537     impl_->helpText_ = help;
538     return *this;
539 }
540
541 CommandLineHelpWriter &
542 CommandLineHelpWriter::setHelpText(const ArrayRef<const char *const> &help)
543 {
544     impl_->helpText_ = joinStrings(help, "\n");
545     return *this;
546 }
547
548 CommandLineHelpWriter &
549 CommandLineHelpWriter::setKnownIssues(const ArrayRef<const char *const> &bugs)
550 {
551     impl_->bugs_ = bugs;
552     return *this;
553 }
554
555 void CommandLineHelpWriter::writeHelp(const CommandLineHelpContext &context)
556 {
557     if (context.isCompletionExport())
558     {
559         context.shellCompletionWriter().writeModuleCompletions(
560                 context.moduleDisplayName(), impl_->options_);
561         return;
562     }
563     const HelpWriterContext &writerContext = context.writerContext();
564     OptionsFilter            filter;
565     filter.setShowHidden(context.showHidden());
566
567     {
568         writerContext.writeTitle("Synopsis");
569         SynopsisFormatter synopsisFormatter(writerContext);
570         synopsisFormatter.start(context.moduleDisplayName());
571         filter.formatSelected(OptionsFilter::eSelectInputFileOptions,
572                               &synopsisFormatter, impl_->options_);
573         filter.formatSelected(OptionsFilter::eSelectInputOutputFileOptions,
574                               &synopsisFormatter, impl_->options_);
575         filter.formatSelected(OptionsFilter::eSelectOutputFileOptions,
576                               &synopsisFormatter, impl_->options_);
577         filter.formatSelected(OptionsFilter::eSelectOtherOptions,
578                               &synopsisFormatter, impl_->options_);
579         synopsisFormatter.finish();
580     }
581
582     if (!impl_->helpText_.empty())
583     {
584         writerContext.writeTitle("Description");
585         writerContext.writeTextBlock(impl_->helpText_);
586     }
587     CommonFormatterData    common(TimeUnitManager().timeUnitAsString());
588     OptionsListFormatter   formatter(writerContext, common, "Options");
589     formatter.startSection("Options to specify input files:");
590     filter.formatSelected(OptionsFilter::eSelectInputFileOptions,
591                           &formatter, impl_->options_);
592     formatter.finishSection();
593     formatter.startSection("Options to specify input/output files:");
594     filter.formatSelected(OptionsFilter::eSelectInputOutputFileOptions,
595                           &formatter, impl_->options_);
596     formatter.finishSection();
597     formatter.startSection("Options to specify output files:");
598     filter.formatSelected(OptionsFilter::eSelectOutputFileOptions,
599                           &formatter, impl_->options_);
600     formatter.finishSection();
601     formatter.startSection("Other options:");
602     filter.formatSelected(OptionsFilter::eSelectOtherOptions,
603                           &formatter, impl_->options_);
604     formatter.finishSection();
605
606     impl_->formatBugs(writerContext);
607 }
608
609 } // namespace gmx