2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013,2014,2015,2016,2017,2018,2019, 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.
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.
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.
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.
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.
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.
37 * Implements gmx::CommandLineHelpModule.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_commandline
44 #include "cmdlinehelpmodule.h"
50 #include "gromacs/commandline/cmdlinehelpcontext.h"
51 #include "gromacs/commandline/cmdlinehelpwriter.h"
52 #include "gromacs/commandline/cmdlineparser.h"
53 #include "gromacs/onlinehelp/helpformat.h"
54 #include "gromacs/onlinehelp/helpmanager.h"
55 #include "gromacs/onlinehelp/helptopic.h"
56 #include "gromacs/onlinehelp/helpwritercontext.h"
57 #include "gromacs/options/basicoptions.h"
58 #include "gromacs/options/options.h"
59 #include "gromacs/utility/arrayref.h"
60 #include "gromacs/utility/baseversion.h"
61 #include "gromacs/utility/exceptions.h"
62 #include "gromacs/utility/fileredirector.h"
63 #include "gromacs/utility/gmxassert.h"
64 #include "gromacs/utility/path.h"
65 #include "gromacs/utility/programcontext.h"
66 #include "gromacs/utility/stringstream.h"
67 #include "gromacs/utility/stringutil.h"
68 #include "gromacs/utility/textreader.h"
69 #include "gromacs/utility/textstream.h"
70 #include "gromacs/utility/textwriter.h"
72 #include "shellcompletions.h"
81 /********************************************************************
82 * RootHelpTopic declaration
86 * Help topic that forms the root of the help tree for the help subcommand.
88 * \ingroup module_commandline
90 class RootHelpTopic : public AbstractCompositeHelpTopic
94 * Creates a root help topic.
98 explicit RootHelpTopic(const CommandLineHelpModuleImpl& helpModule) : helpModule_(helpModule) {}
100 const char* name() const override;
101 const char* title() const override { return title_.c_str(); }
103 //! Adds a top-level topic and optionally marks it as exported.
104 void addTopic(HelpTopicPointer topic, bool bExported)
108 exportedTopics_.emplace_back(topic->name());
110 addSubTopic(std::move(topic));
112 //! Exports all the top-level topics with the given exporter.
113 void exportHelp(IHelpExport* exporter);
115 void writeHelp(const HelpWriterContext& context) const override;
118 // unused because of the writeHelp() override
119 std::string helpText() const override { return ""; }
121 CommandLineHelpContext createContext(const HelpWriterContext& context) const;
123 const CommandLineHelpModuleImpl& helpModule_;
125 std::vector<std::string> exportedTopics_;
127 GMX_DISALLOW_COPY_AND_ASSIGN(RootHelpTopic);
132 /********************************************************************
133 * CommandLineHelpModuleImpl declaration
136 class CommandLineHelpModuleImpl
139 CommandLineHelpModuleImpl(const IProgramContext& programContext,
140 const std::string& binaryName,
141 const CommandLineModuleMap& modules,
142 const CommandLineModuleGroupList& groups);
144 std::unique_ptr<IHelpExport> createExporter(const std::string& format, IFileOutputRedirector* redirector);
145 void exportHelp(IHelpExport* exporter);
147 RootHelpTopic rootTopic_;
148 const IProgramContext& programContext_;
149 std::string binaryName_;
150 const CommandLineModuleMap& modules_;
151 const CommandLineModuleGroupList& groups_;
153 CommandLineHelpContext* context_;
154 const ICommandLineModule* moduleOverride_;
157 IFileOutputRedirector* outputRedirector_;
159 GMX_DISALLOW_COPY_AND_ASSIGN(CommandLineHelpModuleImpl);
165 /********************************************************************
170 * Callbacks for exporting help information for command-line modules.
172 * \ingroup module_commandline
177 //! Shorthand for a list of modules contained in a group.
178 typedef CommandLineModuleGroupData::ModuleList ModuleGroupContents;
180 virtual ~IHelpExport() {}
183 * Called once before exporting individual modules.
185 * Can, e.g., open shared output files (e.g., if the output is written
186 * into a single file, or if a separate index is required) and write
189 virtual void startModuleExport() = 0;
191 * Called to export the help for each module.
193 * \param[in] module Module for which the help should be exported.
194 * \param[in] tag Unique tag for the module (gmx-something).
195 * \param[in] displayName Display name for the module (gmx something).
197 virtual void exportModuleHelp(const ICommandLineModule& module,
198 const std::string& tag,
199 const std::string& displayName) = 0;
201 * Called after all modules have been exported.
203 * Can close files opened in startModuleExport(), write footers to them
206 virtual void finishModuleExport() = 0;
209 * Called once before exporting module groups.
211 * Can, e.g., open a single output file for listing all the groups.
213 virtual void startModuleGroupExport() = 0;
215 * Called to export the help for each module group.
217 * \param[in] title Title for the group.
218 * \param[in] modules List of modules in the group.
220 virtual void exportModuleGroup(const char* title, const ModuleGroupContents& modules) = 0;
222 * Called after all module groups have been exported.
224 * Can close files opened in startModuleGroupExport(), write footers to them
227 virtual void finishModuleGroupExport() = 0;
230 * Called to export the help for a top-level topic.
232 * \param[in] topic Topic to export.
234 virtual void exportTopic(const IHelpTopic& topic) = 0;
237 /********************************************************************
238 * RootHelpTopic implementation
243 static const char title[];
244 static const char* const text[];
247 // These are used for the gmx.1 man page.
248 // TODO: Do not hardcode them here, but pass them from the outside to make this
249 // code more generic.
250 const char RootHelpText::title[] = "molecular dynamics simulation suite";
251 const char* const RootHelpText::text[] = {
252 "|Gromacs| is a full-featured suite of programs to perform molecular",
253 "dynamics simulations, i.e., to simulate the behavior of systems with",
254 "hundreds to millions of particles using Newtonian equations of motion.",
255 "It is primarily used for research on proteins, lipids, and polymers, but",
256 "can be applied to a wide variety of chemical and biological research",
260 const char* RootHelpTopic::name() const
262 return helpModule_.binaryName_.c_str();
265 void RootHelpTopic::exportHelp(IHelpExport* exporter)
267 std::vector<std::string>::const_iterator topicName;
268 for (topicName = exportedTopics_.begin(); topicName != exportedTopics_.end(); ++topicName)
270 const IHelpTopic* topic = findSubTopic(topicName->c_str());
271 GMX_RELEASE_ASSERT(topic != nullptr, "Exported help topic no longer found");
272 exporter->exportTopic(*topic);
274 // For now, the title is only set for the export to make it not appear in
275 // console output, which makes things consistent for 'gmx help' and
276 // 'gmx help <command>'.
277 title_ = RootHelpText::title;
278 exporter->exportTopic(*this);
281 void RootHelpTopic::writeHelp(const HelpWriterContext& context) const
284 CommandLineCommonOptionsHolder optionsHolder;
285 CommandLineHelpContext cmdlineContext(createContext(context));
286 cmdlineContext.setModuleDisplayName(helpModule_.binaryName_);
287 optionsHolder.initOptions();
288 Options& options = *optionsHolder.options();
289 ArrayRef<const char* const> helpText;
290 if (context.outputFormat() != eHelpOutputFormat_Console)
292 helpText = RootHelpText::text;
294 // TODO: Add <command> [<args>] into the synopsis.
295 CommandLineHelpWriter(options).setHelpText(helpText).writeHelp(cmdlineContext);
297 if (context.outputFormat() == eHelpOutputFormat_Console)
299 // TODO: Consider printing a list of "core" commands. Would require someone
300 // to determine such a set...
301 context.paragraphBreak();
302 writeSubTopicList(context, "Additional help is available on the following topics:");
303 context.writeTextBlock("To access the help, use '[PROGRAM] help <topic>'.");
304 context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
308 // TODO: This should not really end up on the HTML page.
309 context.writeTitle(formatString("%s commands", helpModule_.binaryName_.c_str()));
310 context.writeTextBlock(
311 "The following commands are available. Please refer to their "
312 "individual man pages or [TT][PROGRAM] help <command>[tt] "
313 "for further details.");
314 context.writeTextBlock("");
315 context.writeTextBlock(".. include:: /fragments/bytopic-man.rst");
319 CommandLineHelpContext RootHelpTopic::createContext(const HelpWriterContext& context) const
321 if (helpModule_.context_ != nullptr)
323 return CommandLineHelpContext(*helpModule_.context_);
327 return CommandLineHelpContext(context);
331 /********************************************************************
336 * Help topic for listing the commands.
338 * \ingroup module_commandline
340 class CommandsHelpTopic : public IHelpTopic
344 * Creates a command list help topic.
346 * \param[in] helpModule Help module to get module information from.
350 explicit CommandsHelpTopic(const CommandLineHelpModuleImpl& helpModule) :
351 helpModule_(helpModule)
355 const char* name() const override { return "commands"; }
356 const char* title() const override { return "List of available commands"; }
357 bool hasSubTopics() const override { return false; }
358 const IHelpTopic* findSubTopic(const char* /*name*/) const override { return nullptr; }
360 void writeHelp(const HelpWriterContext& context) const override;
363 const CommandLineHelpModuleImpl& helpModule_;
365 GMX_DISALLOW_COPY_AND_ASSIGN(CommandsHelpTopic);
368 void CommandsHelpTopic::writeHelp(const HelpWriterContext& context) const
370 if (context.outputFormat() != eHelpOutputFormat_Console)
372 GMX_THROW(NotImplementedError("Module list is not implemented for this output format"));
374 int maxNameLength = 0;
375 const CommandLineModuleMap& modules = helpModule_.modules_;
376 CommandLineModuleMap::const_iterator module;
377 for (module = modules.begin(); module != modules.end(); ++module)
379 int nameLength = static_cast<int>(module->first.length());
380 if (module->second->shortDescription() != nullptr && nameLength > maxNameLength)
382 maxNameLength = nameLength;
385 context.writeTextBlock(
386 "Usage: [PROGRAM] [<options>] <command> [<args>][PAR]"
387 "Available commands:");
388 TextWriter& file = context.outputFile();
389 TextTableFormatter formatter;
390 formatter.addColumn(nullptr, maxNameLength + 1, false);
391 formatter.addColumn(nullptr, 72 - maxNameLength, true);
392 formatter.setFirstColumnIndent(4);
393 for (module = modules.begin(); module != modules.end(); ++module)
395 const char* name = module->first.c_str();
396 const char* description = module->second->shortDescription();
397 if (description != nullptr)
400 formatter.addColumnLine(0, name);
401 formatter.addColumnLine(1, description);
402 file.writeString(formatter.formatRow());
405 context.writeTextBlock("For help on a command, use '[PROGRAM] help <command>'.");
408 /********************************************************************
413 * Help topic wrapper for a command-line module.
415 * This class implements IHelpTopic such that it wraps a
416 * ICommandLineModule, allowing subcommand "help <command>"
417 * to produce the help for "<command>".
419 * \ingroup module_commandline
421 class ModuleHelpTopic : public IHelpTopic
424 //! Constructs a help topic for a specific module.
425 ModuleHelpTopic(const ICommandLineModule& module, const CommandLineHelpModuleImpl& helpModule) :
427 helpModule_(helpModule)
431 const char* name() const override { return module_.name(); }
432 const char* title() const override { return nullptr; }
433 bool hasSubTopics() const override { return false; }
434 const IHelpTopic* findSubTopic(const char* /*name*/) const override { return nullptr; }
435 void writeHelp(const HelpWriterContext& context) const override;
438 const ICommandLineModule& module_;
439 const CommandLineHelpModuleImpl& helpModule_;
441 GMX_DISALLOW_COPY_AND_ASSIGN(ModuleHelpTopic);
444 void ModuleHelpTopic::writeHelp(const HelpWriterContext& /*context*/) const
446 CommandLineHelpContext context(*helpModule_.context_);
447 const char* const program = helpModule_.binaryName_.c_str();
448 context.setModuleDisplayName(formatString("%s %s", program, module_.name()));
449 module_.writeHelp(context);
452 /********************************************************************
453 * HelpExportReStructuredText
457 * Adds hyperlinks to modules within this binary.
459 * \param[in,out] links Links are added here.
460 * \param[in] helpModule Help module to get module information from.
461 * \throws std::bad_alloc if out of memory.
463 * Initializes a HelpLinks object with links to modules defined in
466 * \ingroup module_commandline
468 void initProgramLinks(HelpLinks* links, const CommandLineHelpModuleImpl& helpModule)
470 const char* const program = helpModule.binaryName_.c_str();
471 CommandLineModuleMap::const_iterator module;
472 for (module = helpModule.modules_.begin(); module != helpModule.modules_.end(); ++module)
474 if (module->second->shortDescription() != nullptr)
476 std::string linkName("[gmx-" + module->first + "]");
477 const char* name = module->first.c_str();
478 std::string reference(formatString(":doc:`%s %s <%s-%s>`", program, name, program, name));
479 std::string displayName(formatString("[TT]%s %s[tt]", program, name));
480 links->addLink(linkName, reference, displayName);
486 * Implements export for web pages as reStructuredText.
488 * \ingroup module_commandline
490 class HelpExportReStructuredText : public IHelpExport
493 //! Initializes reST exporter.
494 HelpExportReStructuredText(const CommandLineHelpModuleImpl& helpModule,
495 IFileOutputRedirector* outputRedirector);
497 void startModuleExport() override;
498 void exportModuleHelp(const ICommandLineModule& module,
499 const std::string& tag,
500 const std::string& displayName) override;
501 void finishModuleExport() override;
503 void startModuleGroupExport() override;
504 void exportModuleGroup(const char* title, const ModuleGroupContents& modules) override;
505 void finishModuleGroupExport() override;
507 void exportTopic(const IHelpTopic& topic) override;
510 IFileOutputRedirector* outputRedirector_;
511 const std::string& binaryName_; //NOLINT(google-runtime-member-string-references)
513 // These never release ownership.
514 std::unique_ptr<TextWriter> indexFile_;
515 std::unique_ptr<TextWriter> manPagesFile_;
518 HelpExportReStructuredText::HelpExportReStructuredText(const CommandLineHelpModuleImpl& helpModule,
519 IFileOutputRedirector* outputRedirector) :
520 outputRedirector_(outputRedirector),
521 binaryName_(helpModule.binaryName_),
522 links_(eHelpOutputFormat_Rst)
524 TextReader linksFile("links.dat");
526 linksFile.setTrimTrailingWhiteSpace(true);
527 while (linksFile.readLine(&line))
529 links_.addLink("[REF]." + line + "[ref]",
530 formatString(":ref:`.%s <%s>`", line.c_str(), line.c_str()), line);
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("* :doc:`%s </onlinehelp/%s>` - %s", binaryName_.c_str(),
542 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 if (displayName == binaryName_ + " mdrun")
558 // Make an extra link target for the convenience of
559 // MPI-specific documentation
560 writer.writeLine(".. _mdrun_mpi:");
562 writer.ensureEmptyLine();
564 CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
565 context.enterSubSection(displayName);
566 context.setModuleDisplayName(displayName);
567 module.writeHelp(context);
569 writer.ensureEmptyLine();
570 writer.writeLine(".. only:: man");
572 writer.writeLine(" See also");
573 writer.writeLine(" --------");
575 writer.writeLine(formatString(" :manpage:`%s(1)`", binaryName_.c_str()));
578 " More information about |Gromacs| is available at <http://www.gromacs.org/>.");
581 indexFile_->writeLine(formatString("* :doc:`%s </onlinehelp/%s>` - %s", displayName.c_str(),
582 tag.c_str(), module.shortDescription()));
583 manPagesFile_->writeLine(formatString(" ('onlinehelp/%s', '%s', \"%s\", '', 1),",
584 tag.c_str(), tag.c_str(), module.shortDescription()));
587 void HelpExportReStructuredText::finishModuleExport()
592 manPagesFile_->writeLine(formatString(" ('onlinehelp/%s', '%s', '%s', '', 1)",
593 binaryName_.c_str(), binaryName_.c_str(), RootHelpText::title));
594 manPagesFile_->writeLine("]");
595 manPagesFile_->close();
596 manPagesFile_.reset();
599 void HelpExportReStructuredText::startModuleGroupExport()
601 indexFile_ = std::make_unique<TextWriter>(
602 outputRedirector_->openTextOutputFile("fragments/bytopic.rst"));
603 manPagesFile_ = std::make_unique<TextWriter>(
604 outputRedirector_->openTextOutputFile("fragments/bytopic-man.rst"));
607 void HelpExportReStructuredText::exportModuleGroup(const char* title, const ModuleGroupContents& modules)
609 indexFile_->ensureEmptyLine();
610 indexFile_->writeLine(title);
611 indexFile_->writeLine(std::string(std::strlen(title), '^'));
612 manPagesFile_->ensureEmptyLine();
613 manPagesFile_->writeLine(title);
614 manPagesFile_->writeLine(std::string(std::strlen(title), '^'));
616 ModuleGroupContents::const_iterator module;
617 for (module = modules.begin(); module != modules.end(); ++module)
619 const std::string& tag(module->first);
620 std::string displayName(tag);
621 // TODO: This does not work if the binary name would contain a dash,
622 // but that is not currently the case.
623 const size_t dashPos = displayName.find('-');
624 GMX_RELEASE_ASSERT(dashPos != std::string::npos,
625 "There should always be at least one dash in the tag");
626 displayName[dashPos] = ' ';
627 indexFile_->writeLine(formatString(":doc:`%s </onlinehelp/%s>`\n %s", displayName.c_str(),
628 tag.c_str(), module->second));
629 manPagesFile_->writeLine(formatString(":manpage:`%s(1)`\n %s", tag.c_str(), module->second));
633 void HelpExportReStructuredText::finishModuleGroupExport()
637 manPagesFile_->close();
638 manPagesFile_.reset();
641 void HelpExportReStructuredText::exportTopic(const IHelpTopic& topic)
643 const std::string path("onlinehelp/" + std::string(topic.name()) + ".rst");
644 TextWriter writer(outputRedirector_->openTextOutputFile(path));
645 CommandLineHelpContext context(&writer, eHelpOutputFormat_Rst, &links_, binaryName_);
646 HelpManager manager(topic, context.writerContext());
647 manager.writeCurrentTopic();
651 /********************************************************************
652 * HelpExportCompletion
656 * Implements export for command-line completion.
658 * \ingroup module_commandline
660 class HelpExportCompletion : public IHelpExport
663 //! Initializes completion exporter.
664 explicit HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule);
666 void startModuleExport() override;
667 void exportModuleHelp(const ICommandLineModule& module,
668 const std::string& tag,
669 const std::string& displayName) override;
670 void finishModuleExport() override;
672 void startModuleGroupExport() override {}
673 void exportModuleGroup(const char* /*title*/, const ModuleGroupContents& /*modules*/) override
676 void finishModuleGroupExport() override {}
678 void exportTopic(const IHelpTopic& /*topic*/) override {}
681 ShellCompletionWriter bashWriter_;
682 std::vector<std::string> modules_;
685 HelpExportCompletion::HelpExportCompletion(const CommandLineHelpModuleImpl& helpModule) :
686 bashWriter_(helpModule.binaryName_, eShellCompletionFormat_Bash)
690 void HelpExportCompletion::startModuleExport()
692 bashWriter_.startCompletions();
695 void HelpExportCompletion::exportModuleHelp(const ICommandLineModule& module,
696 const std::string& /*tag*/,
697 const std::string& /*displayName*/)
699 modules_.emplace_back(module.name());
701 CommandLineHelpContext context(&bashWriter_);
702 // We use the display name to pass the name of the module to the
703 // completion writer.
704 context.setModuleDisplayName(module.name());
705 module.writeHelp(context);
709 void HelpExportCompletion::finishModuleExport()
711 CommandLineCommonOptionsHolder optionsHolder;
712 optionsHolder.initOptions();
713 bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
714 bashWriter_.finishCompletions();
719 /********************************************************************
720 * CommandLineHelpModuleImpl implementation
723 CommandLineHelpModuleImpl::CommandLineHelpModuleImpl(const IProgramContext& programContext,
724 const std::string& binaryName,
725 const CommandLineModuleMap& modules,
726 const CommandLineModuleGroupList& groups) :
728 programContext_(programContext),
729 binaryName_(binaryName),
733 moduleOverride_(nullptr),
735 outputRedirector_(&defaultFileOutputRedirector())
739 std::unique_ptr<IHelpExport> CommandLineHelpModuleImpl::createExporter(const std::string& format,
740 IFileOutputRedirector* redirector)
744 return std::unique_ptr<IHelpExport>(new HelpExportReStructuredText(*this, redirector));
746 else if (format == "completion")
748 return std::unique_ptr<IHelpExport>(new HelpExportCompletion(*this));
750 GMX_THROW(NotImplementedError("This help format is not implemented"));
753 void CommandLineHelpModuleImpl::exportHelp(IHelpExport* exporter)
755 // TODO: Would be nicer to have the file names supplied by the build system
756 // and/or export a list of files from here.
757 const char* const program = binaryName_.c_str();
759 exporter->startModuleExport();
760 CommandLineModuleMap::const_iterator module;
761 for (module = modules_.begin(); module != modules_.end(); ++module)
763 if (module->second->shortDescription() != nullptr)
765 const char* const moduleName = module->first.c_str();
766 std::string tag(formatString("%s-%s", program, moduleName));
767 std::string displayName(formatString("%s %s", program, moduleName));
768 exporter->exportModuleHelp(*module->second, tag, displayName);
771 exporter->finishModuleExport();
773 exporter->startModuleGroupExport();
774 CommandLineModuleGroupList::const_iterator group;
775 for (group = groups_.begin(); group != groups_.end(); ++group)
777 exporter->exportModuleGroup((*group)->title(), (*group)->modules());
779 exporter->finishModuleGroupExport();
781 rootTopic_.exportHelp(exporter);
787 /********************************************************************
788 * ModificationCheckingFileOutputStream
791 class ModificationCheckingFileOutputStream : public TextOutputStream
794 ModificationCheckingFileOutputStream(const char* path, IFileOutputRedirector* redirector) :
796 redirector_(redirector)
800 void write(const char* str) override { contents_.write(str); }
801 void close() override
803 const std::string& newContents = contents_.toString();
804 // TODO: Redirect these for unit tests.
805 if (File::exists(path_, File::returnFalseOnError))
807 const std::string originalContents_ = TextReader::readFileToString(path_);
808 if (originalContents_ == newContents)
813 TextWriter writer(redirector_->openTextOutputFile(path_));
814 writer.writeString(newContents);
819 StringOutputStream contents_;
820 IFileOutputRedirector* redirector_;
823 /********************************************************************
824 * ModificationCheckingFileOutputRedirector
827 class ModificationCheckingFileOutputRedirector : public IFileOutputRedirector
830 explicit ModificationCheckingFileOutputRedirector(IFileOutputRedirector* redirector) :
831 redirector_(redirector)
835 TextOutputStream& standardOutput() override { return redirector_->standardOutput(); }
836 TextOutputStreamPointer openTextOutputFile(const char* filename) override
838 return TextOutputStreamPointer(new ModificationCheckingFileOutputStream(filename, redirector_));
842 IFileOutputRedirector* redirector_;
847 /********************************************************************
848 * CommandLineHelpModule
851 CommandLineHelpModule::CommandLineHelpModule(const IProgramContext& programContext,
852 const std::string& binaryName,
853 const CommandLineModuleMap& modules,
854 const CommandLineModuleGroupList& groups) :
855 impl_(new Impl(programContext, binaryName, modules, groups))
859 CommandLineHelpModule::~CommandLineHelpModule() {}
861 HelpTopicPointer CommandLineHelpModule::createModuleHelpTopic(const ICommandLineModule& module) const
863 return HelpTopicPointer(new ModuleHelpTopic(module, *impl_));
866 void CommandLineHelpModule::addTopic(HelpTopicPointer topic, bool bExported)
868 impl_->rootTopic_.addTopic(std::move(topic), bExported);
871 void CommandLineHelpModule::setShowHidden(bool bHidden)
873 impl_->bHidden_ = bHidden;
876 void CommandLineHelpModule::setModuleOverride(const ICommandLineModule& module)
878 impl_->moduleOverride_ = &module;
881 void CommandLineHelpModule::setOutputRedirector(IFileOutputRedirector* output)
883 impl_->outputRedirector_ = output;
886 int CommandLineHelpModule::run(int argc, char* argv[])
888 // Add internal topics lazily here.
889 addTopic(HelpTopicPointer(new CommandsHelpTopic(*impl_)), false);
891 const char* const exportFormats[] = { "rst", "completion" };
892 std::string exportFormat;
894 options.addOption(StringOption("export").store(&exportFormat).enumValue(exportFormats));
895 CommandLineParser(&options).allowPositionalArguments(true).parse(&argc, argv);
896 if (!exportFormat.empty())
898 ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
899 const std::unique_ptr<IHelpExport> exporter(impl_->createExporter(exportFormat, &redirector));
900 impl_->exportHelp(exporter.get());
904 TextOutputStream& outputFile = impl_->outputRedirector_->standardOutput();
905 TextWriter writer(&outputFile);
906 HelpLinks links(eHelpOutputFormat_Console);
907 initProgramLinks(&links, *impl_);
908 CommandLineHelpContext context(&writer, eHelpOutputFormat_Console, &links, impl_->binaryName_);
909 context.setShowHidden(impl_->bHidden_);
910 if (impl_->moduleOverride_ != nullptr)
912 context.setModuleDisplayName(impl_->programContext_.displayName());
913 impl_->moduleOverride_->writeHelp(context);
916 impl_->context_ = &context;
918 HelpManager helpManager(impl_->rootTopic_, context.writerContext());
921 for (int i = 1; i < argc; ++i)
923 helpManager.enterTopic(argv[i]);
926 catch (const InvalidInputError& ex)
928 fprintf(stderr, "%s\n", ex.what());
931 helpManager.writeCurrentTopic();
935 void CommandLineHelpModule::writeHelp(const CommandLineHelpContext& context) const
937 const HelpWriterContext& writerContext = context.writerContext();
939 if (writerContext.outputFormat() != eHelpOutputFormat_Console)
943 writerContext.writeTextBlock("Usage: [PROGRAM] help [<command>|<topic> [<subtopic> [...]]]");
944 // TODO: More information.