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