2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
5 * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Implements gmx::CommandLineHelpModule.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_commandline
45 #include "cmdlinehelpmodule.h"
51 #include "gromacs/commandline/cmdlinehelpcontext.h"
52 #include "gromacs/commandline/cmdlinehelpwriter.h"
53 #include "gromacs/commandline/cmdlineparser.h"
54 #include "gromacs/onlinehelp/helpformat.h"
55 #include "gromacs/onlinehelp/helpmanager.h"
56 #include "gromacs/onlinehelp/helptopic.h"
57 #include "gromacs/onlinehelp/helpwritercontext.h"
58 #include "gromacs/options/basicoptions.h"
59 #include "gromacs/options/options.h"
60 #include "gromacs/utility/arrayref.h"
61 #include "gromacs/utility/baseversion.h"
62 #include "gromacs/utility/exceptions.h"
63 #include "gromacs/utility/fileredirector.h"
64 #include "gromacs/utility/gmxassert.h"
65 #include "gromacs/utility/path.h"
66 #include "gromacs/utility/programcontext.h"
67 #include "gromacs/utility/stringstream.h"
68 #include "gromacs/utility/stringutil.h"
69 #include "gromacs/utility/textreader.h"
70 #include "gromacs/utility/textstream.h"
71 #include "gromacs/utility/textwriter.h"
73 #include "shellcompletions.h"
82 /********************************************************************
83 * RootHelpTopic declaration
87 * Help topic that forms the root of the help tree for the help subcommand.
89 * \ingroup module_commandline
91 class RootHelpTopic : public AbstractCompositeHelpTopic
95 * Creates a root help topic.
99 explicit RootHelpTopic(const CommandLineHelpModuleImpl& helpModule) : helpModule_(helpModule) {}
101 const char* name() const override;
102 const char* title() const override { return title_.c_str(); }
104 //! Adds a top-level topic and optionally marks it as exported.
105 void addTopic(HelpTopicPointer topic, bool bExported)
109 exportedTopics_.emplace_back(topic->name());
111 addSubTopic(std::move(topic));
113 //! Exports all the top-level topics with the given exporter.
114 void exportHelp(IHelpExport* exporter);
116 void writeHelp(const HelpWriterContext& context) const override;
119 // unused because of the writeHelp() override
120 std::string helpText() const override { return ""; }
122 CommandLineHelpContext createContext(const HelpWriterContext& context) const;
124 const CommandLineHelpModuleImpl& helpModule_;
126 std::vector<std::string> exportedTopics_;
128 GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
133 /********************************************************************
134 * CommandLineHelpModuleImpl declaration
137 class CommandLineHelpModuleImpl
140 CommandLineHelpModuleImpl(const IProgramContext& programContext,
141 const std::string& binaryName,
142 const CommandLineModuleMap& modules,
143 const CommandLineModuleGroupList& groups);
145 std::unique_ptr<IHelpExport> createExporter(const std::string& format, IFileOutputRedirector* redirector);
146 void exportHelp(IHelpExport* exporter);
148 RootHelpTopic rootTopic_;
149 const IProgramContext& programContext_;
150 std::string binaryName_;
151 const CommandLineModuleMap& modules_;
152 const CommandLineModuleGroupList& groups_;
154 CommandLineHelpContext* context_;
155 const ICommandLineModule* moduleOverride_;
158 IFileOutputRedirector* outputRedirector_;
160 GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModuleImpl);
166 /********************************************************************
171 * Callbacks for exporting help information for command-line modules.
173 * \ingroup module_commandline
178 //! Shorthand for a list of modules contained in a group.
179 typedef CommandLineModuleGroupData::ModuleList ModuleGroupContents;
181 virtual ~IHelpExport() {}
184 * Called once before exporting individual modules.
186 * Can, e.g., open shared output files (e.g., if the output is written
187 * into a single file, or if a separate index is required) and write
190 virtual void startModuleExport() = 0;
192 * Called to export the help for each module.
194 * \param[in] module Module for which the help should be exported.
195 * \param[in] tag Unique tag for the module (gmx-something).
196 * \param[in] displayName Display name for the module (gmx something).
198 virtual void exportModuleHelp(const ICommandLineModule& module,
199 const std::string& tag,
200 const std::string& displayName) = 0;
202 * Called after all modules have been exported.
204 * Can close files opened in startModuleExport(), write footers to them
207 virtual void finishModuleExport() = 0;
210 * Called once before exporting module groups.
212 * Can, e.g., open a single output file for listing all the groups.
214 virtual void startModuleGroupExport() = 0;
216 * Called to export the help for each module group.
218 * \param[in] title Title for the group.
219 * \param[in] modules List of modules in the group.
221 virtual void exportModuleGroup(const char* title, const ModuleGroupContents& modules) = 0;
223 * Called after all module groups have been exported.
225 * Can close files opened in startModuleGroupExport(), write footers to them
228 virtual void finishModuleGroupExport() = 0;
231 * Called to export the help for a top-level topic.
233 * \param[in] topic Topic to export.
235 virtual void exportTopic(const IHelpTopic& topic) = 0;
238 /********************************************************************
239 * RootHelpTopic implementation
244 static const char title[];
245 static const char* const text[];
248 // These are used for the gmx.1 man page.
249 // TODO: Do not hardcode them here, but pass them from the outside to make this
250 // code more generic.
251 const char RootHelpText::title[] = "molecular dynamics simulation suite";
252 const char* const RootHelpText::text[] = {
253 "|Gromacs| is a full-featured suite of programs to perform molecular",
254 "dynamics simulations, i.e., to simulate the behavior of systems with",
255 "hundreds to millions of particles using Newtonian equations of motion.",
256 "It is primarily used for research on proteins, lipids, and polymers, but",
257 "can be applied to a wide variety of chemical and biological research",
261 const char* RootHelpTopic::name() const
263 return helpModule_.binaryName_.c_str();
266 void RootHelpTopic::exportHelp(IHelpExport* exporter)
268 std::vector<std::string>::const_iterator topicName;
269 for (topicName = exportedTopics_.begin(); topicName != exportedTopics_.end(); ++topicName)
271 const IHelpTopic* topic = findSubTopic(topicName->c_str());
272 GMX_RELEASE_ASSERT(topic != nullptr, "Exported help topic no longer found");
273 exporter->exportTopic(*topic);
275 // For now, the title is only set for the export to make it not appear in
276 // console output, which makes things consistent for 'gmx help' and
277 // 'gmx help <command>'.
278 title_ = RootHelpText::title;
279 exporter->exportTopic(*this);
282 void RootHelpTopic::writeHelp(const HelpWriterContext& context) const
285 CommandLineCommonOptionsHolder optionsHolder;
286 CommandLineHelpContext cmdlineContext(createContext(context));
287 cmdlineContext.setModuleDisplayName(helpModule_.binaryName_);
288 optionsHolder.initOptions();
289 Options& options = *optionsHolder.options();
290 ArrayRef<const char* const> helpText;
291 if (context.outputFormat() != eHelpOutputFormat_Console)
293 helpText = RootHelpText::text;
295 // TODO: Add <command> [<args>] into the synopsis.
296 CommandLineHelpWriter(options).setHelpText(helpText).writeHelp(cmdlineContext);
298 if (context.outputFormat() == eHelpOutputFormat_Console)
300 // TODO: Consider printing a list of "core" commands. Would require someone
301 // to determine such a set...
302 context.paragraphBreak();
303 writeSubTopicList(context, "Additional help is available on the following topics:");
304 context.writeTextBlock("To access the help, use '[PROGRAM] help <topic>'.");
305 context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
309 // TODO: This should not really end up on the HTML page.
310 context.writeTitle(formatString("%s commands", helpModule_.binaryName_.c_str()));
311 context.writeTextBlock(
312 "The following commands are available. Please refer to their "
313 "individual man pages or [TT][PROGRAM] help <command>[tt] "
314 "for further details.");
315 context.writeTextBlock("");
316 context.writeTextBlock(".. include:: /fragments/bytopic-man.rst");
320 CommandLineHelpContext RootHelpTopic::createContext(const HelpWriterContext& context) const
322 if (helpModule_.context_ != nullptr)
324 return CommandLineHelpContext(*helpModule_.context_);
328 return CommandLineHelpContext(context);
332 /********************************************************************
337 * Help topic for listing the commands.
339 * \ingroup module_commandline
341 class CommandsHelpTopic : public IHelpTopic
345 * Creates a command list help topic.
347 * \param[in] helpModule Help module to get module information from.
351 explicit CommandsHelpTopic(const CommandLineHelpModuleImpl& helpModule) :
352 helpModule_(helpModule)
356 const char* name() const override { return "commands"; }
357 const char* title() const override { return "List of available commands"; }
358 bool hasSubTopics() const override { return false; }
359 const IHelpTopic* findSubTopic(const char* /*name*/) const override { return nullptr; }
361 void writeHelp(const HelpWriterContext& context) const override;
364 const CommandLineHelpModuleImpl& helpModule_;
366 GMX_DISALLOW_COPY_AND_ASSIGN(CommandsHelpTopic);
369 void CommandsHelpTopic::writeHelp(const HelpWriterContext& context) const
371 if (context.outputFormat() != eHelpOutputFormat_Console)
373 GMX_THROW(NotImplementedError("Module list is not implemented for this output format"));
375 int maxNameLength = 0;
376 const CommandLineModuleMap& modules = helpModule_.modules_;
377 CommandLineModuleMap::const_iterator module;
378 for (module = modules.begin(); module != modules.end(); ++module)
380 int nameLength = static_cast<int>(module->first.length());
381 if (module->second->shortDescription() != nullptr && nameLength > maxNameLength)
383 maxNameLength = nameLength;
386 context.writeTextBlock(
387 "Usage: [PROGRAM] [<options>] <command> [<args>][PAR]"
388 "Available commands:");
389 TextWriter& file = context.outputFile();
390 TextTableFormatter formatter;
391 formatter.addColumn(nullptr, maxNameLength + 1, false);
392 formatter.addColumn(nullptr, 72 - maxNameLength, true);
393 formatter.setFirstColumnIndent(4);
394 for (module = modules.begin(); module != modules.end(); ++module)
396 const char* name = module->first.c_str();
397 const char* description = module->second->shortDescription();
398 if (description != nullptr)
401 formatter.addColumnLine(0, name);
402 formatter.addColumnLine(1, description);
403 file.writeString(formatter.formatRow());
406 context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
409 /********************************************************************
414 * Help topic wrapper for a command-line module.
416 * This class implements IHelpTopic such that it wraps a
417 * ICommandLineModule, allowing subcommand "help <command>"
418 * to produce the help for "<command>".
420 * \ingroup module_commandline
422 class ModuleHelpTopic : public IHelpTopic
425 //! Constructs a help topic for a specific module.
426 ModuleHelpTopic(const ICommandLineModule& module, const CommandLineHelpModuleImpl& helpModule) :
428 helpModule_(helpModule)
432 const char* name() const override { return module_.name(); }
433 const char* title() const override { return nullptr; }
434 bool hasSubTopics() const override { return false; }
435 const IHelpTopic* findSubTopic(const char* /*name*/) const override { return nullptr; }
436 void writeHelp(const HelpWriterContext& context) const override;
439 const ICommandLineModule& module_;
440 const CommandLineHelpModuleImpl& helpModule_;
442 GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
445 void ModuleHelpTopic::writeHelp(const HelpWriterContext& /*context*/) const
447 CommandLineHelpContext context(*helpModule_.context_);
448 const char* const program = helpModule_.binaryName_.c_str();
449 context.setModuleDisplayName(formatString("%s %s", program, module_.name()));
450 module_.writeHelp(context);
453 /********************************************************************
454 * HelpExportReStructuredText
458 * Adds hyperlinks to modules within this binary.
460 * \param[in,out] links Links are added here.
461 * \param[in] helpModule Help module to get module information from.
462 * \throws std::bad_alloc if out of memory.
464 * Initializes a HelpLinks object with links to modules defined in
467 * \ingroup module_commandline
469 void initProgramLinks(HelpLinks* links, const CommandLineHelpModuleImpl& helpModule)
471 const char* const program = helpModule.binaryName_.c_str();
472 CommandLineModuleMap::const_iterator module;
473 for (module = helpModule.modules_.begin(); module != helpModule.modules_.end(); ++module)
475 if (module->second->shortDescription() != nullptr)
477 std::string linkName("[gmx-" + module->first + "]");
478 const char* name = module->first.c_str();
479 std::string reference(formatString(":doc:`%s %s <%s-%s>`", program, name, program, name));
480 std::string displayName(formatString("[TT]%s %s[tt]", program, name));
481 links->addLink(linkName, reference, displayName);
487 * Implements export for web pages as reStructuredText.
489 * \ingroup module_commandline
491 class HelpExportReStructuredText : public IHelpExport
494 //! Initializes reST exporter.
495 HelpExportReStructuredText(const CommandLineHelpModuleImpl& helpModule,
496 IFileOutputRedirector* outputRedirector);
498 void startModuleExport() override;
499 void exportModuleHelp(const ICommandLineModule& module,
500 const std::string& tag,
501 const std::string& displayName) override;
502 void finishModuleExport() override;
504 void startModuleGroupExport() override;
505 void exportModuleGroup(const char* title, const ModuleGroupContents& modules) override;
506 void finishModuleGroupExport() override;
508 void exportTopic(const IHelpTopic& topic) override;
511 IFileOutputRedirector* outputRedirector_;
512 const std::string& binaryName_; //NOLINT(google-runtime-member-string-references)
514 // These never release ownership.
515 std::unique_ptr<TextWriter> indexFile_;
516 std::unique_ptr<TextWriter> manPagesFile_;
519 HelpExportReStructuredText::HelpExportReStructuredText(const CommandLineHelpModuleImpl& helpModule,
520 IFileOutputRedirector* outputRedirector) :
521 outputRedirector_(outputRedirector),
522 binaryName_(helpModule.binaryName_),
523 links_(eHelpOutputFormat_Rst)
525 TextReader linksFile("links.dat");
527 linksFile.setTrimTrailingWhiteSpace(true);
528 while (linksFile.readLine(&line))
530 links_.addLink("[REF]." + line + "[ref]",
531 formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()),
533 links_.addLink("[REF]" + line + "[ref]", formatString(":ref:`%s`", line.c_str()), line);
536 initProgramLinks(&links_, helpModule);
539 void HelpExportReStructuredText::startModuleExport()
541 indexFile_ = std::make_unique<TextWriter>(
542 outputRedirector_->openTextOutputFile("fragments/byname.rst"));
543 indexFile_->writeLine(formatString(
544 "* :doc:`%s </onlinehelp/%s>` - %s", binaryName_.c_str(), binaryName_.c_str(), RootHelpText::title));
546 std::make_unique<TextWriter>(outputRedirector_->openTextOutputFile("conf-man.py"));
547 manPagesFile_->writeLine("man_pages = [");
550 void HelpExportReStructuredText::exportModuleHelp(const ICommandLineModule& module,
551 const std::string& tag,
552 const std::string& displayName)
554 TextOutputStreamPointer file =
555 outputRedirector_->openTextOutputFile("onlinehelp/" + tag + ".rst");
556 TextWriter writer(file);
557 writer.writeLine(formatString(".. _%s:", displayName.c_str()));
558 if (displayName == binaryName_ + " mdrun")
560 // Make an extra link target for the convenience of
561 // MPI-specific documentation
562 writer.writeLine(".. _mdrun_mpi:");
564 writer.ensureEmptyLine();
566 CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
567 context.enterSubSection(displayName);
568 context.setModuleDisplayName(displayName);
569 module.writeHelp(context);
571 writer.ensureEmptyLine();
572 writer.writeLine(".. only:: man");
574 writer.writeLine(" See also");
575 writer.writeLine(" --------");
577 writer.writeLine(formatString(" :manpage:`%s(1)`", binaryName_.c_str()));
580 " More information about |Gromacs| is available at <http://www.gromacs.org/>.");
583 indexFile_->writeLine(formatString(
584 "* :doc:`%s </onlinehelp/%s>` - %s", displayName.c_str(), tag.c_str(), module.shortDescription()));
585 manPagesFile_->writeLine(formatString(" ('onlinehelp/%s', '%s', \"%s\", '', 1),",
588 module.shortDescription()));
591 void HelpExportReStructuredText::finishModuleExport()
596 manPagesFile_->writeLine(formatString(" ('onlinehelp/%s', '%s', '%s', '', 1)",
599 RootHelpText::title));
600 manPagesFile_->writeLine("]");
601 manPagesFile_->close();
602 manPagesFile_.reset();
605 void HelpExportReStructuredText::startModuleGroupExport()
607 indexFile_ = std::make_unique<TextWriter>(
608 outputRedirector_->openTextOutputFile("fragments/bytopic.rst"));
609 manPagesFile_ = std::make_unique<TextWriter>(
610 outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst"));
613 void HelpExportReStructuredText::exportModuleGroup(const char* title, const ModuleGroupContents& modules)
615 indexFile_->ensureEmptyLine();
616 indexFile_->writeLine(title);
617 indexFile_->writeLine(std::string(std::strlen(title), '^'));
618 manPagesFile_->ensureEmptyLine();
619 manPagesFile_->writeLine(title);
620 manPagesFile_->writeLine(std::string(std::strlen(title), '^'));
622 ModuleGroupContents::const_iterator module;
623 for (module = modules.begin(); module != modules.end(); ++module)
625 const std::string& tag(module->first);
626 std::string displayName(tag);
627 // TODO: This does not work if the binary name would contain a dash,
628 // but that is not currently the case.
629 const size_t dashPos = displayName.find('-');
630 GMX_RELEASE_ASSERT(dashPos != std::string::npos,
631 "There should always be at least one dash in the tag");
632 displayName[dashPos] = ' ';
633 indexFile_->writeLine(formatString(
634 ":doc:`%s </onlinehelp/%s>`\n %s", displayName.c_str(), tag.c_str(), module->second));
635 manPagesFile_->writeLine(formatString(":manpage:`%s(1)`\n %s", tag.c_str(), module->second));
639 void HelpExportReStructuredText::finishModuleGroupExport()
643 manPagesFile_->close();
644 manPagesFile_.reset();
647 void HelpExportReStructuredText::exportTopic(const IHelpTopic& topic)
649 const std::string path("onlinehelp/" + std::string(topic.name()) + ".rst");
650 TextWriter writer(outputRedirector_->openTextOutputFile(path));
651 CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
652 HelpManager manager(topic, context.writerContext());
653 manager.writeCurrentTopic();
657 /********************************************************************
658 * HelpExportCompletion
662 * Implements export for command-line completion.
664 * \ingroup module_commandline
666 class HelpExportCompletion : public IHelpExport
669 //! Initializes completion exporter.
670 explicit HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule);
672 void startModuleExport() override;
673 void exportModuleHelp(const ICommandLineModule& module,
674 const std::string& tag,
675 const std::string& displayName) override;
676 void finishModuleExport() override;
678 void startModuleGroupExport() override {}
679 void exportModuleGroup(const char* /*title*/, const ModuleGroupContents& /*modules*/) override
682 void finishModuleGroupExport() override {}
684 void exportTopic(const IHelpTopic& /*topic*/) override {}
687 ShellCompletionWriter bashWriter_;
688 std::vector<std::string> modules_;
691 HelpExportCompletion::HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule) :
692 bashWriter_(helpModule.binaryName_, eShellCompletionFormat_Bash)
696 void HelpExportCompletion::startModuleExport()
698 bashWriter_.startCompletions();
701 void HelpExportCompletion::exportModuleHelp(const ICommandLineModule& module,
702 const std::string& /*tag*/,
703 const std::string& /*displayName*/)
705 modules_.emplace_back(module.name());
707 CommandLineHelpContext context(&bashWriter_);
708 // We use the display name to pass the name of the module to the
709 // completion writer.
710 context.setModuleDisplayName(module.name());
711 module.writeHelp(context);
715 void HelpExportCompletion::finishModuleExport()
717 CommandLineCommonOptionsHolder optionsHolder;
718 optionsHolder.initOptions();
719 bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
720 bashWriter_.finishCompletions();
725 /********************************************************************
726 * CommandLineHelpModuleImpl implementation
729 CommandLineHelpModuleImpl::CommandLineHelpModuleImpl(const IProgramContext& programContext,
730 const std::string& binaryName,
731 const CommandLineModuleMap& modules,
732 const CommandLineModuleGroupList& groups) :
734 programContext_(programContext),
735 binaryName_(binaryName),
739 moduleOverride_(nullptr),
741 outputRedirector_(&defaultFileOutputRedirector())
745 std::unique_ptr<IHelpExport> CommandLineHelpModuleImpl::createExporter(const std::string& format,
746 IFileOutputRedirector* redirector)
750 return std::unique_ptr<IHelpExport>(new HelpExportReStructuredText(*this, redirector));
752 else if (format == "completion")
754 return std::unique_ptr<IHelpExport>(new HelpExportCompletion(*this));
756 GMX_THROW(NotImplementedError("This help format is not implemented"));
759 void CommandLineHelpModuleImpl::exportHelp(IHelpExport* exporter)
761 // TODO: Would be nicer to have the file names supplied by the build system
762 // and/or export a list of files from here.
763 const char* const program = binaryName_.c_str();
765 exporter->startModuleExport();
766 CommandLineModuleMap::const_iterator module;
767 for (module = modules_.begin(); module != modules_.end(); ++module)
769 if (module->second->shortDescription() != nullptr)
771 const char* const moduleName = module->first.c_str();
772 std::string tag(formatString("%s-%s", program, moduleName));
773 std::string displayName(formatString("%s %s", program, moduleName));
774 exporter->exportModuleHelp(*module->second, tag, displayName);
777 exporter->finishModuleExport();
779 exporter->startModuleGroupExport();
780 CommandLineModuleGroupList::const_iterator group;
781 for (group = groups_.begin(); group != groups_.end(); ++group)
783 exporter->exportModuleGroup((*group)->title(), (*group)->modules());
785 exporter->finishModuleGroupExport();
787 rootTopic_.exportHelp(exporter);
793 /********************************************************************
794 * ModificationCheckingFileOutputStream
797 class ModificationCheckingFileOutputStream : public TextOutputStream
800 ModificationCheckingFileOutputStream(const char* path, IFileOutputRedirector* redirector) :
802 redirector_(redirector)
806 void write(const char* str) override { contents_.write(str); }
807 void close() override
809 const std::string& newContents = contents_.toString();
810 // TODO: Redirect these for unit tests.
811 if (File::exists(path_, File::returnFalseOnError))
813 const std::string originalContents_ = TextReader::readFileToString(path_);
814 if (originalContents_ == newContents)
819 TextWriter writer(redirector_->openTextOutputFile(path_));
820 writer.writeString(newContents);
825 StringOutputStream contents_;
826 IFileOutputRedirector* redirector_;
829 /********************************************************************
830 * ModificationCheckingFileOutputRedirector
833 class ModificationCheckingFileOutputRedirector : public IFileOutputRedirector
836 explicit ModificationCheckingFileOutputRedirector(IFileOutputRedirector* redirector) :
837 redirector_(redirector)
841 TextOutputStream& standardOutput() override { return redirector_->standardOutput(); }
842 TextOutputStreamPointer openTextOutputFile(const char* filename) override
844 return TextOutputStreamPointer(new ModificationCheckingFileOutputStream(filename, redirector_));
848 IFileOutputRedirector* redirector_;
853 /********************************************************************
854 * CommandLineHelpModule
857 CommandLineHelpModule::CommandLineHelpModule(const IProgramContext& programContext,
858 const std::string& binaryName,
859 const CommandLineModuleMap& modules,
860 const CommandLineModuleGroupList& groups) :
861 impl_(new Impl(programContext, binaryName, modules, groups))
865 CommandLineHelpModule::~CommandLineHelpModule() {}
867 HelpTopicPointer CommandLineHelpModule::createModuleHelpTopic(const ICommandLineModule& module) const
869 return HelpTopicPointer(new ModuleHelpTopic(module, *impl_));
872 void CommandLineHelpModule::addTopic(HelpTopicPointer topic, bool bExported)
874 impl_->rootTopic_.addTopic(std::move(topic), bExported);
877 void CommandLineHelpModule::setShowHidden(bool bHidden)
879 impl_->bHidden_ = bHidden;
882 void CommandLineHelpModule::setModuleOverride(const ICommandLineModule& module)
884 impl_->moduleOverride_ = &module;
887 void CommandLineHelpModule::setOutputRedirector(IFileOutputRedirector* output)
889 impl_->outputRedirector_ = output;
892 int CommandLineHelpModule::run(int argc, char* argv[])
894 // Add internal topics lazily here.
895 addTopic(HelpTopicPointer(new CommandsHelpTopic(*impl_)), false);
897 const char* const exportFormats[] = { "rst", "completion" };
898 std::string exportFormat;
900 options.addOption(StringOption("export").store(&exportFormat).enumValue(exportFormats));
901 CommandLineParser(&options).allowPositionalArguments(true).parse(&argc, argv);
902 if (!exportFormat.empty())
904 ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
905 const std::unique_ptr<IHelpExport> exporter(impl_->createExporter(exportFormat, &redirector));
906 impl_->exportHelp(exporter.get());
910 TextOutputStream& outputFile = impl_->outputRedirector_->standardOutput();
911 TextWriter writer(&outputFile);
912 HelpLinks links(eHelpOutputFormat_Console);
913 initProgramLinks(&links, *impl_);
914 CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, &links, impl_->binaryName_);
915 context.setShowHidden(impl_->bHidden_);
916 if (impl_->moduleOverride_ != nullptr)
918 context.setModuleDisplayName(impl_->programContext_.displayName());
919 impl_->moduleOverride_->writeHelp(context);
922 impl_->context_ = &context;
924 HelpManager helpManager(impl_->rootTopic_, context.writerContext());
927 for (int i = 1; i < argc; ++i)
929 helpManager.enterTopic(argv[i]);
932 catch (const InvalidInputError& ex)
934 fprintf(stderr, "%s\n", ex.what());
937 helpManager.writeCurrentTopic();
941 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext& context) const
943 const HelpWriterContext& writerContext = context.writerContext();
945 if (writerContext.outputFormat() != eHelpOutputFormat_Console)
949 writerContext.writeTextBlock("Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
950 // TODO: More information.