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