Introduce SimulatorBuilder
[gromacs.git] / src / gromacs / onlinehelp / helpwritercontext.cpp
blob71ff04577e1dd5c2382f17ba525e943513f9dbe2
1 /*
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.
35 /*! \internal \file
36 * \brief
37 * Implements gmx::HelpWriterContext.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_onlinehelp
42 #include "gmxpre.h"
44 #include "helpwritercontext.h"
46 #include <cctype>
48 #include <algorithm>
49 #include <memory>
50 #include <string>
51 #include <vector>
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"
62 namespace gmx
65 namespace
68 //! \internal \addtogroup module_onlinehelp
69 //! \{
71 //! Characters used for reStructuredText title underlining.
72 const char g_titleChars[] = "=-^*~+#'_.";
74 struct t_sandr
76 const char *search;
77 const char *replace;
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[] = {
89 { "\\*", "*" },
90 { "\\=", "=" },
91 { "[REF]", "" },
92 { "[ref]", "" },
93 { "[TT]", "" },
94 { "[tt]", "" },
95 { "[BB]", "" },
96 { "[bb]", "" },
97 { "[IT]", "" },
98 { "[it]", "" },
99 { "[MATH]", "" },
100 { "[math]", "" },
101 { "[CHEVRON]", "<" },
102 { "[chevron]", ">" },
103 { "[MAG]", "|" },
104 { "[mag]", "|" },
105 { "[INT]", "integral" },
106 { "[FROM]", " from " },
107 { "[from]", "" },
108 { "[TO]", " to " },
109 { "[to]", " of" },
110 { "[int]", "" },
111 { "[SUM]", "sum" },
112 { "[sum]", "" },
113 { "[SUB]", "_" },
114 { "[sub]", "" },
115 { "[SQRT]", "sqrt(" },
116 { "[sqrt]", ")" },
117 { "[EXP]", "exp(" },
118 { "[exp]", ")" },
119 { "[LN]", "ln(" },
120 { "[ln]", ")" },
121 { "[LOG]", "log(" },
122 { "[log]", ")" },
123 { "[COS]", "cos(" },
124 { "[cos]", ")" },
125 { "[SIN]", "sin(" },
126 { "[sin]", ")" },
127 { "[TAN]", "tan(" },
128 { "[tan]", ")" },
129 { "[COSH]", "cosh(" },
130 { "[cosh]", ")" },
131 { "[SINH]", "sinh(" },
132 { "[sinh]", ")" },
133 { "[TANH]", "tanh(" },
134 { "[tanh]", ")" },
135 { "[PAR]", "\n\n" },
136 { "[GRK]", "" },
137 { "[grk]", "" }
140 //! List of replacements for reStructuredText output.
141 const t_sandr sandrRst[] = {
142 { "[TT]", "``" },
143 { "[tt]", "``" },
144 { "[BB]", "**" },
145 { "[bb]", "**" },
146 { "[IT]", "*" },
147 { "[it]", "*" },
148 { "[MATH]", "" },
149 { "[math]", "" },
150 { "[CHEVRON]", "<" },
151 { "[chevron]", ">" },
152 { "[MAG]", "\\|" },
153 { "[mag]", "\\|" },
154 { "[INT]", "integral" },
155 { "[FROM]", " from " },
156 { "[from]", "" },
157 { "[TO]", " to " },
158 { "[to]", " of" },
159 { "[int]", "" },
160 { "[SUM]", "sum" },
161 { "[sum]", "" },
162 { "[SUB]", "_" },
163 { "[sub]", "" },
164 { "[SQRT]", "sqrt(" },
165 { "[sqrt]", ")" },
166 { "[EXP]", "exp(" },
167 { "[exp]", ")" },
168 { "[LN]", "ln(" },
169 { "[ln]", ")" },
170 { "[LOG]", "log(" },
171 { "[log]", ")" },
172 { "[COS]", "cos(" },
173 { "[cos]", ")" },
174 { "[SIN]", "sin(" },
175 { "[sin]", ")" },
176 { "[TAN]", "tan(" },
177 { "[tan]", ")" },
178 { "[COSH]", "cosh(" },
179 { "[cosh]", ")" },
180 { "[SINH]", "sinh(" },
181 { "[sinh]", ")" },
182 { "[TANH]", "tanh(" },
183 { "[tanh]", ")" },
184 { "[PAR]", "\n\n" },
185 { "[GRK]", "" },
186 { "[grk]", "" }
189 /*! \brief
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);
199 return result;
202 /*! \brief
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);
211 /*! \brief
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().
217 class IWrapper
219 public:
220 virtual ~IWrapper() {}
222 /*! \brief
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;
234 /*! \brief
235 * Wraps markup output into a single string.
237 class WrapperToString : public IWrapper
239 public:
240 //! Creates a wrapper with the given settings.
241 explicit WrapperToString(const TextLineWrapperSettings &settings)
242 : wrapper_(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_; }
257 private:
258 TextLineWrapper wrapper_;
259 std::string result_;
262 /*! \brief
263 * Wraps markup output into a vector of string (one line per element).
265 class WrapperToVector : public IWrapper
267 public:
268 //! Creates a wrapper with the given settings.
269 explicit WrapperToVector(const TextLineWrapperSettings &settings)
270 : wrapper_(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_; }
286 private:
287 TextLineWrapper wrapper_;
288 std::vector<std::string> result_;
291 /*! \brief
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);
302 return result;
305 /*! \brief
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;
317 std::string result;
318 result.reserve(text.length());
319 for (size_t i = 0; i < text.length(); ++i)
321 if (text[i] == '\n')
323 ++newlineCount;
324 if (newlineCount > 2)
326 continue;
329 else
331 newlineCount = 0;
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);
340 return result;
343 //! \}
345 } // namespace
347 /********************************************************************
348 * HelpLinks::Impl
351 /*! \internal \brief
352 * Private implementation class for HelpLinks.
354 * \ingroup module_onlinehelp
356 class HelpLinks::Impl
358 public:
359 struct LinkItem
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)
378 //! List of links.
379 LinkList links_;
380 //! Output format for which the links are formatted.
381 HelpOutputFormat format_;
384 /********************************************************************
385 * HelpLinks
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);
405 break;
406 case eHelpOutputFormat_Rst:
407 replacement = targetName;
408 break;
409 default:
410 GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
412 impl_->links_.emplace_back(linkName, replacement);
415 /********************************************************************
416 * HelpWriterContext::Impl
419 /*! \internal \brief
420 * Private implementation class for HelpWriterContext.
422 * \ingroup module_onlinehelp
424 class HelpWriterContext::Impl
426 public:
427 /*! \brief
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
436 class SharedState
438 public:
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)
446 /*! \brief
447 * Returns a formatter for formatting options lists for console
448 * output.
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.
469 TextWriter &file_;
470 //! Output format for the help output.
471 HelpOutputFormat format_;
472 //! Links to use.
473 const HelpLinks *links_;
475 private:
476 //! Formatter for console output options.
477 // Never releases ownership.
478 mutable std::unique_ptr<TextTableFormatter> consoleOptionsFormatter_;
480 GMX_DISALLOW_COPY_AND_ASSIGN(SharedState);
483 struct ReplaceItem
485 ReplaceItem(const std::string &search,
486 const std::string &replace)
487 : search(search), replace(replace)
490 std::string search;
491 std::string 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;
517 /*! \brief
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.
530 StatePointer state_;
531 //! List of markup/other replacements.
532 ReplaceList replacements_;
533 //! Number of subsections above this context.
534 int sectionDepth_;
536 private:
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);
552 return result;
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(&paragraph);
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);
584 break;
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);
594 break;
596 default:
597 GMX_THROW(InternalError("Invalid help output format"));
601 /********************************************************************
602 * HelpWriterContext
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)
622 : 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");
655 writeTitle(title);
656 ++impl_->sectionDepth_;
659 std::string
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
679 if (title.empty())
681 return;
683 TextWriter &file = outputFile();
684 file.ensureEmptyLine();
685 switch (outputFormat())
687 case eHelpOutputFormat_Console:
688 file.writeLine(toUpperCase(title));
689 break;
690 case eHelpOutputFormat_Rst:
691 file.writeLine(title);
692 file.writeLine(std::string(title.length(),
693 g_titleChars[impl_->sectionDepth_]));
694 break;
695 default:
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());
733 formatter.clear();
734 formatter.addColumnLine(0, name);
735 formatter.addColumnLine(1, value);
736 if (!defaultValue.empty())
738 formatter.addColumnLine(2, "(" + defaultValue + ")");
740 if (!info.empty())
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);
751 break;
753 case eHelpOutputFormat_Rst:
755 std::string args(value);
756 if (!defaultValue.empty())
758 args.append(" (");
759 args.append(defaultValue);
760 args.append(")");
762 if (!info.empty())
764 args.append(" (");
765 args.append(info);
766 args.append(")");
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));
772 break;
774 default:
775 GMX_THROW(NotImplementedError(
776 "This output format is not implemented"));
780 void HelpWriterContext::writeOptionListEnd() const
784 } // namespace gmx