2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013,2014,2015,2016 by the GROMACS development team.
5 * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Implements gmx::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/exceptions.h"
56 #include "gromacs/utility/gmxassert.h"
57 #include "gromacs/utility/programcontext.h"
58 #include "gromacs/utility/stringutil.h"
59 #include "gromacs/utility/textwriter.h"
61 #include "rstparser.h"
69 //! \internal \addtogroup module_onlinehelp
72 //! Characters used for reStructuredText title underlining.
73 const char g_titleChars
[] = "=-^*~+#'_.";
81 /* The order of these arrays is significant. Text search and replace
82 * for each element occurs in order, so earlier changes can induce
83 * subsequent changes even though the original text might not appear
84 * to invoke the latter changes.
85 * TODO: Get rid of this behavior. It makes it very difficult to manage
86 * replacements coming from multiple sources (e.g., hyperlinks).*/
88 //! List of replacements for console output.
89 const t_sandr sandrTty
[] = {
90 { "\\*", "*" }, { "\\=", "=" }, { "[REF]", "" }, { "[ref]", "" },
91 { "[TT]", "" }, { "[tt]", "" }, { "[BB]", "" }, { "[bb]", "" },
92 { "[IT]", "" }, { "[it]", "" }, { "[MATH]", "" }, { "[math]", "" },
93 { "[CHEVRON]", "<" }, { "[chevron]", ">" }, { "[MAG]", "|" }, { "[mag]", "|" },
94 { "[INT]", "integral" }, { "[FROM]", " from " }, { "[from]", "" }, { "[TO]", " to " },
95 { "[to]", " of" }, { "[int]", "" }, { "[SUM]", "sum" }, { "[sum]", "" },
96 { "[SUB]", "_" }, { "[sub]", "" }, { "[SQRT]", "sqrt(" }, { "[sqrt]", ")" },
97 { "[EXP]", "exp(" }, { "[exp]", ")" }, { "[LN]", "ln(" }, { "[ln]", ")" },
98 { "[LOG]", "log(" }, { "[log]", ")" }, { "[COS]", "cos(" }, { "[cos]", ")" },
99 { "[SIN]", "sin(" }, { "[sin]", ")" }, { "[TAN]", "tan(" }, { "[tan]", ")" },
100 { "[COSH]", "cosh(" }, { "[cosh]", ")" }, { "[SINH]", "sinh(" }, { "[sinh]", ")" },
101 { "[TANH]", "tanh(" }, { "[tanh]", ")" }, { "[PAR]", "\n\n" }, { "[GRK]", "" },
105 //! List of replacements for reStructuredText output.
106 const t_sandr sandrRst
[] = {
107 { "[TT]", "``" }, { "[tt]", "``" }, { "[BB]", "**" }, { "[bb]", "**" },
108 { "[IT]", "*" }, { "[it]", "*" }, { "[MATH]", "" }, { "[math]", "" },
109 { "[CHEVRON]", "<" }, { "[chevron]", ">" }, { "[MAG]", "\\|" }, { "[mag]", "\\|" },
110 { "[INT]", "integral" }, { "[FROM]", " from " }, { "[from]", "" }, { "[TO]", " to " },
111 { "[to]", " of" }, { "[int]", "" }, { "[SUM]", "sum" }, { "[sum]", "" },
112 { "[SUB]", "_" }, { "[sub]", "" }, { "[SQRT]", "sqrt(" }, { "[sqrt]", ")" },
113 { "[EXP]", "exp(" }, { "[exp]", ")" }, { "[LN]", "ln(" }, { "[ln]", ")" },
114 { "[LOG]", "log(" }, { "[log]", ")" }, { "[COS]", "cos(" }, { "[cos]", ")" },
115 { "[SIN]", "sin(" }, { "[sin]", ")" }, { "[TAN]", "tan(" }, { "[tan]", ")" },
116 { "[COSH]", "cosh(" }, { "[cosh]", ")" }, { "[SINH]", "sinh(" }, { "[sinh]", ")" },
117 { "[TANH]", "tanh(" }, { "[tanh]", ")" }, { "[PAR]", "\n\n" }, { "[GRK]", "" },
122 * Replaces all entries from a list of replacements.
124 std::string
repall(const std::string
& s
, int nsr
, const t_sandr sa
[])
126 std::string
result(s
);
127 for (int i
= 0; i
< nsr
; ++i
)
129 result
= replaceAll(result
, sa
[i
].search
, sa
[i
].replace
);
135 * Replaces all entries from a list of replacements.
138 std::string
repall(const std::string
& s
, const t_sandr (&sa
)[nsr
])
140 return repall(s
, nsr
, sa
);
144 * Custom output interface for HelpWriterContext::Impl::processMarkup().
146 * Provides an interface that is used to implement different types of output
147 * from HelpWriterContext::Impl::processMarkup().
152 virtual ~IWrapper() {}
155 * Provides the wrapping settings.
157 * HelpWriterContext::Impl::processMarkup() may provide some default
158 * values for the settings if they are not set; this is the reason the
159 * return value is not const.
161 virtual TextLineWrapperSettings
& settings() = 0;
162 //! Appends the given string to output.
163 virtual void wrap(const std::string
& text
) = 0;
167 * Wraps markup output into a single string.
169 class WrapperToString
: public IWrapper
172 //! Creates a wrapper with the given settings.
173 explicit WrapperToString(const TextLineWrapperSettings
& settings
) : wrapper_(settings
) {}
175 TextLineWrapperSettings
& settings() override
{ return wrapper_
.settings(); }
176 void wrap(const std::string
& text
) override
{ result_
.append(wrapper_
.wrapToString(text
)); }
177 //! Returns the result string.
178 const std::string
& result() const { return result_
; }
181 TextLineWrapper wrapper_
;
186 * Wraps markup output into a vector of string (one line per element).
188 class WrapperToVector
: public IWrapper
191 //! Creates a wrapper with the given settings.
192 explicit WrapperToVector(const TextLineWrapperSettings
& settings
) : wrapper_(settings
) {}
194 TextLineWrapperSettings
& settings() override
{ return wrapper_
.settings(); }
195 void wrap(const std::string
& text
) override
197 const std::vector
<std::string
>& lines
= wrapper_
.wrapToVector(text
);
198 result_
.insert(result_
.end(), lines
.begin(), lines
.end());
200 //! Returns a vector with the output lines.
201 const std::vector
<std::string
>& result() const { return result_
; }
204 TextLineWrapper wrapper_
;
205 std::vector
<std::string
> result_
;
209 * Makes the string uppercase.
211 * \param[in] text Input text.
212 * \returns \p text with all characters transformed to uppercase.
213 * \throws std::bad_alloc if out of memory.
215 std::string
toUpperCase(const std::string
& text
)
217 std::string
result(text
);
218 std::transform(result
.begin(), result
.end(), result
.begin(), toupper
);
223 * Removes extra newlines from reStructuredText.
225 * \param[in] text Input text.
226 * \returns \p text with all sequences of more than two newlines replaced
227 * with just two newlines.
228 * \throws std::bad_alloc if out of memory.
230 std::string
removeExtraNewlinesRst(const std::string
& text
)
232 // Start from 2, so that all newlines in the beginning get stripped off.
233 int newlineCount
= 2;
235 result
.reserve(text
.length());
236 for (size_t i
= 0; i
< text
.length(); ++i
)
241 if (newlineCount
> 2)
250 result
.push_back(text
[i
]);
252 size_t last
= result
.find_last_not_of('\n');
253 if (last
!= std::string::npos
)
255 result
.resize(last
+ 1);
264 /********************************************************************
269 * Private implementation class for HelpLinks.
271 * \ingroup module_onlinehelp
273 class HelpLinks::Impl
278 LinkItem(const std::string
& linkName
, const std::string
& replacement
) :
280 replacement(replacement
)
283 std::string linkName
;
284 std::string replacement
;
287 //! Shorthand for a list of links.
288 typedef std::vector
<LinkItem
> LinkList
;
290 //! Initializes empty links with the given format.
291 explicit Impl(HelpOutputFormat format
) : format_(format
) {}
295 //! Output format for which the links are formatted.
296 HelpOutputFormat format_
;
299 /********************************************************************
303 HelpLinks::HelpLinks(HelpOutputFormat format
) : impl_(new Impl(format
)) {}
305 HelpLinks::~HelpLinks() {}
307 void HelpLinks::addLink(const std::string
& linkName
, const std::string
& targetName
, const std::string
& displayName
)
309 std::string replacement
;
310 switch (impl_
->format_
)
312 case eHelpOutputFormat_Console
: replacement
= repall(displayName
, sandrTty
); break;
313 case eHelpOutputFormat_Rst
: replacement
= targetName
; break;
314 default: GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
316 impl_
->links_
.emplace_back(linkName
, replacement
);
319 /********************************************************************
320 * HelpWriterContext::Impl
324 * Private implementation class for HelpWriterContext.
326 * \ingroup module_onlinehelp
328 class HelpWriterContext::Impl
332 * Shared, non-modifiable state for context objects.
334 * Contents of this structure are shared between all context objects
335 * that are created from a common parent.
336 * This state should not be modified after construction.
338 * \ingroup module_onlinehelp
343 //! Initializes the state with the given parameters.
344 SharedState(TextWriter
* writer
, HelpOutputFormat format
, const HelpLinks
* links
) :
352 * Returns a formatter for formatting options lists for console
355 * The formatter is lazily initialized on first access.
357 TextTableFormatter
& consoleOptionsFormatter() const
359 GMX_RELEASE_ASSERT(format_
== eHelpOutputFormat_Console
,
360 "Accessing console formatter for non-console output");
361 if (!consoleOptionsFormatter_
)
363 consoleOptionsFormatter_
= std::make_unique
<TextTableFormatter
>();
364 consoleOptionsFormatter_
->setFirstColumnIndent(1);
365 consoleOptionsFormatter_
->addColumn(nullptr, 7, false);
366 consoleOptionsFormatter_
->addColumn(nullptr, 18, false);
367 consoleOptionsFormatter_
->addColumn(nullptr, 16, false);
368 consoleOptionsFormatter_
->addColumn(nullptr, 28, false);
370 return *consoleOptionsFormatter_
;
373 //! Writer for writing the help.
375 //! Output format for the help output.
376 HelpOutputFormat format_
;
378 const HelpLinks
* links_
;
381 //! Formatter for console output options.
382 // Never releases ownership.
383 mutable std::unique_ptr
<TextTableFormatter
> consoleOptionsFormatter_
;
385 GMX_DISALLOW_COPY_AND_ASSIGN(SharedState
);
390 ReplaceItem(const std::string
& search
, const std::string
& replace
) :
399 //! Smart pointer type for managing the shared state.
400 typedef std::shared_ptr
<const SharedState
> StatePointer
;
401 //! Shorthand for a list of markup/other replacements.
402 typedef std::vector
<ReplaceItem
> ReplaceList
;
404 //! Initializes the context with the given state and section depth.
405 Impl(const StatePointer
& state
, int sectionDepth
) : state_(state
), sectionDepth_(sectionDepth
)
408 //! Copies the context.
409 Impl(const Impl
&) = default;
411 //! Adds a new replacement.
412 void addReplacement(const std::string
& search
, const std::string
& replace
)
414 replacements_
.emplace_back(search
, replace
);
417 //! Replaces links in a given string.
418 std::string
replaceLinks(const std::string
& input
) const;
421 * Process markup and wrap lines within a block of text.
423 * \param[in] text Text to process.
424 * \param wrapper Object used to wrap the text.
426 * The \p wrapper should take care of either writing the text to output
427 * or providing an interface for the caller to retrieve the output.
429 void processMarkup(const std::string
& text
, IWrapper
* wrapper
) const;
431 //! Constant state shared by all child context objects.
433 //! List of markup/other replacements.
434 ReplaceList replacements_
;
435 //! Number of subsections above this context.
439 GMX_DISALLOW_ASSIGN(Impl
);
442 std::string
HelpWriterContext::Impl::replaceLinks(const std::string
& input
) const
444 std::string
result(input
);
445 if (state_
->links_
!= nullptr)
447 HelpLinks::Impl::LinkList::const_iterator link
;
448 for (link
= state_
->links_
->impl_
->links_
.begin();
449 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 {}