Merge release-4-6 into master
[alexxy/gromacs.git] / src / gromacs / commandline / cmdlinehelpwriter.cpp
1 /*
2  *
3  *                This source code is part of
4  *
5  *                 G   R   O   M   A   C   S
6  *
7  *          GROningen MAchine for Chemical Simulations
8  *
9  * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
10  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
11  * Copyright (c) 2001-2009, The GROMACS development team,
12  * check out http://www.gromacs.org for more information.
13
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License
16  * as published by the Free Software Foundation; either version 2
17  * of the License, or (at your option) any later version.
18  *
19  * If you want to redistribute modifications, please consider that
20  * scientific software is very special. Version control is crucial -
21  * bugs must be traceable. We will be happy to consider code for
22  * inclusion in the official distribution, but derived work must not
23  * be called official GROMACS. Details are found in the README & COPYING
24  * files - if they are missing, get the official version at www.gromacs.org.
25  *
26  * To help us fund GROMACS development, we humbly ask that you cite
27  * the papers on the package - you can find them in the top README file.
28  *
29  * For more info, check our website at http://www.gromacs.org
30  */
31 /*! \internal \file
32  * \brief
33  * Implements gmx::CommandLineHelpWriter.
34  *
35  * \author Teemu Murtola <teemu.murtola@cbr.su.se>
36  * \ingroup module_commandline
37  */
38 #include "cmdlinehelpwriter.h"
39
40 #include <string>
41
42 #include "gromacs/onlinehelp/helpformat.h"
43 #include "gromacs/onlinehelp/helpwritercontext.h"
44 #include "gromacs/options/basicoptions.h"
45 #include "gromacs/options/filenameoption.h"
46 #include "gromacs/options/options.h"
47 #include "gromacs/options/optionsvisitor.h"
48 #include "gromacs/options/timeunitmanager.h"
49 #include "gromacs/selection/selectionfileoption.h"
50 #include "gromacs/selection/selectionoption.h"
51 #include "gromacs/utility/exceptions.h"
52 #include "gromacs/utility/file.h"
53 #include "gromacs/utility/stringutil.h"
54
55 namespace gmx
56 {
57
58 namespace
59 {
60
61 /********************************************************************
62  * OptionsFormatterInterface
63  */
64
65 /*! \internal \brief
66  * Interface for output format specific formatting of options.
67  *
68  * \see OptionsFilter
69  *
70  * \ingroup module_commandline
71  */
72 class OptionsFormatterInterface
73 {
74     public:
75         virtual ~OptionsFormatterInterface() {}
76
77         //! Formats the description text block for a section.
78         virtual void formatDescription(const HelpWriterContext &context,
79                                        const Options &section) = 0;
80         //! Formats a single file option.
81         virtual void formatFileOption(const HelpWriterContext &context,
82                                       const FileNameOptionInfo &option) = 0;
83         //! Formats a single non-file, non-selection option.
84         virtual void formatOption(const HelpWriterContext &context,
85                                   const OptionInfo &option) = 0;
86         //! Formats a single selection option.
87         virtual void formatSelectionOption(const HelpWriterContext &context,
88                                            const OptionInfo &option) = 0;
89 };
90
91 /********************************************************************
92  * OptionsFilter
93  */
94
95 /*! \internal \brief
96  * Output format independent processing of options.
97  *
98  * Together with code in CommandLineHelpWriter::writeHelp(), this class
99  * implements the common logic for writing out the help.
100  * An object implementing the OptionsFormatterInterface must be provided to the
101  * constructor, and does the actual formatting that is specific to the output
102  * format.
103  *
104  * \ingroup module_commandline
105  */
106 class OptionsFilter : public OptionsVisitor
107 {
108     public:
109         //! Specifies the type of output that formatSelected() produces.
110         enum FilterType
111         {
112             eSelectDescriptions,
113             eSelectFileOptions,
114             eSelectSelectionOptions,
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               filterType_(eSelectOtherOptions), bShowHidden_(false),
130               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
143         virtual void visitSubSection(const Options &section);
144         virtual void visitOption(const OptionInfo &option);
145
146     private:
147         const HelpWriterContext        &context_;
148         OptionsFormatterInterface      &formatter_;
149         FilterType                      filterType_;
150         bool                            bShowHidden_;
151         bool                            bDidOutput_;
152 };
153
154 void OptionsFilter::formatSelected(FilterType type, const Options &options)
155 {
156     filterType_ = type;
157     bDidOutput_ = false;
158     visitSubSection(options);
159     if (bDidOutput_)
160     {
161         context_.outputFile().writeLine();
162     }
163 }
164
165 void OptionsFilter::visitSubSection(const Options &section)
166 {
167     if (filterType_ == eSelectDescriptions)
168     {
169         if (bDidOutput_)
170         {
171             context_.outputFile().writeLine();
172         }
173         formatter_.formatDescription(context_, section);
174         bDidOutput_ = true;
175     }
176
177     OptionsIterator iterator(section);
178     iterator.acceptSubSections(this);
179     iterator.acceptOptions(this);
180 }
181
182 void OptionsFilter::visitOption(const OptionInfo &option)
183 {
184     if (!bShowHidden_ && option.isHidden())
185     {
186         return;
187     }
188     if (option.isType<FileNameOptionInfo>())
189     {
190         if (filterType_ == eSelectFileOptions)
191         {
192             formatter_.formatFileOption(context_,
193                                         *option.toType<FileNameOptionInfo>());
194             bDidOutput_ = true;
195         }
196         return;
197     }
198     if (option.isType<SelectionFileOptionInfo>()
199         || option.isType<SelectionOptionInfo>())
200     {
201         if (filterType_ == eSelectSelectionOptions)
202         {
203             formatter_.formatSelectionOption(context_, option);
204             bDidOutput_ = true;
205         }
206         return;
207     }
208     if (filterType_ == eSelectOtherOptions)
209     {
210         formatter_.formatOption(context_, option);
211         bDidOutput_ = true;
212         return;
213     }
214 }
215
216 /********************************************************************
217  * CommonFormatterData
218  */
219
220 class CommonFormatterData
221 {
222     public:
223         explicit CommonFormatterData(const char *timeUnit)
224             : timeUnit(timeUnit)
225         {
226         }
227
228         const char             *timeUnit;
229 };
230
231 /********************************************************************
232  * OptionsConsoleFormatter
233  */
234
235 /*! \internal \brief
236  * Formatter implementation for console help.
237  *
238  * \ingroup module_commandline
239  */
240 class OptionsConsoleFormatter : public OptionsFormatterInterface
241 {
242     public:
243         //! Creates a helper object for formatting options help for console.
244         explicit OptionsConsoleFormatter(const CommonFormatterData &common);
245
246         virtual void formatDescription(const HelpWriterContext &context,
247                                        const Options &section);
248         virtual void formatFileOption(const HelpWriterContext &context,
249                                       const FileNameOptionInfo &option);
250         virtual void formatOption(const HelpWriterContext &context,
251                                   const OptionInfo &option);
252         virtual void formatSelectionOption(const HelpWriterContext &context,
253                                            const OptionInfo &option);
254
255     private:
256         const CommonFormatterData &common_;
257         TextTableFormatter       fileOptionFormatter_;
258         TextTableFormatter       genericOptionFormatter_;
259         TextTableFormatter       selectionOptionFormatter_;
260 };
261
262 OptionsConsoleFormatter::OptionsConsoleFormatter(const CommonFormatterData &common)
263     : common_(common)
264 {
265     fileOptionFormatter_.addColumn("Option",      6, false);
266     fileOptionFormatter_.addColumn("Filename",    12, false);
267     fileOptionFormatter_.addColumn("Type",        12, false);
268     fileOptionFormatter_.addColumn("Description", 45, true);
269
270     genericOptionFormatter_.addColumn("Option",      12, false);
271     genericOptionFormatter_.addColumn("Type",         6, false);
272     genericOptionFormatter_.addColumn("Value",        6, false);
273     genericOptionFormatter_.addColumn("Description", 51, true);
274
275     selectionOptionFormatter_.addColumn("Selection",   10, false);
276     selectionOptionFormatter_.addColumn("Description", 67, true);
277 }
278
279 void OptionsConsoleFormatter::formatDescription(
280         const HelpWriterContext &context, const Options &section)
281 {
282     if (!section.description().empty())
283     {
284         File &file = context.outputFile();
285         const std::string &title = section.title();
286         if (!title.empty())
287         {
288             file.writeLine(title);
289             file.writeLine();
290         }
291         context.writeTextBlock(section.description());
292     }
293 }
294
295 void OptionsConsoleFormatter::formatFileOption(
296         const HelpWriterContext &context, const FileNameOptionInfo &option)
297 {
298     int firstShortValue = 0; // The first value after which the type fits.
299     int firstLongValue = -1; // First value that overlaps description column.
300     int lastLongValue = -1;  // Last value like the above.
301
302     // Get the values to write and check where text overflows the columns.
303     fileOptionFormatter_.clear();
304     std::string name(formatString("-%s", option.name().c_str()));
305     fileOptionFormatter_.addColumnLine(0, name);
306     for (int i = 0; i < option.valueCount() || i == 0; ++i)
307     {
308         std::string value;
309         if (option.valueCount() == 0
310             || (option.valueCount() == 1 && option.formatValue(0).empty()))
311         {
312             value = option.formatDefaultValueIfSet();
313         }
314         else
315         {
316             value = option.formatValue(i);
317         }
318         fileOptionFormatter_.addColumnLine(1, value);
319         if (value.length() > 12U && i == firstShortValue)
320         {
321             firstShortValue = i + 1;
322         }
323         if (value.length() > 25U)
324         {
325             if (firstLongValue == -1)
326             {
327                 firstLongValue = i;
328             }
329             lastLongValue = i;
330         }
331     }
332     std::string type;
333     if (option.isInputOutputFile())
334     {
335         type = "In/Out";
336     }
337     else if (option.isInputFile())
338     {
339         type = "Input";
340     }
341     else if (option.isOutputFile())
342     {
343         type = "Output";
344     }
345     if (!option.isRequired())
346     {
347         type += ", Opt.";
348     }
349     if (option.isLibraryFile())
350     {
351         type += ", Lib.";
352     }
353     bool bLongType = (type.length() > 12U);
354     fileOptionFormatter_.addColumnLine(2, type);
355     fileOptionFormatter_.addColumnLine(3, context.substituteMarkup(option.description()));
356
357     // Compute layout.
358     if (name.length() > 6U || firstShortValue > 0)
359     {
360         fileOptionFormatter_.setColumnFirstLineOffset(1, 1);
361         // Assume that the name is <20 chars, so that the type fits
362         if (firstLongValue >= 0)
363         {
364             ++firstLongValue;
365             ++lastLongValue;
366         }
367     }
368     int firstDescriptionLine = 0;
369     if (bLongType)
370     {
371         firstDescriptionLine = 1;
372     }
373     fileOptionFormatter_.setColumnFirstLineOffset(3, firstDescriptionLine);
374     if (firstLongValue >= 0
375         && fileOptionFormatter_.lastColumnLine(3) >= firstLongValue)
376     {
377         firstDescriptionLine = lastLongValue + 1;
378         fileOptionFormatter_.setColumnFirstLineOffset(3, firstDescriptionLine);
379     }
380
381     // Do the formatting.
382     context.outputFile().writeString(fileOptionFormatter_.formatRow());
383 }
384
385 void OptionsConsoleFormatter::formatOption(
386         const HelpWriterContext &context, const OptionInfo &option)
387 {
388     genericOptionFormatter_.clear();
389     bool bIsBool = option.isType<BooleanOptionInfo>();
390     std::string name(formatString("-%s%s", bIsBool ? "[no]" : "",
391                                            option.name().c_str()));
392     genericOptionFormatter_.addColumnLine(0, name);
393     genericOptionFormatter_.addColumnLine(1, option.type());
394     if (name.length() > 12U)
395     {
396         genericOptionFormatter_.setColumnFirstLineOffset(1, 1);
397     }
398     // TODO: Better handling of multiple long values
399     std::string values;
400     for (int i = 0; i < option.valueCount(); ++i)
401     {
402         if (i != 0)
403         {
404             values.append(" ");
405         }
406         values.append(option.formatValue(i));
407     }
408     genericOptionFormatter_.addColumnLine(2, values);
409     std::string description(context.substituteMarkup(option.description()));
410     const DoubleOptionInfo *doubleOption = option.toType<DoubleOptionInfo>();
411     if (doubleOption != NULL && doubleOption->isTime())
412     {
413         description = replaceAll(description, "%t", common_.timeUnit);
414     }
415     genericOptionFormatter_.addColumnLine(3, description);
416     if (values.length() > 6U)
417     {
418         genericOptionFormatter_.setColumnFirstLineOffset(3, 1);
419     }
420
421     context.outputFile().writeString(genericOptionFormatter_.formatRow());
422 }
423
424 void OptionsConsoleFormatter::formatSelectionOption(
425         const HelpWriterContext &context, const OptionInfo &option)
426 {
427     File &file = context.outputFile();
428
429     selectionOptionFormatter_.clear();
430     std::string name(formatString("-%s", option.name().c_str()));
431     selectionOptionFormatter_.addColumnLine(0, name);
432     selectionOptionFormatter_.addColumnLine(1, context.substituteMarkup(option.description()));
433     file.writeString(selectionOptionFormatter_.formatRow());
434
435     TextLineWrapper wrapper;
436     wrapper.settings().setLineLength(77);
437     wrapper.settings().setFirstLineIndent(4);
438     wrapper.settings().setIndent(8);
439     wrapper.settings().setContinuationChar('\\');
440     // TODO: What to do with selection variables?
441     // They are not printed as values for any option.
442     for (int i = 0; i < option.valueCount(); ++i)
443     {
444         std::string value(option.formatValue(i));
445         file.writeLine(wrapper.wrapToString(value));
446     }
447 }
448
449 } // namespace
450
451 /********************************************************************
452  * CommandLineHelpWriter::Impl
453  */
454
455 /*! \internal \brief
456  * Private implementation class for CommandLineHelpWriter.
457  *
458  * \ingroup module_commandline
459  */
460 class CommandLineHelpWriter::Impl
461 {
462     public:
463         //! Sets the Options object to use for generating help.
464         explicit Impl(const Options &options);
465
466         //! Options object to use for generating help.
467         const Options          &options_;
468         //! Time unit to show in descriptions.
469         std::string             timeUnit_;
470         //! Whether to write descriptions to output.
471         bool                    bShowDescriptions_;
472         //! Whether to write hidden options to output.
473         bool                    bShowHidden_;
474 };
475
476 CommandLineHelpWriter::Impl::Impl(const Options &options)
477     : options_(options), timeUnit_(TimeUnitManager().timeUnitAsString()),
478       bShowDescriptions_(false), bShowHidden_(false)
479 {
480 }
481
482 /********************************************************************
483  * CommandLineHelpWriter
484  */
485
486 CommandLineHelpWriter::CommandLineHelpWriter(const Options &options)
487     : impl_(new Impl(options))
488 {
489 }
490
491 CommandLineHelpWriter::~CommandLineHelpWriter()
492 {
493 }
494
495 CommandLineHelpWriter &CommandLineHelpWriter::setShowHidden(bool bSet)
496 {
497     impl_->bShowHidden_ = bSet;
498     return *this;
499 }
500
501 CommandLineHelpWriter &CommandLineHelpWriter::setShowDescriptions(bool bSet)
502 {
503     impl_->bShowDescriptions_ = bSet;
504     return *this;
505 }
506
507 CommandLineHelpWriter &CommandLineHelpWriter::setTimeUnitString(const char *timeUnit)
508 {
509     impl_->timeUnit_ = timeUnit;
510     return *this;
511 }
512
513 void CommandLineHelpWriter::writeHelp(const HelpWriterContext &context)
514 {
515     boost::scoped_ptr<OptionsFormatterInterface> formatter;
516     CommonFormatterData common(impl_->timeUnit_.c_str());
517     switch (context.outputFormat())
518     {
519         case eHelpOutputFormat_Console:
520             formatter.reset(new OptionsConsoleFormatter(common));
521             break;
522         default:
523             // TODO: Implement once the situation with Redmine issue #969 is
524             // more clear.
525             GMX_THROW(NotImplementedError(
526                         "Command-line help is not implemented for this output format"));
527     }
528     OptionsFilter filter(context, formatter.get());
529     filter.setShowHidden(impl_->bShowHidden_);
530
531     if (impl_->bShowDescriptions_)
532     {
533         File &file = context.outputFile();
534         file.writeLine("DESCRIPTION");
535         file.writeLine("-----------");
536         file.writeLine();
537         filter.formatSelected(OptionsFilter::eSelectDescriptions,
538                               impl_->options_);
539     }
540     filter.formatSelected(OptionsFilter::eSelectFileOptions,
541                           impl_->options_);
542     filter.formatSelected(OptionsFilter::eSelectOtherOptions,
543                           impl_->options_);
544     filter.formatSelected(OptionsFilter::eSelectSelectionOptions,
545                           impl_->options_);
546 }
547
548 } // namespace gmx