Framework for help export from wrapper binary.
[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, by the GROMACS development team, led by
5  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6  * others, as listed in the AUTHORS file in the top-level source
7  * 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 "gromacs/commandline/cmdlinehelpcontext.h"
47 #include "gromacs/onlinehelp/helpformat.h"
48 #include "gromacs/onlinehelp/helpwritercontext.h"
49 #include "gromacs/options/basicoptions.h"
50 #include "gromacs/options/filenameoption.h"
51 #include "gromacs/options/options.h"
52 #include "gromacs/options/optionsvisitor.h"
53 #include "gromacs/options/timeunitmanager.h"
54 #include "gromacs/selection/selectionfileoption.h"
55 #include "gromacs/selection/selectionoption.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 /********************************************************************
67  * OptionsFormatterInterface
68  */
69
70 /*! \internal \brief
71  * Interface for output format specific formatting of options.
72  *
73  * \see OptionsFilter
74  *
75  * \ingroup module_commandline
76  */
77 class OptionsFormatterInterface
78 {
79     public:
80         virtual ~OptionsFormatterInterface() {}
81
82         //! Formats the description text block for a section.
83         virtual void formatDescription(const HelpWriterContext &context,
84                                        const Options           &section) = 0;
85         //! Formats a single file option.
86         virtual void formatFileOption(const HelpWriterContext  &context,
87                                       const FileNameOptionInfo &option) = 0;
88         //! Formats a single non-file, non-selection option.
89         virtual void formatOption(const HelpWriterContext &context,
90                                   const OptionInfo        &option) = 0;
91         //! Formats a single selection option.
92         virtual void formatSelectionOption(const HelpWriterContext &context,
93                                            const OptionInfo        &option) = 0;
94 };
95
96 /********************************************************************
97  * OptionsFilter
98  */
99
100 /*! \internal \brief
101  * Output format independent processing of options.
102  *
103  * Together with code in CommandLineHelpWriter::writeHelp(), this class
104  * implements the common logic for writing out the help.
105  * An object implementing the OptionsFormatterInterface must be provided to the
106  * constructor, and does the actual formatting that is specific to the output
107  * format.
108  *
109  * \ingroup module_commandline
110  */
111 class OptionsFilter : public OptionsVisitor
112 {
113     public:
114         //! Specifies the type of output that formatSelected() produces.
115         enum FilterType
116         {
117             eSelectDescriptions,
118             eSelectFileOptions,
119             eSelectSelectionOptions,
120             eSelectOtherOptions
121         };
122
123         /*! \brief
124          * Creates a new filtering object.
125          *
126          * \param[in] context   Help context to use to write the help.
127          * \param[in] formatter Output format specific formatter.
128          *
129          * Does not throw.
130          */
131         OptionsFilter(const HelpWriterContext   &context,
132                       OptionsFormatterInterface *formatter)
133             : context_(context), formatter_(*formatter),
134               filterType_(eSelectOtherOptions), bShowHidden_(false),
135               bDidOutput_(false)
136         {
137         }
138
139         //! Sets whether hidden options will be shown.
140         void setShowHidden(bool bShowHidden)
141         {
142             bShowHidden_ = bShowHidden;
143         }
144
145         //! Formats selected options using the formatter.
146         void formatSelected(FilterType type, const Options &options);
147
148         virtual void visitSubSection(const Options &section);
149         virtual void visitOption(const OptionInfo &option);
150
151     private:
152         const HelpWriterContext        &context_;
153         OptionsFormatterInterface      &formatter_;
154         FilterType                      filterType_;
155         bool                            bShowHidden_;
156         bool                            bDidOutput_;
157 };
158
159 void OptionsFilter::formatSelected(FilterType type, const Options &options)
160 {
161     filterType_ = type;
162     bDidOutput_ = false;
163     visitSubSection(options);
164     if (bDidOutput_)
165     {
166         context_.outputFile().writeLine();
167     }
168 }
169
170 void OptionsFilter::visitSubSection(const Options &section)
171 {
172     if (filterType_ == eSelectDescriptions)
173     {
174         if (bDidOutput_)
175         {
176             context_.outputFile().writeLine();
177         }
178         formatter_.formatDescription(context_, section);
179         bDidOutput_ = true;
180     }
181
182     OptionsIterator iterator(section);
183     iterator.acceptSubSections(this);
184     iterator.acceptOptions(this);
185 }
186
187 void OptionsFilter::visitOption(const OptionInfo &option)
188 {
189     if (!bShowHidden_ && option.isHidden())
190     {
191         return;
192     }
193     if (option.isType<FileNameOptionInfo>())
194     {
195         if (filterType_ == eSelectFileOptions)
196         {
197             formatter_.formatFileOption(context_,
198                                         *option.toType<FileNameOptionInfo>());
199             bDidOutput_ = true;
200         }
201         return;
202     }
203     if (option.isType<SelectionFileOptionInfo>()
204         || option.isType<SelectionOptionInfo>())
205     {
206         if (filterType_ == eSelectSelectionOptions)
207         {
208             formatter_.formatSelectionOption(context_, option);
209             bDidOutput_ = true;
210         }
211         return;
212     }
213     if (filterType_ == eSelectOtherOptions)
214     {
215         formatter_.formatOption(context_, option);
216         bDidOutput_ = true;
217         return;
218     }
219 }
220
221 /********************************************************************
222  * CommonFormatterData
223  */
224
225 class CommonFormatterData
226 {
227     public:
228         explicit CommonFormatterData(const char *timeUnit)
229             : timeUnit(timeUnit)
230         {
231         }
232
233         const char             *timeUnit;
234 };
235
236 /********************************************************************
237  * OptionsConsoleFormatter
238  */
239
240 /*! \internal \brief
241  * Formatter implementation for console help.
242  *
243  * \ingroup module_commandline
244  */
245 class OptionsConsoleFormatter : public OptionsFormatterInterface
246 {
247     public:
248         //! Creates a helper object for formatting options help for console.
249         explicit OptionsConsoleFormatter(const CommonFormatterData &common);
250
251         virtual void formatDescription(const HelpWriterContext &context,
252                                        const Options           &section);
253         virtual void formatFileOption(const HelpWriterContext  &context,
254                                       const FileNameOptionInfo &option);
255         virtual void formatOption(const HelpWriterContext &context,
256                                   const OptionInfo        &option);
257         virtual void formatSelectionOption(const HelpWriterContext &context,
258                                            const OptionInfo        &option);
259
260     private:
261         const CommonFormatterData &common_;
262         TextTableFormatter         fileOptionFormatter_;
263         TextTableFormatter         genericOptionFormatter_;
264         TextTableFormatter         selectionOptionFormatter_;
265 };
266
267 OptionsConsoleFormatter::OptionsConsoleFormatter(const CommonFormatterData &common)
268     : common_(common)
269 {
270     fileOptionFormatter_.addColumn("Option",      6, false);
271     fileOptionFormatter_.addColumn("Filename",    12, false);
272     fileOptionFormatter_.addColumn("Type",        12, false);
273     fileOptionFormatter_.addColumn("Description", 45, true);
274
275     genericOptionFormatter_.addColumn("Option",      12, false);
276     genericOptionFormatter_.addColumn("Type",         6, false);
277     genericOptionFormatter_.addColumn("Value",        6, false);
278     genericOptionFormatter_.addColumn("Description", 51, true);
279
280     selectionOptionFormatter_.addColumn("Selection",   10, false);
281     selectionOptionFormatter_.addColumn("Description", 67, true);
282 }
283
284 void OptionsConsoleFormatter::formatDescription(
285         const HelpWriterContext &context, const Options &section)
286 {
287     if (!section.description().empty())
288     {
289         File              &file  = context.outputFile();
290         const std::string &title = section.title();
291         if (!title.empty())
292         {
293             file.writeLine(title);
294             file.writeLine();
295         }
296         context.writeTextBlock(section.description());
297     }
298 }
299
300 void OptionsConsoleFormatter::formatFileOption(
301         const HelpWriterContext &context, const FileNameOptionInfo &option)
302 {
303     int firstShortValue = 0;  // The first value after which the type fits.
304     int firstLongValue  = -1; // First value that overlaps description column.
305     int lastLongValue   = -1; // Last value like the above.
306
307     // Get the values to write and check where text overflows the columns.
308     fileOptionFormatter_.clear();
309     std::string name(formatString("-%s", option.name().c_str()));
310     fileOptionFormatter_.addColumnLine(0, name);
311     for (int i = 0; i < option.valueCount() || i == 0; ++i)
312     {
313         std::string value;
314         if (option.valueCount() == 0
315             || (option.valueCount() == 1 && option.formatValue(0).empty()))
316         {
317             value = option.formatDefaultValueIfSet();
318         }
319         else
320         {
321             value = option.formatValue(i);
322         }
323         fileOptionFormatter_.addColumnLine(1, value);
324         if (value.length() > 12U && i == firstShortValue)
325         {
326             firstShortValue = i + 1;
327         }
328         if (value.length() > 25U)
329         {
330             if (firstLongValue == -1)
331             {
332                 firstLongValue = i;
333             }
334             lastLongValue = i;
335         }
336     }
337     std::string type;
338     if (option.isInputOutputFile())
339     {
340         type = "In/Out";
341     }
342     else if (option.isInputFile())
343     {
344         type = "Input";
345     }
346     else if (option.isOutputFile())
347     {
348         type = "Output";
349     }
350     if (!option.isRequired())
351     {
352         type += ", Opt.";
353     }
354     if (option.isLibraryFile())
355     {
356         type += ", Lib.";
357     }
358     bool bLongType = (type.length() > 12U);
359     fileOptionFormatter_.addColumnLine(2, type);
360     fileOptionFormatter_.addColumnLine(3, context.substituteMarkup(option.description()));
361
362     // Compute layout.
363     if (name.length() > 6U || firstShortValue > 0)
364     {
365         fileOptionFormatter_.setColumnFirstLineOffset(1, 1);
366         // Assume that the name is <20 chars, so that the type fits
367         if (firstLongValue >= 0)
368         {
369             ++firstLongValue;
370             ++lastLongValue;
371         }
372     }
373     int firstDescriptionLine = 0;
374     if (bLongType)
375     {
376         firstDescriptionLine = 1;
377     }
378     fileOptionFormatter_.setColumnFirstLineOffset(3, firstDescriptionLine);
379     if (firstLongValue >= 0
380         && fileOptionFormatter_.lastColumnLine(3) >= firstLongValue)
381     {
382         firstDescriptionLine = lastLongValue + 1;
383         fileOptionFormatter_.setColumnFirstLineOffset(3, firstDescriptionLine);
384     }
385
386     // Do the formatting.
387     context.outputFile().writeString(fileOptionFormatter_.formatRow());
388 }
389
390 void OptionsConsoleFormatter::formatOption(
391         const HelpWriterContext &context, const OptionInfo &option)
392 {
393     genericOptionFormatter_.clear();
394     bool        bIsBool = option.isType<BooleanOptionInfo>();
395     std::string name(formatString("-%s%s", bIsBool ? "[no]" : "",
396                                   option.name().c_str()));
397     genericOptionFormatter_.addColumnLine(0, name);
398     genericOptionFormatter_.addColumnLine(1, option.type());
399     if (name.length() > 12U)
400     {
401         genericOptionFormatter_.setColumnFirstLineOffset(1, 1);
402     }
403
404     // TODO: Better handling of multiple long values
405     std::string values;
406     for (int i = 0; i < option.valueCount(); ++i)
407     {
408         if (i != 0)
409         {
410             values.append(" ");
411         }
412         values.append(option.formatValue(i));
413     }
414     genericOptionFormatter_.addColumnLine(2, values);
415
416     std::string             description(context.substituteMarkup(option.description()));
417     const DoubleOptionInfo *doubleOption = option.toType<DoubleOptionInfo>();
418     if (doubleOption != NULL && doubleOption->isTime())
419     {
420         description = replaceAll(description, "%t", common_.timeUnit);
421     }
422     const StringOptionInfo *stringOption = option.toType<StringOptionInfo>();
423     if (stringOption != NULL && stringOption->isEnumerated())
424     {
425         const std::vector<std::string> &allowedValues
426             = stringOption->allowedValues();
427         description.append(": ");
428         for (size_t i = 0; i < allowedValues.size(); ++i)
429         {
430             if (i > 0)
431             {
432                 description.append(i + 1 < allowedValues.size()
433                                    ? ", " : ", or ");
434             }
435             description.append(allowedValues[i]);
436         }
437     }
438     genericOptionFormatter_.addColumnLine(3, description);
439     if (values.length() > 6U)
440     {
441         genericOptionFormatter_.setColumnFirstLineOffset(3, 1);
442     }
443
444     context.outputFile().writeString(genericOptionFormatter_.formatRow());
445 }
446
447 void OptionsConsoleFormatter::formatSelectionOption(
448         const HelpWriterContext &context, const OptionInfo &option)
449 {
450     File &file = context.outputFile();
451
452     selectionOptionFormatter_.clear();
453     std::string name(formatString("-%s", option.name().c_str()));
454     selectionOptionFormatter_.addColumnLine(0, name);
455     selectionOptionFormatter_.addColumnLine(1, context.substituteMarkup(option.description()));
456     file.writeString(selectionOptionFormatter_.formatRow());
457
458     TextLineWrapper wrapper;
459     wrapper.settings().setLineLength(77);
460     wrapper.settings().setFirstLineIndent(4);
461     wrapper.settings().setIndent(8);
462     wrapper.settings().setContinuationChar('\\');
463     // TODO: What to do with selection variables?
464     // They are not printed as values for any option.
465     for (int i = 0; i < option.valueCount(); ++i)
466     {
467         std::string value(option.formatValue(i));
468         file.writeLine(wrapper.wrapToString(value));
469     }
470 }
471
472 }   // namespace
473
474 /********************************************************************
475  * CommandLineHelpWriter::Impl
476  */
477
478 /*! \internal \brief
479  * Private implementation class for CommandLineHelpWriter.
480  *
481  * \ingroup module_commandline
482  */
483 class CommandLineHelpWriter::Impl
484 {
485     public:
486         //! Sets the Options object to use for generating help.
487         explicit Impl(const Options &options);
488
489         //! Options object to use for generating help.
490         const Options          &options_;
491         //! Time unit to show in descriptions.
492         std::string             timeUnit_;
493         //! Whether to write descriptions to output.
494         bool                    bShowDescriptions_;
495         //! Whether to write hidden options to output.
496         bool                    bShowHidden_;
497 };
498
499 CommandLineHelpWriter::Impl::Impl(const Options &options)
500     : options_(options), timeUnit_(TimeUnitManager().timeUnitAsString()),
501       bShowDescriptions_(false), bShowHidden_(false)
502 {
503 }
504
505 /********************************************************************
506  * CommandLineHelpWriter
507  */
508
509 CommandLineHelpWriter::CommandLineHelpWriter(const Options &options)
510     : impl_(new Impl(options))
511 {
512 }
513
514 CommandLineHelpWriter::~CommandLineHelpWriter()
515 {
516 }
517
518 CommandLineHelpWriter &CommandLineHelpWriter::setShowHidden(bool bSet)
519 {
520     impl_->bShowHidden_ = bSet;
521     return *this;
522 }
523
524 CommandLineHelpWriter &CommandLineHelpWriter::setShowDescriptions(bool bSet)
525 {
526     impl_->bShowDescriptions_ = bSet;
527     return *this;
528 }
529
530 CommandLineHelpWriter &CommandLineHelpWriter::setTimeUnitString(const char *timeUnit)
531 {
532     impl_->timeUnit_ = timeUnit;
533     return *this;
534 }
535
536 void CommandLineHelpWriter::writeHelp(const CommandLineHelpContext &context)
537 {
538     boost::scoped_ptr<OptionsFormatterInterface> formatter;
539     const HelpWriterContext                     &writerContext = context.writerContext();
540     CommonFormatterData                          common(impl_->timeUnit_.c_str());
541     switch (writerContext.outputFormat())
542     {
543         case eHelpOutputFormat_Console:
544             formatter.reset(new OptionsConsoleFormatter(common));
545             break;
546         default:
547             // TODO: Implement once the situation with Redmine issue #969 is
548             // more clear.
549             GMX_THROW(NotImplementedError(
550                               "Command-line help is not implemented for this output format"));
551     }
552     OptionsFilter filter(writerContext, formatter.get());
553     filter.setShowHidden(impl_->bShowHidden_);
554
555     if (impl_->bShowDescriptions_)
556     {
557         File &file = writerContext.outputFile();
558         file.writeLine("DESCRIPTION");
559         file.writeLine("-----------");
560         file.writeLine();
561         filter.formatSelected(OptionsFilter::eSelectDescriptions,
562                               impl_->options_);
563     }
564     filter.formatSelected(OptionsFilter::eSelectFileOptions,
565                           impl_->options_);
566     filter.formatSelected(OptionsFilter::eSelectOtherOptions,
567                           impl_->options_);
568     filter.formatSelected(OptionsFilter::eSelectSelectionOptions,
569                           impl_->options_);
570 }
571
572 } // namespace gmx