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