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