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::HelpWriterContext.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_onlinehelp
45 #include "helpwritercontext.h"
54 #include "gromacs/onlinehelp/helpformat.h"
55 #include "gromacs/utility/classhelpers.h"
56 #include "gromacs/utility/exceptions.h"
57 #include "gromacs/utility/gmxassert.h"
58 #include "gromacs/utility/programcontext.h"
59 #include "gromacs/utility/stringutil.h"
60 #include "gromacs/utility/textwriter.h"
62 #include "rstparser.h"
70 //! \internal \addtogroup module_onlinehelp
73 //! Characters used for reStructuredText title underlining.
74 const char g_titleChars[] = "=-^*~+#'_.";
82 /* The order of these arrays is significant. Text search and replace
83 * for each element occurs in order, so earlier changes can induce
84 * subsequent changes even though the original text might not appear
85 * to invoke the latter changes.
86 * TODO: Get rid of this behavior. It makes it very difficult to manage
87 * replacements coming from multiple sources (e.g., hyperlinks).*/
89 //! List of replacements for console output.
90 const t_sandr sandrTty[] = {
91 { "\\*", "*" }, { "\\=", "=" }, { "[REF]", "" }, { "[ref]", "" },
92 { "[TT]", "" }, { "[tt]", "" }, { "[BB]", "" }, { "[bb]", "" },
93 { "[IT]", "" }, { "[it]", "" }, { "[MATH]", "" }, { "[math]", "" },
94 { "[CHEVRON]", "<" }, { "[chevron]", ">" }, { "[MAG]", "|" }, { "[mag]", "|" },
95 { "[INT]", "integral" }, { "[FROM]", " from " }, { "[from]", "" }, { "[TO]", " to " },
96 { "[to]", " of" }, { "[int]", "" }, { "[SUM]", "sum" }, { "[sum]", "" },
97 { "[SUB]", "_" }, { "[sub]", "" }, { "[SQRT]", "sqrt(" }, { "[sqrt]", ")" },
98 { "[EXP]", "exp(" }, { "[exp]", ")" }, { "[LN]", "ln(" }, { "[ln]", ")" },
99 { "[LOG]", "log(" }, { "[log]", ")" }, { "[COS]", "cos(" }, { "[cos]", ")" },
100 { "[SIN]", "sin(" }, { "[sin]", ")" }, { "[TAN]", "tan(" }, { "[tan]", ")" },
101 { "[COSH]", "cosh(" }, { "[cosh]", ")" }, { "[SINH]", "sinh(" }, { "[sinh]", ")" },
102 { "[TANH]", "tanh(" }, { "[tanh]", ")" }, { "[PAR]", "\n\n" }, { "[GRK]", "" },
106 //! List of replacements for reStructuredText output.
107 const t_sandr sandrRst[] = {
108 { "[TT]", "``" }, { "[tt]", "``" }, { "[BB]", "**" }, { "[bb]", "**" },
109 { "[IT]", "*" }, { "[it]", "*" }, { "[MATH]", "" }, { "[math]", "" },
110 { "[CHEVRON]", "<" }, { "[chevron]", ">" }, { "[MAG]", "\\|" }, { "[mag]", "\\|" },
111 { "[INT]", "integral" }, { "[FROM]", " from " }, { "[from]", "" }, { "[TO]", " to " },
112 { "[to]", " of" }, { "[int]", "" }, { "[SUM]", "sum" }, { "[sum]", "" },
113 { "[SUB]", "_" }, { "[sub]", "" }, { "[SQRT]", "sqrt(" }, { "[sqrt]", ")" },
114 { "[EXP]", "exp(" }, { "[exp]", ")" }, { "[LN]", "ln(" }, { "[ln]", ")" },
115 { "[LOG]", "log(" }, { "[log]", ")" }, { "[COS]", "cos(" }, { "[cos]", ")" },
116 { "[SIN]", "sin(" }, { "[sin]", ")" }, { "[TAN]", "tan(" }, { "[tan]", ")" },
117 { "[COSH]", "cosh(" }, { "[cosh]", ")" }, { "[SINH]", "sinh(" }, { "[sinh]", ")" },
118 { "[TANH]", "tanh(" }, { "[tanh]", ")" }, { "[PAR]", "\n\n" }, { "[GRK]", "" },
123 * Replaces all entries from a list of replacements.
125 std::string repall(const std::string& s, int nsr, const t_sandr sa[])
127 std::string result(s);
128 for (int i = 0; i < nsr; ++i)
130 result = replaceAll(result, sa[i].search, sa[i].replace);
136 * Replaces all entries from a list of replacements.
139 std::string repall(const std::string& s, const t_sandr (&sa)[nsr])
141 return repall(s, nsr, sa);
145 * Custom output interface for HelpWriterContext::Impl::processMarkup().
147 * Provides an interface that is used to implement different types of output
148 * from HelpWriterContext::Impl::processMarkup().
153 virtual ~IWrapper() {}
156 * Provides the wrapping settings.
158 * HelpWriterContext::Impl::processMarkup() may provide some default
159 * values for the settings if they are not set; this is the reason the
160 * return value is not const.
162 virtual TextLineWrapperSettings& settings() = 0;
163 //! Appends the given string to output.
164 virtual void wrap(const std::string& text) = 0;
168 * Wraps markup output into a single string.
170 class WrapperToString : public IWrapper
173 //! Creates a wrapper with the given settings.
174 explicit WrapperToString(const TextLineWrapperSettings& settings) : wrapper_(settings) {}
176 TextLineWrapperSettings& settings() override { return wrapper_.settings(); }
177 void wrap(const std::string& text) override { result_.append(wrapper_.wrapToString(text)); }
178 //! Returns the result string.
179 const std::string& result() const { return result_; }
182 TextLineWrapper wrapper_;
187 * Wraps markup output into a vector of string (one line per element).
189 class WrapperToVector : public IWrapper
192 //! Creates a wrapper with the given settings.
193 explicit WrapperToVector(const TextLineWrapperSettings& settings) : wrapper_(settings) {}
195 TextLineWrapperSettings& settings() override { return wrapper_.settings(); }
196 void wrap(const std::string& text) override
198 const std::vector<std::string>& lines = wrapper_.wrapToVector(text);
199 result_.insert(result_.end(), lines.begin(), lines.end());
201 //! Returns a vector with the output lines.
202 const std::vector<std::string>& result() const { return result_; }
205 TextLineWrapper wrapper_;
206 std::vector<std::string> result_;
210 * Makes the string uppercase.
212 * \param[in] text Input text.
213 * \returns \p text with all characters transformed to uppercase.
214 * \throws std::bad_alloc if out of memory.
216 std::string toUpperCase(const std::string& text)
218 std::string result(text);
219 std::transform(result.begin(), result.end(), result.begin(), toupper);
224 * Removes extra newlines from reStructuredText.
226 * \param[in] text Input text.
227 * \returns \p text with all sequences of more than two newlines replaced
228 * with just two newlines.
229 * \throws std::bad_alloc if out of memory.
231 std::string removeExtraNewlinesRst(const std::string& text)
233 // Start from 2, so that all newlines in the beginning get stripped off.
234 int newlineCount = 2;
236 result.reserve(text.length());
237 for (size_t i = 0; i < text.length(); ++i)
242 if (newlineCount > 2)
251 result.push_back(text[i]);
253 size_t last = result.find_last_not_of('\n');
254 if (last != std::string::npos)
256 result.resize(last + 1);
265 /********************************************************************
270 * Private implementation class for HelpLinks.
272 * \ingroup module_onlinehelp
274 class HelpLinks::Impl
279 LinkItem(const std::string& linkName, const std::string& replacement) :
281 replacement(replacement)
284 std::string linkName;
285 std::string replacement;
288 //! Shorthand for a list of links.
289 typedef std::vector<LinkItem> LinkList;
291 //! Initializes empty links with the given format.
292 explicit Impl(HelpOutputFormat format) : format_(format) {}
296 //! Output format for which the links are formatted.
297 HelpOutputFormat format_;
300 /********************************************************************
304 HelpLinks::HelpLinks(HelpOutputFormat format) : impl_(new Impl(format)) {}
306 HelpLinks::~HelpLinks() {}
308 void HelpLinks::addLink(const std::string& linkName, const std::string& targetName, const std::string& displayName)
310 std::string replacement;
311 switch (impl_->format_)
313 case eHelpOutputFormat_Console: replacement = repall(displayName, sandrTty); break;
314 case eHelpOutputFormat_Rst: replacement = targetName; break;
315 default: GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
317 impl_->links_.emplace_back(linkName, replacement);
320 /********************************************************************
321 * HelpWriterContext::Impl
325 * Private implementation class for HelpWriterContext.
327 * \ingroup module_onlinehelp
329 class HelpWriterContext::Impl
333 * Shared, non-modifiable state for context objects.
335 * Contents of this structure are shared between all context objects
336 * that are created from a common parent.
337 * This state should not be modified after construction.
339 * \ingroup module_onlinehelp
344 //! Initializes the state with the given parameters.
345 SharedState(TextWriter* writer, HelpOutputFormat format, const HelpLinks* links) :
353 * Returns a formatter for formatting options lists for console
356 * The formatter is lazily initialized on first access.
358 TextTableFormatter& consoleOptionsFormatter() const
360 GMX_RELEASE_ASSERT(format_ == eHelpOutputFormat_Console,
361 "Accessing console formatter for non-console output");
362 if (!consoleOptionsFormatter_)
364 consoleOptionsFormatter_ = std::make_unique<TextTableFormatter>();
365 consoleOptionsFormatter_->setFirstColumnIndent(1);
366 consoleOptionsFormatter_->addColumn(nullptr, 7, false);
367 consoleOptionsFormatter_->addColumn(nullptr, 18, false);
368 consoleOptionsFormatter_->addColumn(nullptr, 16, false);
369 consoleOptionsFormatter_->addColumn(nullptr, 28, false);
371 return *consoleOptionsFormatter_;
374 //! Writer for writing the help.
376 //! Output format for the help output.
377 HelpOutputFormat format_;
379 const HelpLinks* links_;
382 //! Formatter for console output options.
383 // Never releases ownership.
384 mutable std::unique_ptr<TextTableFormatter> consoleOptionsFormatter_;
386 GMX_DISALLOW_COPY_AND_ASSIGN(SharedState);
391 ReplaceItem(const std::string& search, const std::string& replace) :
400 //! Smart pointer type for managing the shared state.
401 typedef std::shared_ptr<const SharedState> StatePointer;
402 //! Shorthand for a list of markup/other replacements.
403 typedef std::vector<ReplaceItem> ReplaceList;
405 //! Initializes the context with the given state and section depth.
406 Impl(const StatePointer& state, int sectionDepth) : state_(state), sectionDepth_(sectionDepth)
409 //! Copies the context.
410 Impl(const Impl&) = default;
412 //! Adds a new replacement.
413 void addReplacement(const std::string& search, const std::string& replace)
415 replacements_.emplace_back(search, replace);
418 //! Replaces links in a given string.
419 std::string replaceLinks(const std::string& input) const;
422 * Process markup and wrap lines within a block of text.
424 * \param[in] text Text to process.
425 * \param wrapper Object used to wrap the text.
427 * The \p wrapper should take care of either writing the text to output
428 * or providing an interface for the caller to retrieve the output.
430 void processMarkup(const std::string& text, IWrapper* wrapper) const;
432 //! Constant state shared by all child context objects.
434 //! List of markup/other replacements.
435 ReplaceList replacements_;
436 //! Number of subsections above this context.
440 GMX_DISALLOW_ASSIGN(Impl);
443 std::string HelpWriterContext::Impl::replaceLinks(const std::string& input) const
445 std::string result(input);
446 if (state_->links_ != nullptr)
448 HelpLinks::Impl::LinkList::const_iterator link;
449 for (link = state_->links_->impl_->links_.begin(); link != state_->links_->impl_->links_.end(); ++link)
451 result = replaceAllWords(result, link->linkName, link->replacement);
457 void HelpWriterContext::Impl::processMarkup(const std::string& text, IWrapper* wrapper) const
459 std::string result(text);
460 for (ReplaceList::const_iterator i = replacements_.begin(); i != replacements_.end(); ++i)
462 result = replaceAll(result, i->search, i->replace);
464 switch (state_->format_)
466 case eHelpOutputFormat_Console:
468 const int baseFirstLineIndent = wrapper->settings().firstLineIndent();
469 const int baseIndent = wrapper->settings().indent();
470 result = repall(result, sandrTty);
471 result = replaceLinks(result);
472 std::string paragraph;
473 paragraph.reserve(result.length());
474 RstParagraphIterator iter(result);
475 while (iter.nextParagraph())
477 iter.getParagraphText(¶graph);
478 wrapper->settings().setFirstLineIndent(baseFirstLineIndent + iter.firstLineIndent());
479 wrapper->settings().setIndent(baseIndent + iter.indent());
480 wrapper->wrap(paragraph);
482 wrapper->settings().setFirstLineIndent(baseFirstLineIndent);
483 wrapper->settings().setIndent(baseIndent);
486 case eHelpOutputFormat_Rst:
488 result = repall(result, sandrRst);
489 result = replaceLinks(result);
490 result = replaceAll(result, "[REF]", "");
491 result = replaceAll(result, "[ref]", "");
492 result = removeExtraNewlinesRst(result);
493 wrapper->wrap(result);
496 default: GMX_THROW(InternalError("Invalid help output format"));
500 /********************************************************************
504 HelpWriterContext::HelpWriterContext(TextWriter* writer, HelpOutputFormat format) :
505 impl_(new Impl(Impl::StatePointer(new Impl::SharedState(writer, format, nullptr)), 0))
509 HelpWriterContext::HelpWriterContext(TextWriter* writer, HelpOutputFormat format, const HelpLinks* links) :
510 impl_(new Impl(Impl::StatePointer(new Impl::SharedState(writer, format, links)), 0))
512 if (links != nullptr)
514 GMX_RELEASE_ASSERT(links->impl_->format_ == format,
515 "Links must have the same output format as the context");
519 HelpWriterContext::HelpWriterContext(Impl* impl) : impl_(impl) {}
521 HelpWriterContext::HelpWriterContext(const HelpWriterContext& other) : impl_(new Impl(*other.impl_))
525 HelpWriterContext::~HelpWriterContext() {}
527 void HelpWriterContext::setReplacement(const std::string& search, const std::string& replace)
529 impl_->addReplacement(search, replace);
532 HelpOutputFormat HelpWriterContext::outputFormat() const
534 return impl_->state_->format_;
537 TextWriter& HelpWriterContext::outputFile() const
539 return impl_->state_->file_;
542 void HelpWriterContext::enterSubSection(const std::string& title)
544 GMX_RELEASE_ASSERT(impl_->sectionDepth_ - 1 < static_cast<int>(std::strlen(g_titleChars)),
545 "Too deeply nested subsections");
547 ++impl_->sectionDepth_;
550 std::string HelpWriterContext::substituteMarkupAndWrapToString(const TextLineWrapperSettings& settings,
551 const std::string& text) const
553 WrapperToString wrapper(settings);
554 impl_->processMarkup(text, &wrapper);
555 return wrapper.result();
558 std::vector<std::string> HelpWriterContext::substituteMarkupAndWrapToVector(const TextLineWrapperSettings& settings,
559 const std::string& text) const
561 WrapperToVector wrapper(settings);
562 impl_->processMarkup(text, &wrapper);
563 return wrapper.result();
566 void HelpWriterContext::writeTitle(const std::string& title) const
572 TextWriter& file = outputFile();
573 file.ensureEmptyLine();
574 switch (outputFormat())
576 case eHelpOutputFormat_Console: file.writeLine(toUpperCase(title)); break;
577 case eHelpOutputFormat_Rst:
578 file.writeLine(title);
579 file.writeLine(std::string(title.length(), g_titleChars[impl_->sectionDepth_]));
581 default: GMX_THROW(NotImplementedError("This output format is not implemented"));
583 file.ensureEmptyLine();
586 void HelpWriterContext::writeTextBlock(const std::string& text) const
588 TextLineWrapperSettings settings;
589 if (outputFormat() == eHelpOutputFormat_Console)
591 settings.setLineLength(78);
593 outputFile().writeLine(substituteMarkupAndWrapToString(settings, text));
596 void HelpWriterContext::paragraphBreak() const
598 outputFile().ensureEmptyLine();
601 void HelpWriterContext::writeOptionListStart() const {}
603 void HelpWriterContext::writeOptionItem(const std::string& name,
604 const std::string& value,
605 const std::string& defaultValue,
606 const std::string& info,
607 const std::string& description) const
609 TextWriter& file = outputFile();
610 switch (outputFormat())
612 case eHelpOutputFormat_Console:
614 TextTableFormatter& formatter(impl_->state_->consoleOptionsFormatter());
616 formatter.addColumnLine(0, name);
617 formatter.addColumnLine(1, value);
618 if (!defaultValue.empty())
620 formatter.addColumnLine(2, "(" + defaultValue + ")");
624 formatter.addColumnLine(3, "(" + info + ")");
626 TextLineWrapperSettings settings;
627 settings.setIndent(11);
628 settings.setLineLength(78);
629 std::string formattedDescription = substituteMarkupAndWrapToString(settings, description);
630 file.writeLine(formatter.formatRow());
631 file.writeLine(formattedDescription);
634 case eHelpOutputFormat_Rst:
636 std::string args(value);
637 if (!defaultValue.empty())
640 args.append(defaultValue);
649 file.writeLine(formatString("``%s`` %s", name.c_str(), args.c_str()));
650 TextLineWrapperSettings settings;
651 settings.setIndent(4);
652 file.writeLine(substituteMarkupAndWrapToString(settings, description));
655 default: GMX_THROW(NotImplementedError("This output format is not implemented"));
659 void HelpWriterContext::writeOptionListEnd() const {}