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::HelpWriterContext.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_onlinehelp
44 #include "helpwritercontext.h"
53 #include "gromacs/onlinehelp/helpformat.h"
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/gmxassert.h"
56 #include "gromacs/utility/programcontext.h"
57 #include "gromacs/utility/stringutil.h"
58 #include "gromacs/utility/textwriter.h"
60 #include "rstparser.h"
68 //! \internal \addtogroup module_onlinehelp
71 //! Characters used for reStructuredText title underlining.
72 const char g_titleChars
[] = "=-^*~+#'_.";
80 /* The order of these arrays is significant. Text search and replace
81 * for each element occurs in order, so earlier changes can induce
82 * subsequent changes even though the original text might not appear
83 * to invoke the latter changes.
84 * TODO: Get rid of this behavior. It makes it very difficult to manage
85 * replacements coming from multiple sources (e.g., hyperlinks).*/
87 //! List of replacements for console output.
88 const t_sandr sandrTty
[] = {
101 { "[CHEVRON]", "<" },
102 { "[chevron]", ">" },
105 { "[INT]", "integral" },
106 { "[FROM]", " from " },
115 { "[SQRT]", "sqrt(" },
129 { "[COSH]", "cosh(" },
131 { "[SINH]", "sinh(" },
133 { "[TANH]", "tanh(" },
140 //! List of replacements for reStructuredText output.
141 const t_sandr sandrRst
[] = {
150 { "[CHEVRON]", "<" },
151 { "[chevron]", ">" },
154 { "[INT]", "integral" },
155 { "[FROM]", " from " },
164 { "[SQRT]", "sqrt(" },
178 { "[COSH]", "cosh(" },
180 { "[SINH]", "sinh(" },
182 { "[TANH]", "tanh(" },
190 * Replaces all entries from a list of replacements.
192 std::string
repall(const std::string
&s
, int nsr
, const t_sandr sa
[])
194 std::string
result(s
);
195 for (int i
= 0; i
< nsr
; ++i
)
197 result
= replaceAll(result
, sa
[i
].search
, sa
[i
].replace
);
203 * Replaces all entries from a list of replacements.
205 template <size_t nsr
>
206 std::string
repall(const std::string
&s
, const t_sandr (&sa
)[nsr
])
208 return repall(s
, nsr
, sa
);
212 * Custom output interface for HelpWriterContext::Impl::processMarkup().
214 * Provides an interface that is used to implement different types of output
215 * from HelpWriterContext::Impl::processMarkup().
220 virtual ~IWrapper() {}
223 * Provides the wrapping settings.
225 * HelpWriterContext::Impl::processMarkup() may provide some default
226 * values for the settings if they are not set; this is the reason the
227 * return value is not const.
229 virtual TextLineWrapperSettings
&settings() = 0;
230 //! Appends the given string to output.
231 virtual void wrap(const std::string
&text
) = 0;
235 * Wraps markup output into a single string.
237 class WrapperToString
: public IWrapper
240 //! Creates a wrapper with the given settings.
241 explicit WrapperToString(const TextLineWrapperSettings
&settings
)
246 TextLineWrapperSettings
&settings() override
248 return wrapper_
.settings();
250 void wrap(const std::string
&text
) override
252 result_
.append(wrapper_
.wrapToString(text
));
254 //! Returns the result string.
255 const std::string
&result() const { return result_
; }
258 TextLineWrapper wrapper_
;
263 * Wraps markup output into a vector of string (one line per element).
265 class WrapperToVector
: public IWrapper
268 //! Creates a wrapper with the given settings.
269 explicit WrapperToVector(const TextLineWrapperSettings
&settings
)
274 TextLineWrapperSettings
&settings() override
276 return wrapper_
.settings();
278 void wrap(const std::string
&text
) override
280 const std::vector
<std::string
> &lines
= wrapper_
.wrapToVector(text
);
281 result_
.insert(result_
.end(), lines
.begin(), lines
.end());
283 //! Returns a vector with the output lines.
284 const std::vector
<std::string
> &result() const { return result_
; }
287 TextLineWrapper wrapper_
;
288 std::vector
<std::string
> result_
;
292 * Makes the string uppercase.
294 * \param[in] text Input text.
295 * \returns \p text with all characters transformed to uppercase.
296 * \throws std::bad_alloc if out of memory.
298 std::string
toUpperCase(const std::string
&text
)
300 std::string
result(text
);
301 std::transform(result
.begin(), result
.end(), result
.begin(), toupper
);
306 * Removes extra newlines from reStructuredText.
308 * \param[in] text Input text.
309 * \returns \p text with all sequences of more than two newlines replaced
310 * with just two newlines.
311 * \throws std::bad_alloc if out of memory.
313 std::string
removeExtraNewlinesRst(const std::string
&text
)
315 // Start from 2, so that all newlines in the beginning get stripped off.
316 int newlineCount
= 2;
318 result
.reserve(text
.length());
319 for (size_t i
= 0; i
< text
.length(); ++i
)
324 if (newlineCount
> 2)
333 result
.push_back(text
[i
]);
335 size_t last
= result
.find_last_not_of('\n');
336 if (last
!= std::string::npos
)
338 result
.resize(last
+ 1);
347 /********************************************************************
352 * Private implementation class for HelpLinks.
354 * \ingroup module_onlinehelp
356 class HelpLinks::Impl
361 LinkItem(const std::string
&linkName
,
362 const std::string
&replacement
)
363 : linkName(linkName
), replacement(replacement
)
366 std::string linkName
;
367 std::string replacement
;
370 //! Shorthand for a list of links.
371 typedef std::vector
<LinkItem
> LinkList
;
373 //! Initializes empty links with the given format.
374 explicit Impl(HelpOutputFormat format
) : format_(format
)
380 //! Output format for which the links are formatted.
381 HelpOutputFormat format_
;
384 /********************************************************************
388 HelpLinks::HelpLinks(HelpOutputFormat format
) : impl_(new Impl(format
))
392 HelpLinks::~HelpLinks()
396 void HelpLinks::addLink(const std::string
&linkName
,
397 const std::string
&targetName
,
398 const std::string
&displayName
)
400 std::string replacement
;
401 switch (impl_
->format_
)
403 case eHelpOutputFormat_Console
:
404 replacement
= repall(displayName
, sandrTty
);
406 case eHelpOutputFormat_Rst
:
407 replacement
= targetName
;
410 GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
412 impl_
->links_
.emplace_back(linkName
, replacement
);
415 /********************************************************************
416 * HelpWriterContext::Impl
420 * Private implementation class for HelpWriterContext.
422 * \ingroup module_onlinehelp
424 class HelpWriterContext::Impl
428 * Shared, non-modifiable state for context objects.
430 * Contents of this structure are shared between all context objects
431 * that are created from a common parent.
432 * This state should not be modified after construction.
434 * \ingroup module_onlinehelp
439 //! Initializes the state with the given parameters.
440 SharedState(TextWriter
*writer
, HelpOutputFormat format
,
441 const HelpLinks
*links
)
442 : file_(*writer
), format_(format
), links_(links
)
447 * Returns a formatter for formatting options lists for console
450 * The formatter is lazily initialized on first access.
452 TextTableFormatter
&consoleOptionsFormatter() const
454 GMX_RELEASE_ASSERT(format_
== eHelpOutputFormat_Console
,
455 "Accessing console formatter for non-console output");
456 if (!consoleOptionsFormatter_
)
458 consoleOptionsFormatter_
= std::make_unique
<TextTableFormatter
>();
459 consoleOptionsFormatter_
->setFirstColumnIndent(1);
460 consoleOptionsFormatter_
->addColumn(nullptr, 7, false);
461 consoleOptionsFormatter_
->addColumn(nullptr, 18, false);
462 consoleOptionsFormatter_
->addColumn(nullptr, 16, false);
463 consoleOptionsFormatter_
->addColumn(nullptr, 28, false);
465 return *consoleOptionsFormatter_
;
468 //! Writer for writing the help.
470 //! Output format for the help output.
471 HelpOutputFormat format_
;
473 const HelpLinks
*links_
;
476 //! Formatter for console output options.
477 // Never releases ownership.
478 mutable std::unique_ptr
<TextTableFormatter
> consoleOptionsFormatter_
;
480 GMX_DISALLOW_COPY_AND_ASSIGN(SharedState
);
485 ReplaceItem(const std::string
&search
,
486 const std::string
&replace
)
487 : search(search
), replace(replace
)
494 //! Smart pointer type for managing the shared state.
495 typedef std::shared_ptr
<const SharedState
> StatePointer
;
496 //! Shorthand for a list of markup/other replacements.
497 typedef std::vector
<ReplaceItem
> ReplaceList
;
499 //! Initializes the context with the given state and section depth.
500 Impl(const StatePointer
&state
, int sectionDepth
)
501 : state_(state
), sectionDepth_(sectionDepth
)
504 //! Copies the context.
505 Impl(const Impl
&) = default;
507 //! Adds a new replacement.
508 void addReplacement(const std::string
&search
,
509 const std::string
&replace
)
511 replacements_
.emplace_back(search
, replace
);
514 //! Replaces links in a given string.
515 std::string
replaceLinks(const std::string
&input
) const;
518 * Process markup and wrap lines within a block of text.
520 * \param[in] text Text to process.
521 * \param wrapper Object used to wrap the text.
523 * The \p wrapper should take care of either writing the text to output
524 * or providing an interface for the caller to retrieve the output.
526 void processMarkup(const std::string
&text
,
527 IWrapper
*wrapper
) const;
529 //! Constant state shared by all child context objects.
531 //! List of markup/other replacements.
532 ReplaceList replacements_
;
533 //! Number of subsections above this context.
537 GMX_DISALLOW_ASSIGN(Impl
);
540 std::string
HelpWriterContext::Impl::replaceLinks(const std::string
&input
) const
542 std::string
result(input
);
543 if (state_
->links_
!= nullptr)
545 HelpLinks::Impl::LinkList::const_iterator link
;
546 for (link
= state_
->links_
->impl_
->links_
.begin();
547 link
!= state_
->links_
->impl_
->links_
.end(); ++link
)
549 result
= replaceAllWords(result
, link
->linkName
, link
->replacement
);
555 void HelpWriterContext::Impl::processMarkup(const std::string
&text
,
556 IWrapper
*wrapper
) const
558 std::string
result(text
);
559 for (ReplaceList::const_iterator i
= replacements_
.begin();
560 i
!= replacements_
.end(); ++i
)
562 result
= replaceAll(result
, i
->search
, i
->replace
);
564 switch (state_
->format_
)
566 case eHelpOutputFormat_Console
:
568 const int baseFirstLineIndent
= wrapper
->settings().firstLineIndent();
569 const int baseIndent
= wrapper
->settings().indent();
570 result
= repall(result
, sandrTty
);
571 result
= replaceLinks(result
);
572 std::string paragraph
;
573 paragraph
.reserve(result
.length());
574 RstParagraphIterator
iter(result
);
575 while (iter
.nextParagraph())
577 iter
.getParagraphText(¶graph
);
578 wrapper
->settings().setFirstLineIndent(baseFirstLineIndent
+ iter
.firstLineIndent());
579 wrapper
->settings().setIndent(baseIndent
+ iter
.indent());
580 wrapper
->wrap(paragraph
);
582 wrapper
->settings().setFirstLineIndent(baseFirstLineIndent
);
583 wrapper
->settings().setIndent(baseIndent
);
586 case eHelpOutputFormat_Rst
:
588 result
= repall(result
, sandrRst
);
589 result
= replaceLinks(result
);
590 result
= replaceAll(result
, "[REF]", "");
591 result
= replaceAll(result
, "[ref]", "");
592 result
= removeExtraNewlinesRst(result
);
593 wrapper
->wrap(result
);
597 GMX_THROW(InternalError("Invalid help output format"));
601 /********************************************************************
605 HelpWriterContext::HelpWriterContext(TextWriter
*writer
, HelpOutputFormat format
)
606 : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(writer
, format
, nullptr)), 0))
610 HelpWriterContext::HelpWriterContext(TextWriter
*writer
, HelpOutputFormat format
,
611 const HelpLinks
*links
)
612 : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(writer
, format
, links
)), 0))
614 if (links
!= nullptr)
616 GMX_RELEASE_ASSERT(links
->impl_
->format_
== format
,
617 "Links must have the same output format as the context");
621 HelpWriterContext::HelpWriterContext(Impl
*impl
)
626 HelpWriterContext::HelpWriterContext(const HelpWriterContext
&other
)
627 : impl_(new Impl(*other
.impl_
))
631 HelpWriterContext::~HelpWriterContext()
635 void HelpWriterContext::setReplacement(const std::string
&search
,
636 const std::string
&replace
)
638 impl_
->addReplacement(search
, replace
);
641 HelpOutputFormat
HelpWriterContext::outputFormat() const
643 return impl_
->state_
->format_
;
646 TextWriter
&HelpWriterContext::outputFile() const
648 return impl_
->state_
->file_
;
651 void HelpWriterContext::enterSubSection(const std::string
&title
)
653 GMX_RELEASE_ASSERT(impl_
->sectionDepth_
- 1 < static_cast<int>(std::strlen(g_titleChars
)),
654 "Too deeply nested subsections");
656 ++impl_
->sectionDepth_
;
660 HelpWriterContext::substituteMarkupAndWrapToString(
661 const TextLineWrapperSettings
&settings
, const std::string
&text
) const
663 WrapperToString
wrapper(settings
);
664 impl_
->processMarkup(text
, &wrapper
);
665 return wrapper
.result();
668 std::vector
<std::string
>
669 HelpWriterContext::substituteMarkupAndWrapToVector(
670 const TextLineWrapperSettings
&settings
, const std::string
&text
) const
672 WrapperToVector
wrapper(settings
);
673 impl_
->processMarkup(text
, &wrapper
);
674 return wrapper
.result();
677 void HelpWriterContext::writeTitle(const std::string
&title
) const
683 TextWriter
&file
= outputFile();
684 file
.ensureEmptyLine();
685 switch (outputFormat())
687 case eHelpOutputFormat_Console
:
688 file
.writeLine(toUpperCase(title
));
690 case eHelpOutputFormat_Rst
:
691 file
.writeLine(title
);
692 file
.writeLine(std::string(title
.length(),
693 g_titleChars
[impl_
->sectionDepth_
]));
696 GMX_THROW(NotImplementedError(
697 "This output format is not implemented"));
699 file
.ensureEmptyLine();
702 void HelpWriterContext::writeTextBlock(const std::string
&text
) const
704 TextLineWrapperSettings settings
;
705 if (outputFormat() == eHelpOutputFormat_Console
)
707 settings
.setLineLength(78);
709 outputFile().writeLine(substituteMarkupAndWrapToString(settings
, text
));
712 void HelpWriterContext::paragraphBreak() const
714 outputFile().ensureEmptyLine();
717 void HelpWriterContext::writeOptionListStart() const
721 void HelpWriterContext::writeOptionItem(const std::string
&name
,
722 const std::string
&value
,
723 const std::string
&defaultValue
,
724 const std::string
&info
,
725 const std::string
&description
) const
727 TextWriter
&file
= outputFile();
728 switch (outputFormat())
730 case eHelpOutputFormat_Console
:
732 TextTableFormatter
&formatter(impl_
->state_
->consoleOptionsFormatter());
734 formatter
.addColumnLine(0, name
);
735 formatter
.addColumnLine(1, value
);
736 if (!defaultValue
.empty())
738 formatter
.addColumnLine(2, "(" + defaultValue
+ ")");
742 formatter
.addColumnLine(3, "(" + info
+ ")");
744 TextLineWrapperSettings settings
;
745 settings
.setIndent(11);
746 settings
.setLineLength(78);
747 std::string formattedDescription
748 = substituteMarkupAndWrapToString(settings
, description
);
749 file
.writeLine(formatter
.formatRow());
750 file
.writeLine(formattedDescription
);
753 case eHelpOutputFormat_Rst
:
755 std::string
args(value
);
756 if (!defaultValue
.empty())
759 args
.append(defaultValue
);
768 file
.writeLine(formatString("``%s`` %s", name
.c_str(), args
.c_str()));
769 TextLineWrapperSettings settings
;
770 settings
.setIndent(4);
771 file
.writeLine(substituteMarkupAndWrapToString(settings
, description
));
775 GMX_THROW(NotImplementedError(
776 "This output format is not implemented"));
780 void HelpWriterContext::writeOptionListEnd() const