Resolve one TODO for command line help tests
[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 #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 WrapperInterface
218 {
219     public:
220         virtual ~WrapperInterface() {}
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 WrapperInterface
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 WrapperInterface
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_.push_back(Impl::LinkItem(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         struct SharedState
437         {
438             //! Initializes the state with the given parameters.
439             SharedState(File *file, HelpOutputFormat format,
440                         const HelpLinks *links)
441                 : file_(*file), format_(format), links_(links)
442             {
443             }
444
445             //! Output file to which the help is written.
446             File                   &file_;
447             //! Output format for the help output.
448             HelpOutputFormat        format_;
449             //! Links to use.
450             const HelpLinks        *links_;
451         };
452
453         struct ReplaceItem
454         {
455             ReplaceItem(const std::string &search,
456                         const std::string &replace)
457                 : search(search), replace(replace)
458             {
459             }
460             std::string         search;
461             std::string         replace;
462         };
463
464         //! Smart pointer type for managing the shared state.
465         typedef boost::shared_ptr<const SharedState> StatePointer;
466         //! Shorthand for a list of markup/other replacements.
467         typedef std::vector<ReplaceItem> ReplaceList;
468
469         //! Initializes the context with the given state and section depth.
470         Impl(const StatePointer &state, int sectionDepth)
471             : state_(state), sectionDepth_(sectionDepth)
472         {
473         }
474
475         //! Adds a new replacement.
476         void addReplacement(const std::string &search,
477                             const std::string &replace)
478         {
479             replacements_.push_back(ReplaceItem(search, replace));
480         }
481
482         //! Replaces links in a given string.
483         std::string replaceLinks(const std::string &input) const;
484
485         /*! \brief
486          * Process markup and wrap lines within a block of text.
487          *
488          * \param[in] text     Text to process.
489          * \param     wrapper  Object used to wrap the text.
490          *
491          * The \p wrapper should take care of either writing the text to output
492          * or providing an interface for the caller to retrieve the output.
493          */
494         void processMarkup(const std::string &text,
495                            WrapperInterface  *wrapper) const;
496
497         //! Constant state shared by all child context objects.
498         StatePointer            state_;
499         //! List of markup/other replacements.
500         ReplaceList             replacements_;
501         //! Number of subsections above this context.
502         int                     sectionDepth_;
503
504     private:
505         GMX_DISALLOW_ASSIGN(Impl);
506 };
507
508 std::string HelpWriterContext::Impl::replaceLinks(const std::string &input) const
509 {
510     std::string result(input);
511     if (state_->links_ != NULL)
512     {
513         HelpLinks::Impl::LinkList::const_iterator link;
514         for (link  = state_->links_->impl_->links_.begin();
515              link != state_->links_->impl_->links_.end(); ++link)
516         {
517             result = replaceAllWords(result, link->linkName, link->replacement);
518         }
519     }
520     return result;
521 }
522
523 void HelpWriterContext::Impl::processMarkup(const std::string &text,
524                                             WrapperInterface  *wrapper) const
525 {
526     std::string result(text);
527     for (ReplaceList::const_iterator i = replacements_.begin();
528          i != replacements_.end(); ++i)
529     {
530         result = replaceAll(result, i->search, i->replace);
531     }
532     switch (state_->format_)
533     {
534         case eHelpOutputFormat_Console:
535         {
536             const int   baseFirstLineIndent = wrapper->settings().firstLineIndent();
537             const int   baseIndent          = wrapper->settings().indent();
538             result = repall(result, sandrTty);
539             result = replaceLinks(result);
540             std::string          paragraph;
541             paragraph.reserve(result.length());
542             RstParagraphIterator iter(result);
543             while (iter.nextParagraph())
544             {
545                 iter.getParagraphText(&paragraph);
546                 wrapper->settings().setFirstLineIndent(baseFirstLineIndent + iter.firstLineIndent());
547                 wrapper->settings().setIndent(baseIndent + iter.indent());
548                 wrapper->wrap(paragraph);
549             }
550             wrapper->settings().setFirstLineIndent(baseFirstLineIndent);
551             wrapper->settings().setIndent(baseIndent);
552             break;
553         }
554         case eHelpOutputFormat_Rst:
555         {
556             result = repall(result, sandrRst);
557             result = replaceLinks(result);
558             result = replaceAll(result, "[REF]", "");
559             result = replaceAll(result, "[ref]", "");
560             result = removeExtraNewlinesRst(result);
561             wrapper->wrap(result);
562             break;
563         }
564         default:
565             GMX_THROW(InternalError("Invalid help output format"));
566     }
567 }
568
569 /********************************************************************
570  * HelpWriterContext
571  */
572
573 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format)
574     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, NULL)), 0))
575 {
576 }
577
578 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format,
579                                      const HelpLinks *links)
580     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, links)), 0))
581 {
582     if (links != NULL)
583     {
584         GMX_RELEASE_ASSERT(links->impl_->format_ == format,
585                            "Links must have the same output format as the context");
586     }
587 }
588
589 HelpWriterContext::HelpWriterContext(Impl *impl)
590     : impl_(impl)
591 {
592 }
593
594 HelpWriterContext::HelpWriterContext(const HelpWriterContext &other)
595     : impl_(new Impl(*other.impl_))
596 {
597 }
598
599 HelpWriterContext::~HelpWriterContext()
600 {
601 }
602
603 void HelpWriterContext::setReplacement(const std::string &search,
604                                        const std::string &replace)
605 {
606     impl_->addReplacement(search, replace);
607 }
608
609 HelpOutputFormat HelpWriterContext::outputFormat() const
610 {
611     return impl_->state_->format_;
612 }
613
614 File &HelpWriterContext::outputFile() const
615 {
616     return impl_->state_->file_;
617 }
618
619 void HelpWriterContext::enterSubSection(const std::string &title)
620 {
621     GMX_RELEASE_ASSERT(impl_->sectionDepth_ - 1 < static_cast<int>(std::strlen(g_titleChars)),
622                        "Too deeply nested subsections");
623     writeTitle(title);
624     ++impl_->sectionDepth_;
625 }
626
627 std::string
628 HelpWriterContext::substituteMarkupAndWrapToString(
629         const TextLineWrapperSettings &settings, const std::string &text) const
630 {
631     WrapperToString wrapper(settings);
632     impl_->processMarkup(text, &wrapper);
633     return wrapper.result();
634 }
635
636 std::vector<std::string>
637 HelpWriterContext::substituteMarkupAndWrapToVector(
638         const TextLineWrapperSettings &settings, const std::string &text) const
639 {
640     WrapperToVector wrapper(settings);
641     impl_->processMarkup(text, &wrapper);
642     return wrapper.result();
643 }
644
645 void HelpWriterContext::writeTitle(const std::string &title) const
646 {
647     if (title.empty())
648     {
649         return;
650     }
651     File &file = outputFile();
652     switch (outputFormat())
653     {
654         case eHelpOutputFormat_Console:
655             file.writeLine(toUpperCase(title));
656             file.writeLine();
657             break;
658         case eHelpOutputFormat_Rst:
659             file.writeLine(title);
660             file.writeLine(std::string(title.length(),
661                                        g_titleChars[impl_->sectionDepth_]));
662             break;
663         default:
664             GMX_THROW(NotImplementedError(
665                               "This output format is not implemented"));
666     }
667 }
668
669 void HelpWriterContext::writeTextBlock(const std::string &text) const
670 {
671     TextLineWrapperSettings settings;
672     if (outputFormat() == eHelpOutputFormat_Console)
673     {
674         settings.setLineLength(78);
675     }
676     outputFile().writeLine(substituteMarkupAndWrapToString(settings, text));
677 }
678
679 void HelpWriterContext::writeOptionListStart() const
680 {
681 }
682
683 void HelpWriterContext::writeOptionItem(const std::string &name,
684                                         const std::string &args,
685                                         const std::string &description) const
686 {
687     File &file = outputFile();
688     switch (outputFormat())
689     {
690         case eHelpOutputFormat_Console:
691             // TODO: Generalize this when there is need for it; the current,
692             // special implementation is in CommandLineHelpWriter.
693             GMX_THROW(NotImplementedError("Option item formatting for console output not implemented"));
694             break;
695         case eHelpOutputFormat_Rst:
696         {
697             file.writeLine(formatString("``%s`` %s", name.c_str(), args.c_str()));
698             TextLineWrapperSettings settings;
699             settings.setIndent(4);
700             file.writeLine(substituteMarkupAndWrapToString(settings, description));
701             break;
702         }
703         default:
704             GMX_THROW(NotImplementedError(
705                               "This output format is not implemented"));
706     }
707 }
708
709 void HelpWriterContext::writeOptionListEnd() const
710 {
711 }
712
713 } // namespace gmx