Fix copyright notices for new C++ code.
[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, 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     // TODO: Better handling of multiple long values
403     std::string values;
404     for (int i = 0; i < option.valueCount(); ++i)
405     {
406         if (i != 0)
407         {
408             values.append(" ");
409         }
410         values.append(option.formatValue(i));
411     }
412     genericOptionFormatter_.addColumnLine(2, values);
413     std::string             description(context.substituteMarkup(option.description()));
414     const DoubleOptionInfo *doubleOption = option.toType<DoubleOptionInfo>();
415     if (doubleOption != NULL && doubleOption->isTime())
416     {
417         description = replaceAll(description, "%t", common_.timeUnit);
418     }
419     genericOptionFormatter_.addColumnLine(3, description);
420     if (values.length() > 6U)
421     {
422         genericOptionFormatter_.setColumnFirstLineOffset(3, 1);
423     }
424
425     context.outputFile().writeString(genericOptionFormatter_.formatRow());
426 }
427
428 void OptionsConsoleFormatter::formatSelectionOption(
429         const HelpWriterContext &context, const OptionInfo &option)
430 {
431     File &file = context.outputFile();
432
433     selectionOptionFormatter_.clear();
434     std::string name(formatString("-%s", option.name().c_str()));
435     selectionOptionFormatter_.addColumnLine(0, name);
436     selectionOptionFormatter_.addColumnLine(1, context.substituteMarkup(option.description()));
437     file.writeString(selectionOptionFormatter_.formatRow());
438
439     TextLineWrapper wrapper;
440     wrapper.settings().setLineLength(77);
441     wrapper.settings().setFirstLineIndent(4);
442     wrapper.settings().setIndent(8);
443     wrapper.settings().setContinuationChar('\\');
444     // TODO: What to do with selection variables?
445     // They are not printed as values for any option.
446     for (int i = 0; i < option.valueCount(); ++i)
447     {
448         std::string value(option.formatValue(i));
449         file.writeLine(wrapper.wrapToString(value));
450     }
451 }
452
453 }   // namespace
454
455 /********************************************************************
456  * CommandLineHelpWriter::Impl
457  */
458
459 /*! \internal \brief
460  * Private implementation class for CommandLineHelpWriter.
461  *
462  * \ingroup module_commandline
463  */
464 class CommandLineHelpWriter::Impl
465 {
466     public:
467         //! Sets the Options object to use for generating help.
468         explicit Impl(const Options &options);
469
470         //! Options object to use for generating help.
471         const Options          &options_;
472         //! Time unit to show in descriptions.
473         std::string             timeUnit_;
474         //! Whether to write descriptions to output.
475         bool                    bShowDescriptions_;
476         //! Whether to write hidden options to output.
477         bool                    bShowHidden_;
478 };
479
480 CommandLineHelpWriter::Impl::Impl(const Options &options)
481     : options_(options), timeUnit_(TimeUnitManager().timeUnitAsString()),
482       bShowDescriptions_(false), bShowHidden_(false)
483 {
484 }
485
486 /********************************************************************
487  * CommandLineHelpWriter
488  */
489
490 CommandLineHelpWriter::CommandLineHelpWriter(const Options &options)
491     : impl_(new Impl(options))
492 {
493 }
494
495 CommandLineHelpWriter::~CommandLineHelpWriter()
496 {
497 }
498
499 CommandLineHelpWriter &CommandLineHelpWriter::setShowHidden(bool bSet)
500 {
501     impl_->bShowHidden_ = bSet;
502     return *this;
503 }
504
505 CommandLineHelpWriter &CommandLineHelpWriter::setShowDescriptions(bool bSet)
506 {
507     impl_->bShowDescriptions_ = bSet;
508     return *this;
509 }
510
511 CommandLineHelpWriter &CommandLineHelpWriter::setTimeUnitString(const char *timeUnit)
512 {
513     impl_->timeUnit_ = timeUnit;
514     return *this;
515 }
516
517 void CommandLineHelpWriter::writeHelp(const HelpWriterContext &context)
518 {
519     boost::scoped_ptr<OptionsFormatterInterface> formatter;
520     CommonFormatterData common(impl_->timeUnit_.c_str());
521     switch (context.outputFormat())
522     {
523         case eHelpOutputFormat_Console:
524             formatter.reset(new OptionsConsoleFormatter(common));
525             break;
526         default:
527             // TODO: Implement once the situation with Redmine issue #969 is
528             // more clear.
529             GMX_THROW(NotImplementedError(
530                               "Command-line help is not implemented for this output format"));
531     }
532     OptionsFilter filter(context, formatter.get());
533     filter.setShowHidden(impl_->bShowHidden_);
534
535     if (impl_->bShowDescriptions_)
536     {
537         File &file = context.outputFile();
538         file.writeLine("DESCRIPTION");
539         file.writeLine("-----------");
540         file.writeLine();
541         filter.formatSelected(OptionsFilter::eSelectDescriptions,
542                               impl_->options_);
543     }
544     filter.formatSelected(OptionsFilter::eSelectFileOptions,
545                           impl_->options_);
546     filter.formatSelected(OptionsFilter::eSelectOtherOptions,
547                           impl_->options_);
548     filter.formatSelected(OptionsFilter::eSelectSelectionOptions,
549                           impl_->options_);
550 }
551
552 } // namespace gmx