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