Tidy: modernize-use-nullptr
[alexxy/gromacs.git] / src / gromacs / onlinehelp / helpwritercontext.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015,2016,2017, 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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::HelpWriterContext.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_onlinehelp
41  */
42 #include "gmxpre.h"
43
44 #include "helpwritercontext.h"
45
46 #include <cctype>
47
48 #include <algorithm>
49 #include <memory>
50 #include <string>
51 #include <vector>
52
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"
59
60 #include "rstparser.h"
61
62 namespace gmx
63 {
64
65 namespace
66 {
67
68 //! \internal \addtogroup module_onlinehelp
69 //! \{
70
71 //! Characters used for reStructuredText title underlining.
72 const char g_titleChars[] = "=-^*~+#'_.";
73
74 struct t_sandr
75 {
76     const char *search;
77     const char *replace;
78 };
79
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).*/
86
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]", "" }
138 };
139
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]", "" }
187 };
188
189 /*! \brief
190  * Replaces all entries from a list of replacements.
191  */
192 std::string repall(const std::string &s, int nsr, const t_sandr sa[])
193 {
194     std::string result(s);
195     for (int i = 0; i < nsr; ++i)
196     {
197         result = replaceAll(result, sa[i].search, sa[i].replace);
198     }
199     return result;
200 }
201
202 /*! \brief
203  * Replaces all entries from a list of replacements.
204  */
205 template <size_t nsr>
206 std::string repall(const std::string &s, const t_sandr (&sa)[nsr])
207 {
208     return repall(s, nsr, sa);
209 }
210
211 /*! \brief
212  * Custom output interface for HelpWriterContext::Impl::processMarkup().
213  *
214  * Provides an interface that is used to implement different types of output
215  * from HelpWriterContext::Impl::processMarkup().
216  */
217 class IWrapper
218 {
219     public:
220         virtual ~IWrapper() {}
221
222         /*! \brief
223          * Provides the wrapping settings.
224          *
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.
228          */
229         virtual TextLineWrapperSettings &settings() = 0;
230         //! Appends the given string to output.
231         virtual void wrap(const std::string &text)  = 0;
232 };
233
234 /*! \brief
235  * Wraps markup output into a single string.
236  */
237 class WrapperToString : public IWrapper
238 {
239     public:
240         //! Creates a wrapper with the given settings.
241         explicit WrapperToString(const TextLineWrapperSettings &settings)
242             : wrapper_(settings)
243         {
244         }
245
246         virtual TextLineWrapperSettings &settings()
247         {
248             return wrapper_.settings();
249         }
250         virtual void wrap(const std::string &text)
251         {
252             result_.append(wrapper_.wrapToString(text));
253         }
254         //! Returns the result string.
255         const std::string &result() const { return result_; }
256
257     private:
258         TextLineWrapper         wrapper_;
259         std::string             result_;
260 };
261
262 /*! \brief
263  * Wraps markup output into a vector of string (one line per element).
264  */
265 class WrapperToVector : public IWrapper
266 {
267     public:
268         //! Creates a wrapper with the given settings.
269         explicit WrapperToVector(const TextLineWrapperSettings &settings)
270             : wrapper_(settings)
271         {
272         }
273
274         virtual TextLineWrapperSettings &settings()
275         {
276             return wrapper_.settings();
277         }
278         virtual void wrap(const std::string &text)
279         {
280             const std::vector<std::string> &lines = wrapper_.wrapToVector(text);
281             result_.insert(result_.end(), lines.begin(), lines.end());
282         }
283         //! Returns a vector with the output lines.
284         const std::vector<std::string> &result() const { return result_; }
285
286     private:
287         TextLineWrapper          wrapper_;
288         std::vector<std::string> result_;
289 };
290
291 /*! \brief
292  * Makes the string uppercase.
293  *
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.
297  */
298 std::string toUpperCase(const std::string &text)
299 {
300     std::string result(text);
301     std::transform(result.begin(), result.end(), result.begin(), toupper);
302     return result;
303 }
304
305 /*! \brief
306  * Removes extra newlines from reStructuredText.
307  *
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.
312  */
313 std::string removeExtraNewlinesRst(const std::string &text)
314 {
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)
320     {
321         if (text[i] == '\n')
322         {
323             ++newlineCount;
324             if (newlineCount > 2)
325             {
326                 continue;
327             }
328         }
329         else
330         {
331             newlineCount = 0;
332         }
333         result.push_back(text[i]);
334     }
335     size_t last = result.find_last_not_of('\n');
336     if (last != std::string::npos)
337     {
338         result.resize(last + 1);
339     }
340     return result;
341 }
342
343 //! \}
344
345 }   // namespace
346
347 /********************************************************************
348  * HelpLinks::Impl
349  */
350
351 /*! \internal \brief
352  * Private implementation class for HelpLinks.
353  *
354  * \ingroup module_onlinehelp
355  */
356 class HelpLinks::Impl
357 {
358     public:
359         struct LinkItem
360         {
361             LinkItem(const std::string &linkName,
362                      const std::string &replacement)
363                 : linkName(linkName), replacement(replacement)
364             {
365             }
366             std::string         linkName;
367             std::string         replacement;
368         };
369
370         //! Shorthand for a list of links.
371         typedef std::vector<LinkItem> LinkList;
372
373         //! Initializes empty links with the given format.
374         explicit Impl(HelpOutputFormat format) : format_(format)
375         {
376         }
377
378         //! List of links.
379         LinkList          links_;
380         //! Output format for which the links are formatted.
381         HelpOutputFormat  format_;
382 };
383
384 /********************************************************************
385  * HelpLinks
386  */
387
388 HelpLinks::HelpLinks(HelpOutputFormat format) : impl_(new Impl(format))
389 {
390 }
391
392 HelpLinks::~HelpLinks()
393 {
394 }
395
396 void HelpLinks::addLink(const std::string &linkName,
397                         const std::string &targetName,
398                         const std::string &displayName)
399 {
400     std::string replacement;
401     switch (impl_->format_)
402     {
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");
411     }
412     impl_->links_.emplace_back(linkName, replacement);
413 }
414
415 /********************************************************************
416  * HelpWriterContext::Impl
417  */
418
419 /*! \internal \brief
420  * Private implementation class for HelpWriterContext.
421  *
422  * \ingroup module_onlinehelp
423  */
424 class HelpWriterContext::Impl
425 {
426     public:
427         /*! \brief
428          * Shared, non-modifiable state for context objects.
429          *
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.
433          *
434          * \ingroup module_onlinehelp
435          */
436         class SharedState
437         {
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)
443                 {
444                 }
445
446                 /*! \brief
447                  * Returns a formatter for formatting options lists for console
448                  * output.
449                  *
450                  * The formatter is lazily initialized on first access.
451                  */
452                 TextTableFormatter &consoleOptionsFormatter() const
453                 {
454                     GMX_RELEASE_ASSERT(format_ == eHelpOutputFormat_Console,
455                                        "Accessing console formatter for non-console output");
456                     if (!consoleOptionsFormatter_)
457                     {
458                         consoleOptionsFormatter_.reset(new 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);
464                     }
465                     return *consoleOptionsFormatter_;
466                 }
467
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_;
474
475             private:
476                 //! Formatter for console output options.
477                 // Never releases ownership.
478                 mutable std::unique_ptr<TextTableFormatter> consoleOptionsFormatter_;
479
480                 GMX_DISALLOW_COPY_AND_ASSIGN(SharedState);
481         };
482
483         struct ReplaceItem
484         {
485             ReplaceItem(const std::string &search,
486                         const std::string &replace)
487                 : search(search), replace(replace)
488             {
489             }
490             std::string         search;
491             std::string         replace;
492         };
493
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;
498
499         //! Initializes the context with the given state and section depth.
500         Impl(const StatePointer &state, int sectionDepth)
501             : state_(state), sectionDepth_(sectionDepth)
502         {
503         }
504         //! Copies the context.
505         Impl(const Impl &)            = default;
506
507         //! Adds a new replacement.
508         void addReplacement(const std::string &search,
509                             const std::string &replace)
510         {
511             replacements_.emplace_back(search, replace);
512         }
513
514         //! Replaces links in a given string.
515         std::string replaceLinks(const std::string &input) const;
516
517         /*! \brief
518          * Process markup and wrap lines within a block of text.
519          *
520          * \param[in] text     Text to process.
521          * \param     wrapper  Object used to wrap the text.
522          *
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.
525          */
526         void processMarkup(const std::string &text,
527                            IWrapper          *wrapper) const;
528
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_;
535
536     private:
537         GMX_DISALLOW_ASSIGN(Impl);
538 };
539
540 std::string HelpWriterContext::Impl::replaceLinks(const std::string &input) const
541 {
542     std::string result(input);
543     if (state_->links_ != nullptr)
544     {
545         HelpLinks::Impl::LinkList::const_iterator link;
546         for (link  = state_->links_->impl_->links_.begin();
547              link != state_->links_->impl_->links_.end(); ++link)
548         {
549             result = replaceAllWords(result, link->linkName, link->replacement);
550         }
551     }
552     return result;
553 }
554
555 void HelpWriterContext::Impl::processMarkup(const std::string &text,
556                                             IWrapper          *wrapper) const
557 {
558     std::string result(text);
559     for (ReplaceList::const_iterator i = replacements_.begin();
560          i != replacements_.end(); ++i)
561     {
562         result = replaceAll(result, i->search, i->replace);
563     }
564     switch (state_->format_)
565     {
566         case eHelpOutputFormat_Console:
567         {
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())
576             {
577                 iter.getParagraphText(&paragraph);
578                 wrapper->settings().setFirstLineIndent(baseFirstLineIndent + iter.firstLineIndent());
579                 wrapper->settings().setIndent(baseIndent + iter.indent());
580                 wrapper->wrap(paragraph);
581             }
582             wrapper->settings().setFirstLineIndent(baseFirstLineIndent);
583             wrapper->settings().setIndent(baseIndent);
584             break;
585         }
586         case eHelpOutputFormat_Rst:
587         {
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;
595         }
596         default:
597             GMX_THROW(InternalError("Invalid help output format"));
598     }
599 }
600
601 /********************************************************************
602  * HelpWriterContext
603  */
604
605 HelpWriterContext::HelpWriterContext(TextWriter *writer, HelpOutputFormat format)
606     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(writer, format, nullptr)), 0))
607 {
608 }
609
610 HelpWriterContext::HelpWriterContext(TextWriter *writer, HelpOutputFormat format,
611                                      const HelpLinks *links)
612     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(writer, format, links)), 0))
613 {
614     if (links != nullptr)
615     {
616         GMX_RELEASE_ASSERT(links->impl_->format_ == format,
617                            "Links must have the same output format as the context");
618     }
619 }
620
621 HelpWriterContext::HelpWriterContext(Impl *impl)
622     : impl_(impl)
623 {
624 }
625
626 HelpWriterContext::HelpWriterContext(const HelpWriterContext &other)
627     : impl_(new Impl(*other.impl_))
628 {
629 }
630
631 HelpWriterContext::~HelpWriterContext()
632 {
633 }
634
635 void HelpWriterContext::setReplacement(const std::string &search,
636                                        const std::string &replace)
637 {
638     impl_->addReplacement(search, replace);
639 }
640
641 HelpOutputFormat HelpWriterContext::outputFormat() const
642 {
643     return impl_->state_->format_;
644 }
645
646 TextWriter &HelpWriterContext::outputFile() const
647 {
648     return impl_->state_->file_;
649 }
650
651 void HelpWriterContext::enterSubSection(const std::string &title)
652 {
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_;
657 }
658
659 std::string
660 HelpWriterContext::substituteMarkupAndWrapToString(
661         const TextLineWrapperSettings &settings, const std::string &text) const
662 {
663     WrapperToString wrapper(settings);
664     impl_->processMarkup(text, &wrapper);
665     return wrapper.result();
666 }
667
668 std::vector<std::string>
669 HelpWriterContext::substituteMarkupAndWrapToVector(
670         const TextLineWrapperSettings &settings, const std::string &text) const
671 {
672     WrapperToVector wrapper(settings);
673     impl_->processMarkup(text, &wrapper);
674     return wrapper.result();
675 }
676
677 void HelpWriterContext::writeTitle(const std::string &title) const
678 {
679     if (title.empty())
680     {
681         return;
682     }
683     TextWriter &file = outputFile();
684     file.ensureEmptyLine();
685     switch (outputFormat())
686     {
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"));
698     }
699     file.ensureEmptyLine();
700 }
701
702 void HelpWriterContext::writeTextBlock(const std::string &text) const
703 {
704     TextLineWrapperSettings settings;
705     if (outputFormat() == eHelpOutputFormat_Console)
706     {
707         settings.setLineLength(78);
708     }
709     outputFile().writeLine(substituteMarkupAndWrapToString(settings, text));
710 }
711
712 void HelpWriterContext::paragraphBreak() const
713 {
714     outputFile().ensureEmptyLine();
715 }
716
717 void HelpWriterContext::writeOptionListStart() const
718 {
719 }
720
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
726 {
727     TextWriter &file = outputFile();
728     switch (outputFormat())
729     {
730         case eHelpOutputFormat_Console:
731         {
732             TextTableFormatter &formatter(impl_->state_->consoleOptionsFormatter());
733             formatter.clear();
734             formatter.addColumnLine(0, name);
735             formatter.addColumnLine(1, value);
736             if (!defaultValue.empty())
737             {
738                 formatter.addColumnLine(2, "(" + defaultValue + ")");
739             }
740             if (!info.empty())
741             {
742                 formatter.addColumnLine(3, "(" + info + ")");
743             }
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;
752         }
753         case eHelpOutputFormat_Rst:
754         {
755             std::string args(value);
756             if (!defaultValue.empty())
757             {
758                 args.append(" (");
759                 args.append(defaultValue);
760                 args.append(")");
761             }
762             if (!info.empty())
763             {
764                 args.append(" (");
765                 args.append(info);
766                 args.append(")");
767             }
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;
773         }
774         default:
775             GMX_THROW(NotImplementedError(
776                               "This output format is not implemented"));
777     }
778 }
779
780 void HelpWriterContext::writeOptionListEnd() const
781 {
782 }
783
784 } // namespace gmx