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