680828df70bc38938f65f5a5c1d67e7a4aaea06e
[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, 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 <string>
50 #include <vector>
51
52 #include <boost/shared_ptr.hpp>
53
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/file.h"
56 #include "gromacs/utility/gmxassert.h"
57 #include "gromacs/utility/programcontext.h"
58 #include "gromacs/utility/stringutil.h"
59
60 namespace gmx
61 {
62
63 namespace
64 {
65
66 //! \internal \addtogroup module_onlinehelp
67 //! \{
68
69 //! Characters used for reStructuredText title underlining.
70 const char g_titleChars[] = "=-^*~+#'_.";
71
72 struct t_sandr
73 {
74     const char *search;
75     const char *replace;
76 };
77
78 /* The order of these arrays is significant. Text search and replace
79  * for each element occurs in order, so earlier changes can induce
80  * subsequent changes even though the original text might not appear
81  * to invoke the latter changes.
82  * TODO: Get rid of this behavior. It makes it very difficult to manage
83  * replacements coming from multiple sources (e.g., hyperlinks).*/
84
85 //! List of replacements for console output.
86 const t_sandr sandrTty[] = {
87     { "\\*", "*" },
88     { "\\=", "=" },
89     { "[REF]", "" },
90     { "[ref]", "" },
91     { "[TT]", "" },
92     { "[tt]", "" },
93     { "[BB]", "" },
94     { "[bb]", "" },
95     { "[IT]", "" },
96     { "[it]", "" },
97     { "[MATH]", "" },
98     { "[math]", "" },
99     { "[CHEVRON]", "<" },
100     { "[chevron]", ">" },
101     { "[MAG]", "|" },
102     { "[mag]", "|" },
103     { "[INT]", "integral" },
104     { "[FROM]", " from " },
105     { "[from]", "" },
106     { "[TO]", " to " },
107     { "[to]", " of" },
108     { "[int]", "" },
109     { "[SUM]", "sum" },
110     { "[sum]", "" },
111     { "[SUB]", "_" },
112     { "[sub]", "" },
113     { "[SQRT]", "sqrt(" },
114     { "[sqrt]", ")" },
115     { "[EXP]", "exp(" },
116     { "[exp]", ")" },
117     { "[LN]", "ln(" },
118     { "[ln]", ")" },
119     { "[LOG]", "log(" },
120     { "[log]", ")" },
121     { "[COS]", "cos(" },
122     { "[cos]", ")" },
123     { "[SIN]", "sin(" },
124     { "[sin]", ")" },
125     { "[TAN]", "tan(" },
126     { "[tan]", ")" },
127     { "[COSH]", "cosh(" },
128     { "[cosh]", ")" },
129     { "[SINH]", "sinh(" },
130     { "[sinh]", ")" },
131     { "[TANH]", "tanh(" },
132     { "[tanh]", ")" },
133     { "[PAR]", "\n\n" },
134     { "[GRK]", "" },
135     { "[grk]", "" }
136 };
137
138 //! List of replacements for reStructuredText output.
139 const t_sandr sandrRst[] = {
140     { "[TT]", "``" },
141     { "[tt]", "``" },
142     { "[BB]", "**" },
143     { "[bb]", "**" },
144     { "[IT]", "*" },
145     { "[it]", "*" },
146     { "[MATH]", "" },
147     { "[math]", "" },
148     { "[CHEVRON]", "<" },
149     { "[chevron]", ">" },
150     { "[MAG]", "\\|" },
151     { "[mag]", "\\|" },
152     { "[INT]", "integral" },
153     { "[FROM]", " from " },
154     { "[from]", "" },
155     { "[TO]", " to " },
156     { "[to]", " of" },
157     { "[int]", "" },
158     { "[SUM]", "sum" },
159     { "[sum]", "" },
160     { "[SUB]", "_" },
161     { "[sub]", "" },
162     { "[SQRT]", "sqrt(" },
163     { "[sqrt]", ")" },
164     { "[EXP]", "exp(" },
165     { "[exp]", ")" },
166     { "[LN]", "ln(" },
167     { "[ln]", ")" },
168     { "[LOG]", "log(" },
169     { "[log]", ")" },
170     { "[COS]", "cos(" },
171     { "[cos]", ")" },
172     { "[SIN]", "sin(" },
173     { "[sin]", ")" },
174     { "[TAN]", "tan(" },
175     { "[tan]", ")" },
176     { "[COSH]", "cosh(" },
177     { "[cosh]", ")" },
178     { "[SINH]", "sinh(" },
179     { "[sinh]", ")" },
180     { "[TANH]", "tanh(" },
181     { "[tanh]", ")" },
182     { "[PAR]", "\n\n" },
183     { "[GRK]", "" },
184     { "[grk]", "" }
185 };
186
187 /*! \brief
188  * Replaces all entries from a list of replacements.
189  */
190 std::string repall(const std::string &s, int nsr, const t_sandr sa[])
191 {
192     std::string result(s);
193     for (int i = 0; i < nsr; ++i)
194     {
195         result = replaceAll(result, sa[i].search, sa[i].replace);
196     }
197     return result;
198 }
199
200 /*! \brief
201  * Replaces all entries from a list of replacements.
202  */
203 template <size_t nsr>
204 std::string repall(const std::string &s, const t_sandr (&sa)[nsr])
205 {
206     return repall(s, nsr, sa);
207 }
208
209 /*! \brief
210  * Custom output interface for HelpWriterContext::Impl::processMarkup().
211  *
212  * Provides an interface that is used to implement different types of output
213  * from HelpWriterContext::Impl::processMarkup().
214  */
215 class WrapperInterface
216 {
217     public:
218         virtual ~WrapperInterface() {}
219
220         /*! \brief
221          * Provides the wrapping settings.
222          *
223          * HelpWriterContext::Impl::processMarkup() may provide some default
224          * values for the settings if they are not set; this is the reason the
225          * return value is not const.
226          */
227         virtual TextLineWrapperSettings &settings() = 0;
228         //! Appends the given string to output.
229         virtual void wrap(const std::string &text)  = 0;
230 };
231
232 /*! \brief
233  * Wraps markup output into a single string.
234  */
235 class WrapperToString : public WrapperInterface
236 {
237     public:
238         //! Creates a wrapper with the given settings.
239         explicit WrapperToString(const TextLineWrapperSettings &settings)
240             : wrapper_(settings)
241         {
242         }
243
244         virtual TextLineWrapperSettings &settings()
245         {
246             return wrapper_.settings();
247         }
248         virtual void wrap(const std::string &text)
249         {
250             result_.append(wrapper_.wrapToString(text));
251         }
252         //! Returns the result string.
253         const std::string &result() const { return result_; }
254
255     private:
256         TextLineWrapper         wrapper_;
257         std::string             result_;
258 };
259
260 /*! \brief
261  * Wraps markup output into a vector of string (one line per element).
262  */
263 class WrapperToVector : public WrapperInterface
264 {
265     public:
266         //! Creates a wrapper with the given settings.
267         explicit WrapperToVector(const TextLineWrapperSettings &settings)
268             : wrapper_(settings)
269         {
270         }
271
272         virtual TextLineWrapperSettings &settings()
273         {
274             return wrapper_.settings();
275         }
276         virtual void wrap(const std::string &text)
277         {
278             const std::vector<std::string> &lines = wrapper_.wrapToVector(text);
279             result_.insert(result_.end(), lines.begin(), lines.end());
280         }
281         //! Returns a vector with the output lines.
282         const std::vector<std::string> &result() const { return result_; }
283
284     private:
285         TextLineWrapper          wrapper_;
286         std::vector<std::string> result_;
287 };
288
289 /*! \brief
290  * Makes the string uppercase.
291  *
292  * \param[in] text  Input text.
293  * \returns   \p text with all characters transformed to uppercase.
294  * \throws    std::bad_alloc if out of memory.
295  */
296 std::string toUpperCase(const std::string &text)
297 {
298     std::string result(text);
299     std::transform(result.begin(), result.end(), result.begin(), toupper);
300     return result;
301 }
302
303 /*! \brief
304  * Removes extra newlines from reStructuredText.
305  *
306  * \param[in] text  Input text.
307  * \returns   \p text with all sequences of more than two newlines replaced
308  *     with just two newlines.
309  * \throws    std::bad_alloc if out of memory.
310  */
311 std::string removeExtraNewlinesRst(const std::string &text)
312 {
313     // Start from 2, so that all newlines in the beginning get stripped off.
314     int         newlineCount = 2;
315     std::string result;
316     result.reserve(text.length());
317     for (size_t i = 0; i < text.length(); ++i)
318     {
319         if (text[i] == '\n')
320         {
321             ++newlineCount;
322             if (newlineCount > 2)
323             {
324                 continue;
325             }
326         }
327         else
328         {
329             newlineCount = 0;
330         }
331         result.push_back(text[i]);
332     }
333     size_t last = result.find_last_not_of('\n');
334     if (last != std::string::npos)
335     {
336         result.resize(last + 1);
337     }
338     return result;
339 }
340
341 /*! \brief
342  * Returns `true` if a list item starts in \p text at \p index.
343  *
344  * Does not throw.
345  */
346 bool startsListItem(const std::string &text, size_t index)
347 {
348     if (text.length() <= index + 1)
349     {
350         return false;
351     }
352     if (text[index] == '*' && std::isspace(text[index+1]))
353     {
354         return true;
355     }
356     if (std::isdigit(text[index]))
357     {
358         while (index < text.length() && std::isdigit(text[index]))
359         {
360             ++index;
361         }
362         if (text.length() > index + 1 && text[index] == '.'
363             && std::isspace(text[index+1]))
364         {
365             return true;
366         }
367     }
368     return false;
369 }
370
371 /*! \brief
372  * Returns `true` if a table starts in \p text at \p index.
373  *
374  * The function only inspects the first line for something that looks like a
375  * reStructuredText table, and accepts also some malformed tables.
376  * Any issues should be apparent when Sphinx parses the reStructuredText
377  * export, so full validation is not done here.
378  *
379  * Does not throw.
380  */
381 bool startsTable(const std::string &text, size_t index)
382 {
383     if (text[index] == '=')
384     {
385         while (index < text.length() && text[index] != '\n')
386         {
387             if (text[index] != '=' && !std::isspace(text[index]))
388             {
389                 return false;
390             }
391             ++index;
392         }
393         return true;
394     }
395     else if (text[index] == '+')
396     {
397         while (index < text.length() && text[index] != '\n')
398         {
399             if (text[index] != '-' && text[index] != '+')
400             {
401                 return false;
402             }
403             ++index;
404         }
405         return true;
406     }
407     return false;
408 }
409
410 /*! \brief
411  * Returns `true` if a line in \p text starting at \p index is a title underline.
412  *
413  * Does not throw.
414  */
415 bool isTitleUnderline(const std::string &text, size_t index)
416 {
417     const char firstChar = text[index];
418     if (std::ispunct(firstChar))
419     {
420         while (index < text.length() && text[index] != '\n')
421         {
422             if (text[index] != firstChar)
423             {
424                 return false;
425             }
426             ++index;
427         }
428         return true;
429     }
430     return false;
431 }
432
433 //! \}
434
435 }   // namespace
436
437 /********************************************************************
438  * HelpLinks::Impl
439  */
440
441 /*! \internal \brief
442  * Private implementation class for HelpLinks.
443  *
444  * \ingroup module_onlinehelp
445  */
446 class HelpLinks::Impl
447 {
448     public:
449         struct LinkItem
450         {
451             LinkItem(const std::string &linkName,
452                      const std::string &replacement)
453                 : linkName(linkName), replacement(replacement)
454             {
455             }
456             std::string         linkName;
457             std::string         replacement;
458         };
459
460         //! Shorthand for a list of links.
461         typedef std::vector<LinkItem> LinkList;
462
463         //! Initializes empty links with the given format.
464         explicit Impl(HelpOutputFormat format) : format_(format)
465         {
466         }
467
468         //! List of links.
469         LinkList          links_;
470         //! Output format for which the links are formatted.
471         HelpOutputFormat  format_;
472 };
473
474 /********************************************************************
475  * HelpLinks
476  */
477
478 HelpLinks::HelpLinks(HelpOutputFormat format) : impl_(new Impl(format))
479 {
480 }
481
482 HelpLinks::~HelpLinks()
483 {
484 }
485
486 void HelpLinks::addLink(const std::string &linkName,
487                         const std::string &targetName,
488                         const std::string &displayName)
489 {
490     std::string replacement;
491     switch (impl_->format_)
492     {
493         case eHelpOutputFormat_Console:
494             replacement = repall(displayName, sandrTty);
495             break;
496         case eHelpOutputFormat_Rst:
497             replacement = targetName;
498             break;
499         default:
500             GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
501     }
502     impl_->links_.push_back(Impl::LinkItem(linkName, replacement));
503 }
504
505 /********************************************************************
506  * HelpWriterContext::Impl
507  */
508
509 /*! \internal \brief
510  * Private implementation class for HelpWriterContext.
511  *
512  * \ingroup module_onlinehelp
513  */
514 class HelpWriterContext::Impl
515 {
516     public:
517         /*! \brief
518          * Shared, non-modifiable state for context objects.
519          *
520          * Contents of this structure are shared between all context objects
521          * that are created from a common parent.
522          * This state should not be modified after construction.
523          *
524          * \ingroup module_onlinehelp
525          */
526         struct SharedState
527         {
528             //! Initializes the state with the given parameters.
529             SharedState(File *file, HelpOutputFormat format,
530                         const HelpLinks *links)
531                 : file_(*file), format_(format), links_(links)
532             {
533             }
534
535             //! Output file to which the help is written.
536             File                   &file_;
537             //! Output format for the help output.
538             HelpOutputFormat        format_;
539             //! Links to use.
540             const HelpLinks        *links_;
541         };
542
543         struct ReplaceItem
544         {
545             ReplaceItem(const std::string &search,
546                         const std::string &replace)
547                 : search(search), replace(replace)
548             {
549             }
550             std::string         search;
551             std::string         replace;
552         };
553
554         //! Smart pointer type for managing the shared state.
555         typedef boost::shared_ptr<const SharedState> StatePointer;
556         //! Shorthand for a list of markup/other replacements.
557         typedef std::vector<ReplaceItem> ReplaceList;
558
559         //! Initializes the context with the given state and section depth.
560         Impl(const StatePointer &state, int sectionDepth)
561             : state_(state), sectionDepth_(sectionDepth)
562         {
563             initDefaultReplacements();
564         }
565
566         //! Initializes default replacements for the chosen output format.
567         void initDefaultReplacements();
568         //! Adds a new replacement.
569         void addReplacement(const std::string &search,
570                             const std::string &replace)
571         {
572             replacements_.push_back(ReplaceItem(search, replace));
573         }
574
575         //! Replaces links in a given string.
576         std::string replaceLinks(const std::string &input) const;
577
578         /*! \brief
579          * Process markup and wrap lines within a block of text.
580          *
581          * \param[in] text     Text to process.
582          * \param     wrapper  Object used to wrap the text.
583          *
584          * The \p wrapper should take care of either writing the text to output
585          * or providing an interface for the caller to retrieve the output.
586          */
587         void processMarkup(const std::string &text,
588                            WrapperInterface  *wrapper) const;
589
590         //! Constant state shared by all child context objects.
591         StatePointer            state_;
592         //! List of markup/other replacements.
593         ReplaceList             replacements_;
594         //! Number of subsections above this context.
595         int                     sectionDepth_;
596
597     private:
598         GMX_DISALLOW_ASSIGN(Impl);
599 };
600
601 void HelpWriterContext::Impl::initDefaultReplacements()
602 {
603     const char *program = getProgramContext().programName();
604     addReplacement("[PROGRAM]", program);
605 }
606
607 std::string HelpWriterContext::Impl::replaceLinks(const std::string &input) const
608 {
609     std::string result(input);
610     if (state_->links_ != NULL)
611     {
612         HelpLinks::Impl::LinkList::const_iterator link;
613         for (link  = state_->links_->impl_->links_.begin();
614              link != state_->links_->impl_->links_.end(); ++link)
615         {
616             result = replaceAllWords(result, link->linkName, link->replacement);
617         }
618     }
619     return result;
620 }
621
622 void HelpWriterContext::Impl::processMarkup(const std::string &text,
623                                             WrapperInterface  *wrapper) const
624 {
625     std::string result(text);
626     for (ReplaceList::const_iterator i = replacements_.begin();
627          i != replacements_.end(); ++i)
628     {
629         result = replaceAll(result, i->search, i->replace);
630     }
631     switch (state_->format_)
632     {
633         case eHelpOutputFormat_Console:
634         {
635             const int   baseFirstLineIndent = wrapper->settings().firstLineIndent();
636             const int   baseIndent          = wrapper->settings().indent();
637             result = repall(result, sandrTty);
638             result = replaceLinks(result);
639             std::string paragraph;
640             paragraph.reserve(result.length());
641             size_t      i             = 0;
642             int         nextBreakSize = 0;
643             bool        bLiteral      = false;
644             while (i < result.length())
645             {
646                 while (i < result.length() && result[i] == '\n')
647                 {
648                     ++i;
649                 }
650                 if (i == result.length())
651                 {
652                     break;
653                 }
654                 const int breakSize     = nextBreakSize;
655                 int       currentLine   = 0;
656                 bool      bLineStart    = true;
657                 int       currentIndent = 0;
658                 int       firstIndent   = 0;
659                 int       indent        = 0;
660                 paragraph.clear();
661                 for (;; ++i)
662                 {
663                     if (result[i] == '\n' || i == result.length())
664                     {
665                         if (currentLine == 0)
666                         {
667                             firstIndent = currentIndent;
668                         }
669                         else if (currentLine == 1)
670                         {
671                             indent = currentIndent;
672                         }
673                         ++currentLine;
674                         bLineStart    = true;
675                         currentIndent = 0;
676                         if (i + 1 >= result.length() || result[i + 1] == '\n')
677                         {
678                             nextBreakSize = 2;
679                             break;
680                         }
681                         if (!bLiteral)
682                         {
683                             if (!std::isspace(result[i - 1]))
684                             {
685                                 paragraph.push_back(' ');
686                             }
687                             continue;
688                         }
689                     }
690                     else if (bLineStart)
691                     {
692                         if (std::isspace(result[i]))
693                         {
694                             ++currentIndent;
695                             continue;
696                         }
697                         else if (startsListItem(result, i))
698                         {
699                             if (currentLine > 0)
700                             {
701                                 while (i > 0 && result[i - 1] != '\n')
702                                 {
703                                     --i;
704                                 }
705                                 paragraph     = stripString(paragraph);
706                                 nextBreakSize = 1;
707                                 break;
708                             }
709                             int prefixLength = 0;
710                             while (!std::isspace(result[i + prefixLength]))
711                             {
712                                 ++prefixLength;
713                             }
714                             while (i + prefixLength < result.length()
715                                    && std::isspace(result[i + prefixLength]))
716                             {
717                                 ++prefixLength;
718                             }
719                             indent = currentIndent + prefixLength;
720                         }
721                         else if (currentLine == 0 && startsTable(result, i))
722                         {
723                             bLiteral = true;
724                         }
725                         else if (currentLine == 1 && isTitleUnderline(result, i))
726                         {
727                             // TODO: Nicer formatting that shares
728                             // implementation with writeTitle() and honors the
729                             // nesting depths etc.
730                             if (i > 0)
731                             {
732                                 paragraph[paragraph.length() - 1] = '\n';
733                             }
734                         }
735                         bLineStart = false;
736                     }
737                     paragraph.push_back(result[i]);
738                 }
739                 if (endsWith(paragraph, "::"))
740                 {
741                     bLiteral = true;
742                     if (paragraph.length() == 2)
743                     {
744                         if (breakSize == 0)
745                         {
746                             nextBreakSize = 0;
747                         }
748                         continue;
749                     }
750                     if (paragraph[paragraph.length() - 3] == ' ')
751                     {
752                         paragraph.resize(paragraph.length() - 3);
753                     }
754                     else
755                     {
756                         paragraph.resize(paragraph.length() - 1);
757                     }
758                 }
759                 else
760                 {
761                     bLiteral = false;
762                 }
763                 if (breakSize > 0)
764                 {
765                     wrapper->wrap(std::string(breakSize, '\n'));
766                 }
767                 wrapper->settings().setFirstLineIndent(baseFirstLineIndent + firstIndent);
768                 wrapper->settings().setIndent(baseIndent + indent);
769                 wrapper->wrap(paragraph);
770                 wrapper->settings().setFirstLineIndent(baseFirstLineIndent);
771                 wrapper->settings().setIndent(baseIndent);
772             }
773             break;
774         }
775         case eHelpOutputFormat_Rst:
776         {
777             result = repall(result, sandrRst);
778             result = replaceLinks(result);
779             result = replaceAll(result, "[REF]", "");
780             result = replaceAll(result, "[ref]", "");
781             result = removeExtraNewlinesRst(result);
782             wrapper->wrap(result);
783             break;
784         }
785         default:
786             GMX_THROW(InternalError("Invalid help output format"));
787     }
788 }
789
790 /********************************************************************
791  * HelpWriterContext
792  */
793
794 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format)
795     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, NULL)), 0))
796 {
797 }
798
799 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format,
800                                      const HelpLinks *links)
801     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, links)), 0))
802 {
803     if (links != NULL)
804     {
805         GMX_RELEASE_ASSERT(links->impl_->format_ == format,
806                            "Links must have the same output format as the context");
807     }
808 }
809
810 HelpWriterContext::HelpWriterContext(Impl *impl)
811     : impl_(impl)
812 {
813 }
814
815 HelpWriterContext::HelpWriterContext(const HelpWriterContext &other)
816     : impl_(new Impl(*other.impl_))
817 {
818 }
819
820 HelpWriterContext::~HelpWriterContext()
821 {
822 }
823
824 void HelpWriterContext::setReplacement(const std::string &search,
825                                        const std::string &replace)
826 {
827     impl_->addReplacement(search, replace);
828 }
829
830 HelpOutputFormat HelpWriterContext::outputFormat() const
831 {
832     return impl_->state_->format_;
833 }
834
835 File &HelpWriterContext::outputFile() const
836 {
837     return impl_->state_->file_;
838 }
839
840 void HelpWriterContext::enterSubSection(const std::string &title)
841 {
842     GMX_RELEASE_ASSERT(impl_->sectionDepth_ - 1 < static_cast<int>(std::strlen(g_titleChars)),
843                        "Too deeply nested subsections");
844     writeTitle(title);
845     ++impl_->sectionDepth_;
846 }
847
848 std::string
849 HelpWriterContext::substituteMarkupAndWrapToString(
850         const TextLineWrapperSettings &settings, const std::string &text) const
851 {
852     WrapperToString wrapper(settings);
853     impl_->processMarkup(text, &wrapper);
854     return wrapper.result();
855 }
856
857 std::vector<std::string>
858 HelpWriterContext::substituteMarkupAndWrapToVector(
859         const TextLineWrapperSettings &settings, const std::string &text) const
860 {
861     WrapperToVector wrapper(settings);
862     impl_->processMarkup(text, &wrapper);
863     return wrapper.result();
864 }
865
866 void HelpWriterContext::writeTitle(const std::string &title) const
867 {
868     if (title.empty())
869     {
870         return;
871     }
872     File &file = outputFile();
873     switch (outputFormat())
874     {
875         case eHelpOutputFormat_Console:
876             file.writeLine(toUpperCase(title));
877             file.writeLine();
878             break;
879         case eHelpOutputFormat_Rst:
880             file.writeLine(title);
881             file.writeLine(std::string(title.length(),
882                                        g_titleChars[impl_->sectionDepth_]));
883             break;
884         default:
885             GMX_THROW(NotImplementedError(
886                               "This output format is not implemented"));
887     }
888 }
889
890 void HelpWriterContext::writeTextBlock(const std::string &text) const
891 {
892     TextLineWrapperSettings settings;
893     if (outputFormat() == eHelpOutputFormat_Console)
894     {
895         settings.setLineLength(78);
896     }
897     outputFile().writeLine(substituteMarkupAndWrapToString(settings, text));
898 }
899
900 void HelpWriterContext::writeOptionListStart() const
901 {
902 }
903
904 void HelpWriterContext::writeOptionItem(const std::string &name,
905                                         const std::string &args,
906                                         const std::string &description) const
907 {
908     File &file = outputFile();
909     switch (outputFormat())
910     {
911         case eHelpOutputFormat_Console:
912             // TODO: Generalize this when there is need for it; the current,
913             // special implementation is in CommandLineHelpWriter.
914             GMX_THROW(NotImplementedError("Option item formatting for console output not implemented"));
915             break;
916         case eHelpOutputFormat_Rst:
917         {
918             file.writeLine(formatString("``%s`` %s", name.c_str(), args.c_str()));
919             TextLineWrapperSettings settings;
920             settings.setIndent(4);
921             file.writeLine(substituteMarkupAndWrapToString(settings, description));
922             break;
923         }
924         default:
925             GMX_THROW(NotImplementedError(
926                               "This output format is not implemented"));
927     }
928 }
929
930 void HelpWriterContext::writeOptionListEnd() const
931 {
932 }
933
934 } // namespace gmx