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,2021, 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/classhelpers.h"
63 #include "gromacs/utility/exceptions.h"
64 #include "gromacs/utility/fileredirector.h"
65 #include "gromacs/utility/gmxassert.h"
66 #include "gromacs/utility/path.h"
67 #include "gromacs/utility/programcontext.h"
68 #include "gromacs/utility/stringstream.h"
69 #include "gromacs/utility/stringutil.h"
70 #include "gromacs/utility/textreader.h"
71 #include "gromacs/utility/textstream.h"
72 #include "gromacs/utility/textwriter.h"
74 #include "shellcompletions.h"
83 /********************************************************************
84 * RootHelpTopic declaration
88 * Help topic that forms the root of the help tree for the help subcommand.
90 * \ingroup module_commandline
92 class RootHelpTopic : public AbstractCompositeHelpTopic
96 * Creates a root help topic.
100 explicit RootHelpTopic(const CommandLineHelpModuleImpl& helpModule) : helpModule_(helpModule) {}
102 const char* name() const override;
103 const char* title() const override { return title_.c_str(); }
105 //! Adds a top-level topic and optionally marks it as exported.
106 void addTopic(HelpTopicPointer topic, bool bExported)
110 exportedTopics_.emplace_back(topic->name());
112 addSubTopic(std::move(topic));
114 //! Exports all the top-level topics with the given exporter.
115 void exportHelp(IHelpExport* exporter);
117 void writeHelp(const HelpWriterContext& context) const override;
120 // unused because of the writeHelp() override
121 std::string helpText() const override { return ""; }
123 CommandLineHelpContext createContext(const HelpWriterContext& context) const;
125 const CommandLineHelpModuleImpl& helpModule_;
127 std::vector<std::string> exportedTopics_;
129 GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
134 /********************************************************************
135 * CommandLineHelpModuleImpl declaration
138 class CommandLineHelpModuleImpl
141 CommandLineHelpModuleImpl(const IProgramContext& programContext,
142 const std::string& binaryName,
143 const CommandLineModuleMap& modules,
144 const CommandLineModuleGroupList& groups);
146 std::unique_ptr<IHelpExport> createExporter(const std::string& format, IFileOutputRedirector* redirector);
147 void exportHelp(IHelpExport* exporter);
149 RootHelpTopic rootTopic_;
150 const IProgramContext& programContext_;
151 std::string binaryName_;
152 const CommandLineModuleMap& modules_;
153 const CommandLineModuleGroupList& groups_;
155 CommandLineHelpContext* context_;
156 const ICommandLineModule* moduleOverride_;
159 IFileOutputRedirector* outputRedirector_;
161 GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModuleImpl);
167 /********************************************************************
172 * Callbacks for exporting help information for command-line modules.
174 * \ingroup module_commandline
179 //! Shorthand for a list of modules contained in a group.
180 typedef CommandLineModuleGroupData::ModuleList ModuleGroupContents;
182 virtual ~IHelpExport() {}
185 * Called once before exporting individual modules.
187 * Can, e.g., open shared output files (e.g., if the output is written
188 * into a single file, or if a separate index is required) and write
191 virtual void startModuleExport() = 0;
193 * Called to export the help for each module.
195 * \param[in] module Module for which the help should be exported.
196 * \param[in] tag Unique tag for the module (gmx-something).
197 * \param[in] displayName Display name for the module (gmx something).
199 virtual void exportModuleHelp(const ICommandLineModule& module,
200 const std::string& tag,
201 const std::string& displayName) = 0;
203 * Called after all modules have been exported.
205 * Can close files opened in startModuleExport(), write footers to them
208 virtual void finishModuleExport() = 0;
211 * Called once before exporting module groups.
213 * Can, e.g., open a single output file for listing all the groups.
215 virtual void startModuleGroupExport() = 0;
217 * Called to export the help for each module group.
219 * \param[in] title Title for the group.
220 * \param[in] modules List of modules in the group.
222 virtual void exportModuleGroup(const char* title, const ModuleGroupContents& modules) = 0;
224 * Called after all module groups have been exported.
226 * Can close files opened in startModuleGroupExport(), write footers to them
229 virtual void finishModuleGroupExport() = 0;
232 * Called to export the help for a top-level topic.
234 * \param[in] topic Topic to export.
236 virtual void exportTopic(const IHelpTopic& topic) = 0;
239 /********************************************************************
240 * RootHelpTopic implementation
245 static const char title[];
246 static const char* const text[];
249 // These are used for the gmx.1 man page.
250 // TODO: Do not hardcode them here, but pass them from the outside to make this
251 // code more generic.
252 const char RootHelpText::title[] = "molecular dynamics simulation suite";
253 const char* const RootHelpText::text[] = {
254 "|Gromacs| is a full-featured suite of programs to perform molecular",
255 "dynamics simulations, i.e., to simulate the behavior of systems with",
256 "hundreds to millions of particles using Newtonian equations of motion.",
257 "It is primarily used for research on proteins, lipids, and polymers, but",
258 "can be applied to a wide variety of chemical and biological research",
262 const char* RootHelpTopic::name() const
264 return helpModule_.binaryName_.c_str();
267 void RootHelpTopic::exportHelp(IHelpExport* exporter)
269 std::vector<std::string>::const_iterator topicName;
270 for (topicName = exportedTopics_.begin(); topicName != exportedTopics_.end(); ++topicName)
272 const IHelpTopic* topic = findSubTopic(topicName->c_str());
273 GMX_RELEASE_ASSERT(topic != nullptr, "Exported help topic no longer found");
274 exporter->exportTopic(*topic);
276 // For now, the title is only set for the export to make it not appear in
277 // console output, which makes things consistent for 'gmx help' and
278 // 'gmx help <command>'.
279 title_ = RootHelpText::title;
280 exporter->exportTopic(*this);
283 void RootHelpTopic::writeHelp(const HelpWriterContext& context) const
286 CommandLineCommonOptionsHolder optionsHolder;
287 CommandLineHelpContext cmdlineContext(createContext(context));
288 cmdlineContext.setModuleDisplayName(helpModule_.binaryName_);
289 optionsHolder.initOptions();
290 Options& options = *optionsHolder.options();
291 ArrayRef<const char* const> helpText;
292 if (context.outputFormat() != eHelpOutputFormat_Console)
294 helpText = RootHelpText::text;
296 // TODO: Add <command> [<args>] into the synopsis.
297 CommandLineHelpWriter(options).setHelpText(helpText).writeHelp(cmdlineContext);
299 if (context.outputFormat() == eHelpOutputFormat_Console)
301 // TODO: Consider printing a list of "core" commands. Would require someone
302 // to determine such a set...
303 context.paragraphBreak();
304 writeSubTopicList(context, "Additional help is available on the following topics:");
305 context.writeTextBlock("To access the help, use '[PROGRAM] help <topic>'.");
306 context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
310 // TODO: This should not really end up on the HTML page.
311 context.writeTitle(formatString("%s commands", helpModule_.binaryName_.c_str()));
312 context.writeTextBlock(
313 "The following commands are available. Please refer to their "
314 "individual man pages or [TT][PROGRAM] help <command>[tt] "
315 "for further details.");
316 context.writeTextBlock("");
317 context.writeTextBlock(".. include:: /fragments/bytopic-man.rst");
321 CommandLineHelpContext RootHelpTopic::createContext(const HelpWriterContext& context) const
323 if (helpModule_.context_ != nullptr)
325 return CommandLineHelpContext(*helpModule_.context_);
329 return CommandLineHelpContext(context);
333 /********************************************************************
338 * Help topic for listing the commands.
340 * \ingroup module_commandline
342 class CommandsHelpTopic : public IHelpTopic
346 * Creates a command list help topic.
348 * \param[in] helpModule Help module to get module information from.
352 explicit CommandsHelpTopic(const CommandLineHelpModuleImpl& helpModule) :
353 helpModule_(helpModule)
357 const char* name() const override { return "commands"; }
358 const char* title() const override { return "List of available commands"; }
359 bool hasSubTopics() const override { return false; }
360 const IHelpTopic* findSubTopic(const char* /*name*/) const override { return nullptr; }
362 void writeHelp(const HelpWriterContext& context) const override;
365 const CommandLineHelpModuleImpl& helpModule_;
367 GMX_DISALLOW_COPY_AND_ASSIGN(CommandsHelpTopic);
370 void CommandsHelpTopic::writeHelp(const HelpWriterContext& context) const
372 if (context.outputFormat() != eHelpOutputFormat_Console)
374 GMX_THROW(NotImplementedError("Module list is not implemented for this output format"));
376 int maxNameLength = 0;
377 const CommandLineModuleMap& modules = helpModule_.modules_;
378 CommandLineModuleMap::const_iterator module;
379 for (module = modules.begin(); module != modules.end(); ++module)
381 int nameLength = static_cast<int>(module->first.length());
382 if (module->second->shortDescription() != nullptr && nameLength > maxNameLength)
384 maxNameLength = nameLength;
387 context.writeTextBlock(
388 "Usage: [PROGRAM] [<options>] <command> [<args>][PAR]"
389 "Available commands:");
390 TextWriter& file = context.outputFile();
391 TextTableFormatter formatter;
392 formatter.addColumn(nullptr, maxNameLength + 1, false);
393 formatter.addColumn(nullptr, 72 - maxNameLength, true);
394 formatter.setFirstColumnIndent(4);
395 for (module = modules.begin(); module != modules.end(); ++module)
397 const char* name = module->first.c_str();
398 const char* description = module->second->shortDescription();
399 if (description != nullptr)
402 formatter.addColumnLine(0, name);
403 formatter.addColumnLine(1, description);
404 file.writeString(formatter.formatRow());
407 context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
410 /********************************************************************
415 * Help topic wrapper for a command-line module.
417 * This class implements IHelpTopic such that it wraps a
418 * ICommandLineModule, allowing subcommand "help <command>"
419 * to produce the help for "<command>".
421 * \ingroup module_commandline
423 class ModuleHelpTopic : public IHelpTopic
426 //! Constructs a help topic for a specific module.
427 ModuleHelpTopic(const ICommandLineModule& module, const CommandLineHelpModuleImpl& helpModule) :
429 helpModule_(helpModule)
433 const char* name() const override { return module_.name(); }
434 const char* title() const override { return nullptr; }
435 bool hasSubTopics() const override { return false; }
436 const IHelpTopic* findSubTopic(const char* /*name*/) const override { return nullptr; }
437 void writeHelp(const HelpWriterContext& context) const override;
440 const ICommandLineModule& module_;
441 const CommandLineHelpModuleImpl& helpModule_;
443 GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
446 void ModuleHelpTopic::writeHelp(const HelpWriterContext& /*context*/) const
448 CommandLineHelpContext context(*helpModule_.context_);
449 const char* const program = helpModule_.binaryName_.c_str();
450 context.setModuleDisplayName(formatString("%s %s", program, module_.name()));
451 module_.writeHelp(context);
454 /********************************************************************
455 * HelpExportReStructuredText
459 * Adds hyperlinks to modules within this binary.
461 * \param[in,out] links Links are added here.
462 * \param[in] helpModule Help module to get module information from.
463 * \throws std::bad_alloc if out of memory.
465 * Initializes a HelpLinks object with links to modules defined in
468 * \ingroup module_commandline
470 void initProgramLinks(HelpLinks* links, const CommandLineHelpModuleImpl& helpModule)
472 const char* const program = helpModule.binaryName_.c_str();
473 CommandLineModuleMap::const_iterator module;
474 for (module = helpModule.modules_.begin(); module != helpModule.modules_.end(); ++module)
476 if (module->second->shortDescription() != nullptr)
478 std::string linkName("[gmx-" + module->first + "]");
479 const char* name = module->first.c_str();
480 std::string reference(formatString(":doc:`%s %s <%s-%s>`", program, name, program, name));
481 std::string displayName(formatString("[TT]%s %s[tt]", program, name));
482 links->addLink(linkName, reference, displayName);
488 * Implements export for web pages as reStructuredText.
490 * \ingroup module_commandline
492 class HelpExportReStructuredText : public IHelpExport
495 //! Initializes reST exporter.
496 HelpExportReStructuredText(const CommandLineHelpModuleImpl& helpModule,
497 IFileOutputRedirector* outputRedirector);
499 void startModuleExport() override;
500 void exportModuleHelp(const ICommandLineModule& module,
501 const std::string& tag,
502 const std::string& displayName) override;
503 void finishModuleExport() override;
505 void startModuleGroupExport() override;
506 void exportModuleGroup(const char* title, const ModuleGroupContents& modules) override;
507 void finishModuleGroupExport() override;
509 void exportTopic(const IHelpTopic& topic) override;
512 IFileOutputRedirector* outputRedirector_;
513 const std::string& binaryName_; //NOLINT(google-runtime-member-string-references)
515 // These never release ownership.
516 std::unique_ptr<TextWriter> indexFile_;
517 std::unique_ptr<TextWriter> manPagesFile_;
520 HelpExportReStructuredText::HelpExportReStructuredText(const CommandLineHelpModuleImpl& helpModule,
521 IFileOutputRedirector* outputRedirector) :
522 outputRedirector_(outputRedirector),
523 binaryName_(helpModule.binaryName_),
524 links_(eHelpOutputFormat_Rst)
526 TextReader linksFile("links.dat");
528 linksFile.setTrimTrailingWhiteSpace(true);
529 while (linksFile.readLine(&line))
531 links_.addLink("[REF]." + line + "[ref]",
532 formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()),
534 links_.addLink("[REF]" + line + "[ref]", formatString(":ref:`%s`", line.c_str()), line);
537 initProgramLinks(&links_, helpModule);
540 void HelpExportReStructuredText::startModuleExport()
542 indexFile_ = std::make_unique<TextWriter>(
543 outputRedirector_->openTextOutputFile("fragments/byname.rst"));
544 indexFile_->writeLine(formatString(
545 "* :doc:`%s </onlinehelp/%s>` - %s", binaryName_.c_str(), binaryName_.c_str(), RootHelpText::title));
547 std::make_unique<TextWriter>(outputRedirector_->openTextOutputFile("conf-man.py"));
548 manPagesFile_->writeLine("man_pages = [");
551 void HelpExportReStructuredText::exportModuleHelp(const ICommandLineModule& module,
552 const std::string& tag,
553 const std::string& displayName)
555 TextOutputStreamPointer file =
556 outputRedirector_->openTextOutputFile("onlinehelp/" + tag + ".rst");
557 TextWriter writer(file);
558 writer.writeLine(formatString(".. _%s:", displayName.c_str()));
559 writer.ensureEmptyLine();
561 CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
562 context.enterSubSection(displayName);
563 context.setModuleDisplayName(displayName);
564 module.writeHelp(context);
566 writer.ensureEmptyLine();
567 writer.writeLine(".. only:: man");
569 writer.writeLine(" See also");
570 writer.writeLine(" --------");
572 writer.writeLine(formatString(" :manpage:`%s(1)`", binaryName_.c_str()));
575 " More information about |Gromacs| is available at <http://www.gromacs.org/>.");
578 indexFile_->writeLine(formatString(
579 "* :doc:`%s </onlinehelp/%s>` - %s", displayName.c_str(), tag.c_str(), module.shortDescription()));
580 manPagesFile_->writeLine(formatString(" ('onlinehelp/%s', '%s', \"%s\", '', 1),",
583 module.shortDescription()));
586 void HelpExportReStructuredText::finishModuleExport()
591 manPagesFile_->writeLine(formatString(" ('onlinehelp/%s', '%s', '%s', '', 1)",
594 RootHelpText::title));
595 manPagesFile_->writeLine("]");
596 manPagesFile_->close();
597 manPagesFile_.reset();
600 void HelpExportReStructuredText::startModuleGroupExport()
602 indexFile_ = std::make_unique<TextWriter>(
603 outputRedirector_->openTextOutputFile("fragments/bytopic.rst"));
604 manPagesFile_ = std::make_unique<TextWriter>(
605 outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst"));
608 void HelpExportReStructuredText::exportModuleGroup(const char* title, const ModuleGroupContents& modules)
610 indexFile_->ensureEmptyLine();
611 indexFile_->writeLine(title);
612 indexFile_->writeLine(std::string(std::strlen(title), '^'));
613 manPagesFile_->ensureEmptyLine();
614 manPagesFile_->writeLine(title);
615 manPagesFile_->writeLine(std::string(std::strlen(title), '^'));
617 ModuleGroupContents::const_iterator module;
618 for (module = modules.begin(); module != modules.end(); ++module)
620 const std::string& tag(module->first);
621 std::string displayName(tag);
622 // TODO: This does not work if the binary name would contain a dash,
623 // but that is not currently the case.
624 const size_t dashPos = displayName.find('-');
625 GMX_RELEASE_ASSERT(dashPos != std::string::npos,
626 "There should always be at least one dash in the tag");
627 displayName[dashPos] = ' ';
628 indexFile_->writeLine(formatString(
629 ":doc:`%s </onlinehelp/%s>`\n %s", displayName.c_str(), tag.c_str(), module->second));
630 manPagesFile_->writeLine(formatString(":manpage:`%s(1)`\n %s", tag.c_str(), module->second));
634 void HelpExportReStructuredText::finishModuleGroupExport()
638 manPagesFile_->close();
639 manPagesFile_.reset();
642 void HelpExportReStructuredText::exportTopic(const IHelpTopic& topic)
644 const std::string path("onlinehelp/" + std::string(topic.name()) + ".rst");
645 TextWriter writer(outputRedirector_->openTextOutputFile(path));
646 CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
647 HelpManager manager(topic, context.writerContext());
648 manager.writeCurrentTopic();
652 /********************************************************************
653 * HelpExportCompletion
657 * Implements export for command-line completion.
659 * \ingroup module_commandline
661 class HelpExportCompletion : public IHelpExport
664 //! Initializes completion exporter.
665 explicit HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule);
667 void startModuleExport() override;
668 void exportModuleHelp(const ICommandLineModule& module,
669 const std::string& tag,
670 const std::string& displayName) override;
671 void finishModuleExport() override;
673 void startModuleGroupExport() override {}
674 void exportModuleGroup(const char* /*title*/, const ModuleGroupContents& /*modules*/) override
677 void finishModuleGroupExport() override {}
679 void exportTopic(const IHelpTopic& /*topic*/) override {}
682 ShellCompletionWriter bashWriter_;
683 std::vector<std::string> modules_;
686 HelpExportCompletion::HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule) :
687 bashWriter_(helpModule.binaryName_, eShellCompletionFormat_Bash)
691 void HelpExportCompletion::startModuleExport()
693 bashWriter_.startCompletions();
696 void HelpExportCompletion::exportModuleHelp(const ICommandLineModule& module,
697 const std::string& /*tag*/,
698 const std::string& /*displayName*/)
700 modules_.emplace_back(module.name());
702 CommandLineHelpContext context(&bashWriter_);
703 // We use the display name to pass the name of the module to the
704 // completion writer.
705 context.setModuleDisplayName(module.name());
706 module.writeHelp(context);
710 void HelpExportCompletion::finishModuleExport()
712 CommandLineCommonOptionsHolder optionsHolder;
713 optionsHolder.initOptions();
714 bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
715 bashWriter_.finishCompletions();
720 /********************************************************************
721 * CommandLineHelpModuleImpl implementation
724 CommandLineHelpModuleImpl::CommandLineHelpModuleImpl(const IProgramContext& programContext,
725 const std::string& binaryName,
726 const CommandLineModuleMap& modules,
727 const CommandLineModuleGroupList& groups) :
729 programContext_(programContext),
730 binaryName_(binaryName),
734 moduleOverride_(nullptr),
736 outputRedirector_(&defaultFileOutputRedirector())
740 std::unique_ptr<IHelpExport> CommandLineHelpModuleImpl::createExporter(const std::string& format,
741 IFileOutputRedirector* redirector)
745 return std::unique_ptr<IHelpExport>(new HelpExportReStructuredText(*this, redirector));
747 else if (format == "completion")
749 return std::unique_ptr<IHelpExport>(new HelpExportCompletion(*this));
751 GMX_THROW(NotImplementedError("This help format is not implemented"));
754 void CommandLineHelpModuleImpl::exportHelp(IHelpExport* exporter)
756 // TODO: Would be nicer to have the file names supplied by the build system
757 // and/or export a list of files from here.
758 const char* const program = binaryName_.c_str();
760 exporter->startModuleExport();
761 CommandLineModuleMap::const_iterator module;
762 for (module = modules_.begin(); module != modules_.end(); ++module)
764 if (module->second->shortDescription() != nullptr)
766 const char* const moduleName = module->first.c_str();
767 std::string tag(formatString("%s-%s", program, moduleName));
768 std::string displayName(formatString("%s %s", program, moduleName));
769 exporter->exportModuleHelp(*module->second, tag, displayName);
772 exporter->finishModuleExport();
774 exporter->startModuleGroupExport();
775 CommandLineModuleGroupList::const_iterator group;
776 for (group = groups_.begin(); group != groups_.end(); ++group)
778 exporter->exportModuleGroup((*group)->title(), (*group)->modules());
780 exporter->finishModuleGroupExport();
782 rootTopic_.exportHelp(exporter);
788 /********************************************************************
789 * ModificationCheckingFileOutputStream
792 class ModificationCheckingFileOutputStream : public TextOutputStream
795 ModificationCheckingFileOutputStream(const char* path, IFileOutputRedirector* redirector) :
797 redirector_(redirector)
801 void write(const char* str) override { contents_.write(str); }
802 void close() override
804 const std::string& newContents = contents_.toString();
805 // TODO: Redirect these for unit tests.
806 if (File::exists(path_, File::returnFalseOnError))
808 const std::string originalContents_ = TextReader::readFileToString(path_);
809 if (originalContents_ == newContents)
814 TextWriter writer(redirector_->openTextOutputFile(path_));
815 writer.writeString(newContents);
820 StringOutputStream contents_;
821 IFileOutputRedirector* redirector_;
824 /********************************************************************
825 * ModificationCheckingFileOutputRedirector
828 class ModificationCheckingFileOutputRedirector : public IFileOutputRedirector
831 explicit ModificationCheckingFileOutputRedirector(IFileOutputRedirector* redirector) :
832 redirector_(redirector)
836 TextOutputStream& standardOutput() override { return redirector_->standardOutput(); }
837 TextOutputStreamPointer openTextOutputFile(const char* filename) override
839 return TextOutputStreamPointer(new ModificationCheckingFileOutputStream(filename, redirector_));
843 IFileOutputRedirector* redirector_;
848 /********************************************************************
849 * CommandLineHelpModule
852 CommandLineHelpModule::CommandLineHelpModule(const IProgramContext& programContext,
853 const std::string& binaryName,
854 const CommandLineModuleMap& modules,
855 const CommandLineModuleGroupList& groups) :
856 impl_(new Impl(programContext, binaryName, modules, groups))
860 CommandLineHelpModule::~CommandLineHelpModule() {}
862 HelpTopicPointer CommandLineHelpModule::createModuleHelpTopic(const ICommandLineModule& module) const
864 return HelpTopicPointer(new ModuleHelpTopic(module, *impl_));
867 void CommandLineHelpModule::addTopic(HelpTopicPointer topic, bool bExported)
869 impl_->rootTopic_.addTopic(std::move(topic), bExported);
872 void CommandLineHelpModule::setShowHidden(bool bHidden)
874 impl_->bHidden_ = bHidden;
877 void CommandLineHelpModule::setModuleOverride(const ICommandLineModule& module)
879 impl_->moduleOverride_ = &module;
882 void CommandLineHelpModule::setOutputRedirector(IFileOutputRedirector* output)
884 impl_->outputRedirector_ = output;
887 int CommandLineHelpModule::run(int argc, char* argv[])
889 // Add internal topics lazily here.
890 addTopic(HelpTopicPointer(new CommandsHelpTopic(*impl_)), false);
892 const char* const exportFormats[] = { "rst", "completion" };
893 std::string exportFormat;
895 options.addOption(StringOption("export").store(&exportFormat).enumValue(exportFormats));
896 CommandLineParser(&options).allowPositionalArguments(true).parse(&argc, argv);
897 if (!exportFormat.empty())
899 ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
900 const std::unique_ptr<IHelpExport> exporter(impl_->createExporter(exportFormat, &redirector));
901 impl_->exportHelp(exporter.get());
905 TextOutputStream& outputFile = impl_->outputRedirector_->standardOutput();
906 TextWriter writer(&outputFile);
907 HelpLinks links(eHelpOutputFormat_Console);
908 initProgramLinks(&links, *impl_);
909 CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, &links, impl_->binaryName_);
910 context.setShowHidden(impl_->bHidden_);
911 if (impl_->moduleOverride_ != nullptr)
913 context.setModuleDisplayName(impl_->programContext_.displayName());
914 impl_->moduleOverride_->writeHelp(context);
917 impl_->context_ = &context;
919 HelpManager helpManager(impl_->rootTopic_, context.writerContext());
922 for (int i = 1; i < argc; ++i)
924 helpManager.enterTopic(argv[i]);
927 catch (const InvalidInputError& ex)
929 fprintf(stderr, "%s\n", ex.what());
932 helpManager.writeCurrentTopic();
936 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext& context) const
938 const HelpWriterContext& writerContext = context.writerContext();
940 if (writerContext.outputFormat() != eHelpOutputFormat_Console)
944 writerContext.writeTextBlock("Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
945 // TODO: More information.