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