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