b410e904af8441624f1aabe08a60fb4ab476d8cf
[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 "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/commandline/shellcompletions.h"
55 #include "gromacs/onlinehelp/helpformat.h"
56 #include "gromacs/onlinehelp/helpwritercontext.h"
57 #include "gromacs/options/basicoptions.h"
58 #include "gromacs/options/filenameoption.h"
59 #include "gromacs/options/options.h"
60 #include "gromacs/options/optionsvisitor.h"
61 #include "gromacs/options/timeunitmanager.h"
62 #include "gromacs/utility/arrayref.h"
63 #include "gromacs/utility/exceptions.h"
64 #include "gromacs/utility/file.h"
65 #include "gromacs/utility/stringutil.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             eSelectFileOptions,
177             eSelectOtherOptions
178         };
179
180         /*! \brief
181          * Creates a new filtering object.
182          *
183          * Does not throw.
184          */
185         OptionsFilter()
186             : formatter_(NULL), filterType_(eSelectOtherOptions),
187               bShowHidden_(false)
188         {
189         }
190
191         //! Sets whether hidden options will be shown.
192         void setShowHidden(bool bShowHidden)
193         {
194             bShowHidden_ = bShowHidden;
195         }
196
197         //! Formats selected options using the formatter.
198         void formatSelected(FilterType                 type,
199                             OptionsFormatterInterface *formatter,
200                             const Options             &options);
201
202         virtual void visitSubSection(const Options &section);
203         virtual void visitOption(const OptionInfo &option);
204
205     private:
206         OptionsFormatterInterface      *formatter_;
207         FilterType                      filterType_;
208         bool                            bShowHidden_;
209
210         GMX_DISALLOW_COPY_AND_ASSIGN(OptionsFilter);
211 };
212
213 void OptionsFilter::formatSelected(FilterType                 type,
214                                    OptionsFormatterInterface *formatter,
215                                    const Options             &options)
216 {
217     formatter_  = formatter;
218     filterType_ = type;
219     visitSubSection(options);
220 }
221
222 void OptionsFilter::visitSubSection(const Options &section)
223 {
224     OptionsIterator iterator(section);
225     iterator.acceptSubSections(this);
226     iterator.acceptOptions(this);
227 }
228
229 void OptionsFilter::visitOption(const OptionInfo &option)
230 {
231     if (!bShowHidden_ && option.isHidden())
232     {
233         return;
234     }
235     if (option.isType<FileNameOptionInfo>())
236     {
237         if (filterType_ == eSelectFileOptions)
238         {
239             formatter_->formatOption(option);
240         }
241         return;
242     }
243     if (filterType_ == eSelectOtherOptions)
244     {
245         formatter_->formatOption(option);
246         return;
247     }
248 }
249
250 /********************************************************************
251  * CommonFormatterData
252  */
253
254 class CommonFormatterData
255 {
256     public:
257         explicit CommonFormatterData(const char *timeUnit)
258             : timeUnit(timeUnit)
259         {
260         }
261
262         const char             *timeUnit;
263 };
264
265 /********************************************************************
266  * Helper functions
267  */
268
269 //! Formats option name and value.
270 void formatOptionNameAndValue(const OptionInfo &option, std::string *name,
271                               std::string *value)
272 {
273     *name  = option.name();
274     *value = "<" + option.type() + ">";
275     if (option.isType<FileNameOptionInfo>())
276     {
277         // TODO: Make these work also for other option types.
278         if (option.maxValueCount() != 1)
279         {
280             *value += " [...]";
281         }
282         if (option.minValueCount() == 0)
283         {
284             *value = "[" + *value + "]";
285         }
286     }
287     if (option.isType<BooleanOptionInfo>())
288     {
289         *name = "[no]" + *name;
290         // Old command-line parser doesn't accept any values for these.
291         // value = "[" + value + "]";
292         value->clear();
293     }
294 }
295
296 //! Formats the default option value as a string.
297 std::string
298 defaultOptionValue(const OptionInfo &option)
299 {
300     if (option.valueCount() == 0
301         || (option.valueCount() == 1 && option.formatValue(0).empty()))
302     {
303         return option.formatDefaultValueIfSet();
304     }
305     else
306     {
307         std::string result;
308         for (int i = 0; i < option.valueCount(); ++i)
309         {
310             if (i != 0)
311             {
312                 result.append(" ");
313             }
314             result.append(option.formatValue(i));
315         }
316         return result;
317     }
318 }
319
320 //! Formats the flags for a file option as a string.
321 std::string
322 fileOptionFlagsAsString(const FileNameOptionInfo &option, bool bAbbrev)
323 {
324     std::string type;
325     if (option.isInputOutputFile())
326     {
327         type = bAbbrev ? "In/Out" : "Input/Output";
328     }
329     else if (option.isInputFile())
330     {
331         type = "Input";
332     }
333     else if (option.isOutputFile())
334     {
335         type = "Output";
336     }
337     if (!option.isRequired())
338     {
339         type += bAbbrev ? ", Opt." : ", Optional";
340     }
341     if (option.isLibraryFile())
342     {
343         type += bAbbrev ? ", Lib." : ", Library";
344     }
345     return type;
346 }
347
348 //! Formats the description for an option as a string.
349 std::string
350 descriptionWithOptionDetails(const CommonFormatterData &common,
351                              const OptionInfo          &option)
352 {
353     std::string             description(option.formatDescription());
354
355     const FloatOptionInfo  *floatOption  = option.toType<FloatOptionInfo>();
356     const DoubleOptionInfo *doubleOption = option.toType<DoubleOptionInfo>();
357     if ((floatOption != NULL && floatOption->isTime())
358         || (doubleOption != NULL && doubleOption->isTime()))
359     {
360         // TODO: It could be nicer to have this in basicoptions.cpp.
361         description = replaceAll(description, "%t", common.timeUnit);
362     }
363
364     return description;
365 }
366
367 /********************************************************************
368  * OptionsSynopsisFormatter
369  */
370
371 /*! \brief
372  * Formatter implementation for synopsis.
373  */
374 class SynopsisFormatter : public OptionsFormatterInterface
375 {
376     public:
377         //! Creates a helper object for formatting the synopsis.
378         explicit SynopsisFormatter(const HelpWriterContext &context)
379             : context_(context), lineLength_(0), indent_(0), currentLength_(0)
380         {
381         }
382
383         //! Starts formatting the synopsis.
384         void start(const char *name);
385         //! Finishes formatting the synopsis.
386         void finish();
387
388         virtual void formatOption(const OptionInfo &option);
389
390     private:
391         const HelpWriterContext &context_;
392         int                      lineLength_;
393         int                      indent_;
394         int                      currentLength_;
395
396         GMX_DISALLOW_COPY_AND_ASSIGN(SynopsisFormatter);
397 };
398
399 void SynopsisFormatter::start(const char *name)
400 {
401     currentLength_ = std::strlen(name) + 1;
402     indent_        = std::min(currentLength_, 13);
403     File &file = context_.outputFile();
404     switch (context_.outputFormat())
405     {
406         case eHelpOutputFormat_Console:
407             lineLength_ = 78;
408             file.writeString(name);
409             break;
410         case eHelpOutputFormat_Man:
411             lineLength_ = 70;
412             file.writeString(name);
413             break;
414         case eHelpOutputFormat_Html:
415             lineLength_ = 78;
416             file.writeLine("<pre>");
417             file.writeString(name);
418             break;
419         default:
420             GMX_THROW(NotImplementedError("Synopsis formatting not implemented for this output format"));
421     }
422 }
423
424 void SynopsisFormatter::finish()
425 {
426     File &file = context_.outputFile();
427     file.writeLine();
428     if (context_.outputFormat() == eHelpOutputFormat_Html)
429     {
430         file.writeLine("</pre>");
431     }
432     file.writeLine();
433 }
434
435 void SynopsisFormatter::formatOption(const OptionInfo &option)
436 {
437     std::string name, value;
438     formatOptionNameAndValue(option, &name, &value);
439     std::string fullOptionText(" [-" + name);
440     if (!value.empty())
441     {
442         fullOptionText.append(" ");
443         fullOptionText.append(value);
444     }
445     fullOptionText.append("]");
446     const int   totalLength = fullOptionText.size();
447
448     if (context_.outputFormat() == eHelpOutputFormat_Html)
449     {
450         value = replaceAll(value, "<", "&lt;");
451         value = replaceAll(value, ">", "&gt;");
452     }
453
454     File &file = context_.outputFile();
455     currentLength_ += totalLength;
456     if (currentLength_ >= lineLength_)
457     {
458         file.writeString(formatString("\n%*c", indent_ - 1, ' '));
459         currentLength_ = indent_ - 1 + totalLength;
460     }
461     file.writeString(fullOptionText);
462 }
463
464 /********************************************************************
465  * OptionsListFormatter
466  */
467
468 /*! \brief
469  * Formatter implementation for help export.
470  */
471 class OptionsListFormatter : public OptionsFormatterInterface
472 {
473     public:
474         //! Creates a helper object for formatting options.
475         OptionsListFormatter(const HelpWriterContext   &context,
476                              const CommonFormatterData &common,
477                              const char                *title);
478
479         //! Initiates a new section in the options list.
480         void startSection(const char *header)
481         {
482             header_     = header;
483             bDidOutput_ = false;
484         }
485         //! Finishes a section in the options list.
486         void finishSection()
487         {
488             if (bDidOutput_)
489             {
490                 context_.writeOptionListEnd();
491                 context_.outputFile().writeLine();
492             }
493         }
494
495         virtual void formatOption(const OptionInfo &option);
496
497     private:
498         void writeSectionStartIfNecessary()
499         {
500             if (title_ != NULL)
501             {
502                 context_.writeTitle(title_);
503                 title_ = NULL;
504             }
505             if (!bDidOutput_)
506             {
507                 if (header_ != NULL)
508                 {
509                     context_.writeTextBlock(header_);
510                 }
511                 context_.writeOptionListStart();
512             }
513             bDidOutput_ = true;
514         }
515
516         const HelpWriterContext               &context_;
517         const CommonFormatterData             &common_;
518         boost::scoped_ptr<TextTableFormatter>  consoleFormatter_;
519         const char                            *title_;
520         const char                            *header_;
521         bool                                   bDidOutput_;
522
523         GMX_DISALLOW_COPY_AND_ASSIGN(OptionsListFormatter);
524 };
525
526 OptionsListFormatter::OptionsListFormatter(
527         const HelpWriterContext   &context,
528         const CommonFormatterData &common,
529         const char                *title)
530     : context_(context), common_(common),
531       title_(title), header_(NULL), bDidOutput_(false)
532 {
533     if (context.outputFormat() == eHelpOutputFormat_Console)
534     {
535         consoleFormatter_.reset(new TextTableFormatter());
536         consoleFormatter_->setFirstColumnIndent(1);
537         consoleFormatter_->setFoldLastColumnToNextLine(4);
538         consoleFormatter_->addColumn(NULL, 6, false);
539         consoleFormatter_->addColumn(NULL, 8, false);
540         consoleFormatter_->addColumn(NULL, 10, false);
541         consoleFormatter_->addColumn(NULL, 50, true);
542     }
543 }
544
545 void OptionsListFormatter::formatOption(const OptionInfo &option)
546 {
547     writeSectionStartIfNecessary();
548     std::string name, value;
549     formatOptionNameAndValue(option, &name, &value);
550     std::string defaultValue(defaultOptionValue(option));
551     std::string info;
552     if (!defaultValue.empty())
553     {
554         info = "(" + defaultValue + ")";
555     }
556     const FileNameOptionInfo *fileOption = option.toType<FileNameOptionInfo>();
557     if (fileOption != NULL)
558     {
559         const bool bAbbrev = (context_.outputFormat() == eHelpOutputFormat_Console);
560         if (!info.empty())
561         {
562             info.append(" ");
563         }
564         info.append("(");
565         info.append(fileOptionFlagsAsString(*fileOption, bAbbrev));
566         info.append(")");
567     }
568     std::string description(descriptionWithOptionDetails(common_, option));
569     if (context_.outputFormat() == eHelpOutputFormat_Console)
570     {
571         consoleFormatter_->clear();
572         consoleFormatter_->addColumnLine(0, "-" + name);
573         consoleFormatter_->addColumnLine(1, value);
574         if (!info.empty())
575         {
576             consoleFormatter_->addColumnLine(2, info);
577         }
578         consoleFormatter_->addColumnHelpTextBlock(3, context_, description);
579         context_.outputFile().writeString(consoleFormatter_->formatRow());
580     }
581     else
582     {
583         if (!info.empty())
584         {
585             value.append(" ");
586             value.append(info);
587         }
588         context_.writeOptionItem("-" + name, value, description);
589     }
590 }
591
592 //! \}
593
594 }   // namespace
595
596 /********************************************************************
597  * CommandLineHelpWriter::Impl
598  */
599
600 /*! \internal \brief
601  * Private implementation class for CommandLineHelpWriter.
602  *
603  * \ingroup module_commandline
604  */
605 class CommandLineHelpWriter::Impl
606 {
607     public:
608         //! Sets the Options object to use for generating help.
609         explicit Impl(const Options &options);
610
611         //! Format the list of known issues.
612         void formatBugs(const HelpWriterContext &context);
613
614         //! Options object to use for generating help.
615         const Options               &options_;
616         //! List of bugs/knows issues.
617         ConstArrayRef<const char *>  bugs_;
618         //! Time unit to show in descriptions.
619         std::string                  timeUnit_;
620         //! Whether to write descriptions to output.
621         bool                         bShowDescriptions_;
622 };
623
624 CommandLineHelpWriter::Impl::Impl(const Options &options)
625     : options_(options), timeUnit_(TimeUnitManager().timeUnitAsString()),
626       bShowDescriptions_(false)
627 {
628 }
629
630 void CommandLineHelpWriter::Impl::formatBugs(const HelpWriterContext &context)
631 {
632     if (bugs_.empty())
633     {
634         return;
635     }
636     context.writeTitle("Known Issues");
637     if (context.outputFormat() != eHelpOutputFormat_Console)
638     {
639         context.writeTextBlock("[UL]");
640     }
641     ConstArrayRef<const char *>::const_iterator i;
642     for (i = bugs_.begin(); i != bugs_.end(); ++i)
643     {
644         const char *const bug = *i;
645         // TODO: The context should be able to do this also for console output, but
646         // that requires a lot more elaborate parser for the markup.
647         if (context.outputFormat() == eHelpOutputFormat_Console)
648         {
649             TextLineWrapperSettings settings;
650             settings.setIndent(2);
651             settings.setFirstLineIndent(0);
652             settings.setLineLength(78);
653             context.outputFile().writeLine(
654                     context.substituteMarkupAndWrapToString(
655                             settings, formatString("* %s", bug)));
656         }
657         else
658         {
659             context.writeTextBlock(formatString("[LI]%s", bug));
660         }
661     }
662     if (context.outputFormat() != eHelpOutputFormat_Console)
663     {
664         context.writeTextBlock("[ul]");
665     }
666 }
667
668
669 /********************************************************************
670  * CommandLineHelpWriter
671  */
672
673 CommandLineHelpWriter::CommandLineHelpWriter(const Options &options)
674     : impl_(new Impl(options))
675 {
676 }
677
678 CommandLineHelpWriter::~CommandLineHelpWriter()
679 {
680 }
681
682 CommandLineHelpWriter &
683 CommandLineHelpWriter::setShowDescriptions(bool bSet)
684 {
685     impl_->bShowDescriptions_ = bSet;
686     return *this;
687 }
688
689 CommandLineHelpWriter &
690 CommandLineHelpWriter::setTimeUnitString(const char *timeUnit)
691 {
692     impl_->timeUnit_ = timeUnit;
693     return *this;
694 }
695
696 CommandLineHelpWriter &
697 CommandLineHelpWriter::setKnownIssues(const ConstArrayRef<const char *> &bugs)
698 {
699     impl_->bugs_ = bugs;
700     return *this;
701 }
702
703 void CommandLineHelpWriter::writeHelp(const CommandLineHelpContext &context)
704 {
705     if (context.isCompletionExport())
706     {
707         context.shellCompletionWriter().writeModuleCompletions(
708                 context.moduleDisplayName(), impl_->options_);
709         return;
710     }
711     const HelpWriterContext &writerContext = context.writerContext();
712     OptionsFilter            filter;
713     filter.setShowHidden(context.showHidden());
714
715     {
716         writerContext.writeTitle("Synopsis");
717         SynopsisFormatter synopsisFormatter(writerContext);
718         synopsisFormatter.start(context.moduleDisplayName());
719         filter.formatSelected(OptionsFilter::eSelectFileOptions,
720                               &synopsisFormatter, impl_->options_);
721         filter.formatSelected(OptionsFilter::eSelectOtherOptions,
722                               &synopsisFormatter, impl_->options_);
723         synopsisFormatter.finish();
724     }
725
726     if (impl_->bShowDescriptions_)
727     {
728         DescriptionsFormatter descriptionFormatter(writerContext);
729         descriptionFormatter.format(impl_->options_, "Description");
730     }
731     CommonFormatterData    common(impl_->timeUnit_.c_str());
732     OptionsListFormatter   formatter(writerContext, common, "Options");
733     formatter.startSection("Options to specify input and output files:[PAR]");
734     filter.formatSelected(OptionsFilter::eSelectFileOptions,
735                           &formatter, impl_->options_);
736     formatter.finishSection();
737     formatter.startSection("Other options:[PAR]");
738     filter.formatSelected(OptionsFilter::eSelectOtherOptions,
739                           &formatter, impl_->options_);
740     formatter.finishSection();
741
742     impl_->formatBugs(writerContext);
743 }
744
745 } // namespace gmx