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) :
428 module_(module), 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), binaryName_(helpModule.binaryName_), links_(eHelpOutputFormat_Rst)
523 TextReader linksFile("links.dat");
525 linksFile.setTrimTrailingWhiteSpace(true);
526 while (linksFile.readLine(&line))
528 links_.addLink("[REF]." + line + "[ref]",
529 formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()),
531 links_.addLink("[REF]" + line + "[ref]", formatString(":ref:`%s`", line.c_str()), line);
534 initProgramLinks(&links_, helpModule);
537 void HelpExportReStructuredText::startModuleExport()
539 indexFile_ = std::make_unique<TextWriter>(
540 outputRedirector_->openTextOutputFile("fragments/byname.rst"));
541 indexFile_->writeLine(formatString(
542 "* :doc:`%s </onlinehelp/%s>` - %s", binaryName_.c_str(), binaryName_.c_str(), RootHelpText::title));
544 std::make_unique<TextWriter>(outputRedirector_->openTextOutputFile("conf-man.py"));
545 manPagesFile_->writeLine("man_pages = [");
548 void HelpExportReStructuredText::exportModuleHelp(const ICommandLineModule& module,
549 const std::string& tag,
550 const std::string& displayName)
552 TextOutputStreamPointer file =
553 outputRedirector_->openTextOutputFile("onlinehelp/" + tag + ".rst");
554 TextWriter writer(file);
555 writer.writeLine(formatString(".. _%s:", displayName.c_str()));
556 writer.ensureEmptyLine();
558 CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
559 context.enterSubSection(displayName);
560 context.setModuleDisplayName(displayName);
561 module.writeHelp(context);
563 writer.ensureEmptyLine();
564 writer.writeLine(".. only:: man");
566 writer.writeLine(" See also");
567 writer.writeLine(" --------");
569 writer.writeLine(formatString(" :manpage:`%s(1)`", binaryName_.c_str()));
572 " More information about |Gromacs| is available at <http://www.gromacs.org/>.");
575 indexFile_->writeLine(formatString(
576 "* :doc:`%s </onlinehelp/%s>` - %s", displayName.c_str(), tag.c_str(), module.shortDescription()));
577 manPagesFile_->writeLine(formatString(" ('onlinehelp/%s', '%s', \"%s\", '', 1),",
580 module.shortDescription()));
583 void HelpExportReStructuredText::finishModuleExport()
588 manPagesFile_->writeLine(formatString(" ('onlinehelp/%s', '%s', '%s', '', 1)",
591 RootHelpText::title));
592 manPagesFile_->writeLine("]");
593 manPagesFile_->close();
594 manPagesFile_.reset();
597 void HelpExportReStructuredText::startModuleGroupExport()
599 indexFile_ = std::make_unique<TextWriter>(
600 outputRedirector_->openTextOutputFile("fragments/bytopic.rst"));
601 manPagesFile_ = std::make_unique<TextWriter>(
602 outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst"));
605 void HelpExportReStructuredText::exportModuleGroup(const char* title, const ModuleGroupContents& modules)
607 indexFile_->ensureEmptyLine();
608 indexFile_->writeLine(title);
609 indexFile_->writeLine(std::string(std::strlen(title), '^'));
610 manPagesFile_->ensureEmptyLine();
611 manPagesFile_->writeLine(title);
612 manPagesFile_->writeLine(std::string(std::strlen(title), '^'));
614 ModuleGroupContents::const_iterator module;
615 for (module = modules.begin(); module != modules.end(); ++module)
617 const std::string& tag(module->first);
618 std::string displayName(tag);
619 // TODO: This does not work if the binary name would contain a dash,
620 // but that is not currently the case.
621 const size_t dashPos = displayName.find('-');
622 GMX_RELEASE_ASSERT(dashPos != std::string::npos,
623 "There should always be at least one dash in the tag");
624 displayName[dashPos] = ' ';
625 indexFile_->writeLine(formatString(
626 ":doc:`%s </onlinehelp/%s>`\n %s", displayName.c_str(), tag.c_str(), module->second));
627 manPagesFile_->writeLine(formatString(":manpage:`%s(1)`\n %s", tag.c_str(), module->second));
631 void HelpExportReStructuredText::finishModuleGroupExport()
635 manPagesFile_->close();
636 manPagesFile_.reset();
639 void HelpExportReStructuredText::exportTopic(const IHelpTopic& topic)
641 const std::string path("onlinehelp/" + std::string(topic.name()) + ".rst");
642 TextWriter writer(outputRedirector_->openTextOutputFile(path));
643 CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
644 HelpManager manager(topic, context.writerContext());
645 manager.writeCurrentTopic();
649 /********************************************************************
650 * HelpExportCompletion
654 * Implements export for command-line completion.
656 * \ingroup module_commandline
658 class HelpExportCompletion : public IHelpExport
661 //! Initializes completion exporter.
662 explicit HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule);
664 void startModuleExport() override;
665 void exportModuleHelp(const ICommandLineModule& module,
666 const std::string& tag,
667 const std::string& displayName) override;
668 void finishModuleExport() override;
670 void startModuleGroupExport() override {}
671 void exportModuleGroup(const char* /*title*/, const ModuleGroupContents& /*modules*/) override
674 void finishModuleGroupExport() override {}
676 void exportTopic(const IHelpTopic& /*topic*/) override {}
679 ShellCompletionWriter bashWriter_;
680 std::vector<std::string> modules_;
683 HelpExportCompletion::HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule) :
684 bashWriter_(helpModule.binaryName_, eShellCompletionFormat_Bash)
688 void HelpExportCompletion::startModuleExport()
690 bashWriter_.startCompletions();
693 void HelpExportCompletion::exportModuleHelp(const ICommandLineModule& module,
694 const std::string& /*tag*/,
695 const std::string& /*displayName*/)
697 modules_.emplace_back(module.name());
699 CommandLineHelpContext context(&bashWriter_);
700 // We use the display name to pass the name of the module to the
701 // completion writer.
702 context.setModuleDisplayName(module.name());
703 module.writeHelp(context);
707 void HelpExportCompletion::finishModuleExport()
709 CommandLineCommonOptionsHolder optionsHolder;
710 optionsHolder.initOptions();
711 bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
712 bashWriter_.finishCompletions();
717 /********************************************************************
718 * CommandLineHelpModuleImpl implementation
721 CommandLineHelpModuleImpl::CommandLineHelpModuleImpl(const IProgramContext& programContext,
722 const std::string& binaryName,
723 const CommandLineModuleMap& modules,
724 const CommandLineModuleGroupList& groups) :
726 programContext_(programContext),
727 binaryName_(binaryName),
731 moduleOverride_(nullptr),
733 outputRedirector_(&defaultFileOutputRedirector())
737 std::unique_ptr<IHelpExport> CommandLineHelpModuleImpl::createExporter(const std::string& format,
738 IFileOutputRedirector* redirector)
742 return std::unique_ptr<IHelpExport>(new HelpExportReStructuredText(*this, redirector));
744 else if (format == "completion")
746 return std::unique_ptr<IHelpExport>(new HelpExportCompletion(*this));
748 GMX_THROW(NotImplementedError("This help format is not implemented"));
751 void CommandLineHelpModuleImpl::exportHelp(IHelpExport* exporter)
753 // TODO: Would be nicer to have the file names supplied by the build system
754 // and/or export a list of files from here.
755 const char* const program = binaryName_.c_str();
757 exporter->startModuleExport();
758 CommandLineModuleMap::const_iterator module;
759 for (module = modules_.begin(); module != modules_.end(); ++module)
761 if (module->second->shortDescription() != nullptr)
763 const char* const moduleName = module->first.c_str();
764 std::string tag(formatString("%s-%s", program, moduleName));
765 std::string displayName(formatString("%s %s", program, moduleName));
766 exporter->exportModuleHelp(*module->second, tag, displayName);
769 exporter->finishModuleExport();
771 exporter->startModuleGroupExport();
772 CommandLineModuleGroupList::const_iterator group;
773 for (group = groups_.begin(); group != groups_.end(); ++group)
775 exporter->exportModuleGroup((*group)->title(), (*group)->modules());
777 exporter->finishModuleGroupExport();
779 rootTopic_.exportHelp(exporter);
785 /********************************************************************
786 * ModificationCheckingFileOutputStream
789 class ModificationCheckingFileOutputStream : public TextOutputStream
792 ModificationCheckingFileOutputStream(const char* path, IFileOutputRedirector* redirector) :
793 path_(path), redirector_(redirector)
797 void write(const char* str) override { contents_.write(str); }
798 void close() override
800 const std::string& newContents = contents_.toString();
801 // TODO: Redirect these for unit tests.
802 if (File::exists(path_, File::returnFalseOnError))
804 const std::string originalContents_ = TextReader::readFileToString(path_);
805 if (originalContents_ == newContents)
810 TextWriter writer(redirector_->openTextOutputFile(path_));
811 writer.writeString(newContents);
816 StringOutputStream contents_;
817 IFileOutputRedirector* redirector_;
820 /********************************************************************
821 * ModificationCheckingFileOutputRedirector
824 class ModificationCheckingFileOutputRedirector : public IFileOutputRedirector
827 explicit ModificationCheckingFileOutputRedirector(IFileOutputRedirector* redirector) :
828 redirector_(redirector)
832 TextOutputStream& standardOutput() override { return redirector_->standardOutput(); }
833 TextOutputStreamPointer openTextOutputFile(const char* filename) override
835 return TextOutputStreamPointer(new ModificationCheckingFileOutputStream(filename, redirector_));
839 IFileOutputRedirector* redirector_;
844 /********************************************************************
845 * CommandLineHelpModule
848 CommandLineHelpModule::CommandLineHelpModule(const IProgramContext& programContext,
849 const std::string& binaryName,
850 const CommandLineModuleMap& modules,
851 const CommandLineModuleGroupList& groups) :
852 impl_(new Impl(programContext, binaryName, modules, groups))
856 CommandLineHelpModule::~CommandLineHelpModule() {}
858 HelpTopicPointer CommandLineHelpModule::createModuleHelpTopic(const ICommandLineModule& module) const
860 return HelpTopicPointer(new ModuleHelpTopic(module, *impl_));
863 void CommandLineHelpModule::addTopic(HelpTopicPointer topic, bool bExported)
865 impl_->rootTopic_.addTopic(std::move(topic), bExported);
868 void CommandLineHelpModule::setShowHidden(bool bHidden)
870 impl_->bHidden_ = bHidden;
873 void CommandLineHelpModule::setModuleOverride(const ICommandLineModule& module)
875 impl_->moduleOverride_ = &module;
878 void CommandLineHelpModule::setOutputRedirector(IFileOutputRedirector* output)
880 impl_->outputRedirector_ = output;
883 int CommandLineHelpModule::run(int argc, char* argv[])
885 // Add internal topics lazily here.
886 addTopic(HelpTopicPointer(new CommandsHelpTopic(*impl_)), false);
888 const char* const exportFormats[] = { "rst", "completion" };
889 std::string exportFormat;
891 options.addOption(StringOption("export").store(&exportFormat).enumValue(exportFormats));
892 CommandLineParser(&options).allowPositionalArguments(true).parse(&argc, argv);
893 if (!exportFormat.empty())
895 ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
896 const std::unique_ptr<IHelpExport> exporter(impl_->createExporter(exportFormat, &redirector));
897 impl_->exportHelp(exporter.get());
901 TextOutputStream& outputFile = impl_->outputRedirector_->standardOutput();
902 TextWriter writer(&outputFile);
903 HelpLinks links(eHelpOutputFormat_Console);
904 initProgramLinks(&links, *impl_);
905 CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, &links, impl_->binaryName_);
906 context.setShowHidden(impl_->bHidden_);
907 if (impl_->moduleOverride_ != nullptr)
909 context.setModuleDisplayName(impl_->programContext_.displayName());
910 impl_->moduleOverride_->writeHelp(context);
913 impl_->context_ = &context;
915 HelpManager helpManager(impl_->rootTopic_, context.writerContext());
918 for (int i = 1; i < argc; ++i)
920 helpManager.enterTopic(argv[i]);
923 catch (const InvalidInputError& ex)
925 fprintf(stderr, "%s\n", ex.what());
928 helpManager.writeCurrentTopic();
932 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext& context) const
934 const HelpWriterContext& writerContext = context.writerContext();
936 if (writerContext.outputFormat() != eHelpOutputFormat_Console)
940 writerContext.writeTextBlock("Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
941 // TODO: More information.