Merge release-5-0 into release-5-1
[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/onlinehelp/helpformat.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/file.h"
57 #include "gromacs/utility/gmxassert.h"
58 #include "gromacs/utility/programcontext.h"
59 #include "gromacs/utility/stringutil.h"
60
61 #include "rstparser.h"
62
63 namespace gmx
64 {
65
66 namespace
67 {
68
69 //! \internal \addtogroup module_onlinehelp
70 //! \{
71
72 //! Characters used for reStructuredText title underlining.
73 const char g_titleChars[] = "=-^*~+#'_.";
74
75 struct t_sandr
76 {
77     const char *search;
78     const char *replace;
79 };
80
81 /* The order of these arrays is significant. Text search and replace
82  * for each element occurs in order, so earlier changes can induce
83  * subsequent changes even though the original text might not appear
84  * to invoke the latter changes.
85  * TODO: Get rid of this behavior. It makes it very difficult to manage
86  * replacements coming from multiple sources (e.g., hyperlinks).*/
87
88 //! List of replacements for console output.
89 const t_sandr sandrTty[] = {
90     { "\\*", "*" },
91     { "\\=", "=" },
92     { "[REF]", "" },
93     { "[ref]", "" },
94     { "[TT]", "" },
95     { "[tt]", "" },
96     { "[BB]", "" },
97     { "[bb]", "" },
98     { "[IT]", "" },
99     { "[it]", "" },
100     { "[MATH]", "" },
101     { "[math]", "" },
102     { "[CHEVRON]", "<" },
103     { "[chevron]", ">" },
104     { "[MAG]", "|" },
105     { "[mag]", "|" },
106     { "[INT]", "integral" },
107     { "[FROM]", " from " },
108     { "[from]", "" },
109     { "[TO]", " to " },
110     { "[to]", " of" },
111     { "[int]", "" },
112     { "[SUM]", "sum" },
113     { "[sum]", "" },
114     { "[SUB]", "_" },
115     { "[sub]", "" },
116     { "[SQRT]", "sqrt(" },
117     { "[sqrt]", ")" },
118     { "[EXP]", "exp(" },
119     { "[exp]", ")" },
120     { "[LN]", "ln(" },
121     { "[ln]", ")" },
122     { "[LOG]", "log(" },
123     { "[log]", ")" },
124     { "[COS]", "cos(" },
125     { "[cos]", ")" },
126     { "[SIN]", "sin(" },
127     { "[sin]", ")" },
128     { "[TAN]", "tan(" },
129     { "[tan]", ")" },
130     { "[COSH]", "cosh(" },
131     { "[cosh]", ")" },
132     { "[SINH]", "sinh(" },
133     { "[sinh]", ")" },
134     { "[TANH]", "tanh(" },
135     { "[tanh]", ")" },
136     { "[PAR]", "\n\n" },
137     { "[GRK]", "" },
138     { "[grk]", "" }
139 };
140
141 //! List of replacements for reStructuredText output.
142 const t_sandr sandrRst[] = {
143     { "[TT]", "``" },
144     { "[tt]", "``" },
145     { "[BB]", "**" },
146     { "[bb]", "**" },
147     { "[IT]", "*" },
148     { "[it]", "*" },
149     { "[MATH]", "" },
150     { "[math]", "" },
151     { "[CHEVRON]", "<" },
152     { "[chevron]", ">" },
153     { "[MAG]", "\\|" },
154     { "[mag]", "\\|" },
155     { "[INT]", "integral" },
156     { "[FROM]", " from " },
157     { "[from]", "" },
158     { "[TO]", " to " },
159     { "[to]", " of" },
160     { "[int]", "" },
161     { "[SUM]", "sum" },
162     { "[sum]", "" },
163     { "[SUB]", "_" },
164     { "[sub]", "" },
165     { "[SQRT]", "sqrt(" },
166     { "[sqrt]", ")" },
167     { "[EXP]", "exp(" },
168     { "[exp]", ")" },
169     { "[LN]", "ln(" },
170     { "[ln]", ")" },
171     { "[LOG]", "log(" },
172     { "[log]", ")" },
173     { "[COS]", "cos(" },
174     { "[cos]", ")" },
175     { "[SIN]", "sin(" },
176     { "[sin]", ")" },
177     { "[TAN]", "tan(" },
178     { "[tan]", ")" },
179     { "[COSH]", "cosh(" },
180     { "[cosh]", ")" },
181     { "[SINH]", "sinh(" },
182     { "[sinh]", ")" },
183     { "[TANH]", "tanh(" },
184     { "[tanh]", ")" },
185     { "[PAR]", "\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 //! \}
345
346 }   // namespace
347
348 /********************************************************************
349  * HelpLinks::Impl
350  */
351
352 /*! \internal \brief
353  * Private implementation class for HelpLinks.
354  *
355  * \ingroup module_onlinehelp
356  */
357 class HelpLinks::Impl
358 {
359     public:
360         struct LinkItem
361         {
362             LinkItem(const std::string &linkName,
363                      const std::string &replacement)
364                 : linkName(linkName), replacement(replacement)
365             {
366             }
367             std::string         linkName;
368             std::string         replacement;
369         };
370
371         //! Shorthand for a list of links.
372         typedef std::vector<LinkItem> LinkList;
373
374         //! Initializes empty links with the given format.
375         explicit Impl(HelpOutputFormat format) : format_(format)
376         {
377         }
378
379         //! List of links.
380         LinkList          links_;
381         //! Output format for which the links are formatted.
382         HelpOutputFormat  format_;
383 };
384
385 /********************************************************************
386  * HelpLinks
387  */
388
389 HelpLinks::HelpLinks(HelpOutputFormat format) : impl_(new Impl(format))
390 {
391 }
392
393 HelpLinks::~HelpLinks()
394 {
395 }
396
397 void HelpLinks::addLink(const std::string &linkName,
398                         const std::string &targetName,
399                         const std::string &displayName)
400 {
401     std::string replacement;
402     switch (impl_->format_)
403     {
404         case eHelpOutputFormat_Console:
405             replacement = repall(displayName, sandrTty);
406             break;
407         case eHelpOutputFormat_Rst:
408             replacement = targetName;
409             break;
410         default:
411             GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
412     }
413     impl_->links_.push_back(Impl::LinkItem(linkName, replacement));
414 }
415
416 /********************************************************************
417  * HelpWriterContext::Impl
418  */
419
420 /*! \internal \brief
421  * Private implementation class for HelpWriterContext.
422  *
423  * \ingroup module_onlinehelp
424  */
425 class HelpWriterContext::Impl
426 {
427     public:
428         /*! \brief
429          * Shared, non-modifiable state for context objects.
430          *
431          * Contents of this structure are shared between all context objects
432          * that are created from a common parent.
433          * This state should not be modified after construction.
434          *
435          * \ingroup module_onlinehelp
436          */
437         class SharedState
438         {
439             public:
440                 //! Initializes the state with the given parameters.
441                 SharedState(File *file, HelpOutputFormat format,
442                             const HelpLinks *links)
443                     : file_(*file), format_(format), links_(links)
444                 {
445                 }
446
447                 /*! \brief
448                  * Returns a formatter for formatting options lists for console
449                  * output.
450                  *
451                  * The formatter is lazily initialized on first access.
452                  */
453                 TextTableFormatter &consoleOptionsFormatter() const
454                 {
455                     GMX_RELEASE_ASSERT(format_ == eHelpOutputFormat_Console,
456                                        "Accessing console formatter for non-console output");
457                     if (!consoleOptionsFormatter_)
458                     {
459                         consoleOptionsFormatter_.reset(new TextTableFormatter());
460                         consoleOptionsFormatter_->setFirstColumnIndent(1);
461                         consoleOptionsFormatter_->addColumn(NULL, 7, false);
462                         consoleOptionsFormatter_->addColumn(NULL, 18, false);
463                         consoleOptionsFormatter_->addColumn(NULL, 16, false);
464                         consoleOptionsFormatter_->addColumn(NULL, 28, false);
465                     }
466                     return *consoleOptionsFormatter_;
467                 }
468
469                 //! Output file to which the help is written.
470                 File                   &file_;
471                 //! Output format for the help output.
472                 HelpOutputFormat        format_;
473                 //! Links to use.
474                 const HelpLinks        *links_;
475
476             private:
477                 //! Formatter for console output options.
478                 mutable boost::scoped_ptr<TextTableFormatter> consoleOptionsFormatter_;
479         };
480
481         struct ReplaceItem
482         {
483             ReplaceItem(const std::string &search,
484                         const std::string &replace)
485                 : search(search), replace(replace)
486             {
487             }
488             std::string         search;
489             std::string         replace;
490         };
491
492         //! Smart pointer type for managing the shared state.
493         typedef boost::shared_ptr<const SharedState> StatePointer;
494         //! Shorthand for a list of markup/other replacements.
495         typedef std::vector<ReplaceItem> ReplaceList;
496
497         //! Initializes the context with the given state and section depth.
498         Impl(const StatePointer &state, int sectionDepth)
499             : state_(state), sectionDepth_(sectionDepth)
500         {
501         }
502
503         //! Adds a new replacement.
504         void addReplacement(const std::string &search,
505                             const std::string &replace)
506         {
507             replacements_.push_back(ReplaceItem(search, replace));
508         }
509
510         //! Replaces links in a given string.
511         std::string replaceLinks(const std::string &input) const;
512
513         /*! \brief
514          * Process markup and wrap lines within a block of text.
515          *
516          * \param[in] text     Text to process.
517          * \param     wrapper  Object used to wrap the text.
518          *
519          * The \p wrapper should take care of either writing the text to output
520          * or providing an interface for the caller to retrieve the output.
521          */
522         void processMarkup(const std::string &text,
523                            WrapperInterface  *wrapper) const;
524
525         //! Constant state shared by all child context objects.
526         StatePointer            state_;
527         //! List of markup/other replacements.
528         ReplaceList             replacements_;
529         //! Number of subsections above this context.
530         int                     sectionDepth_;
531
532     private:
533         GMX_DISALLOW_ASSIGN(Impl);
534 };
535
536 std::string HelpWriterContext::Impl::replaceLinks(const std::string &input) const
537 {
538     std::string result(input);
539     if (state_->links_ != NULL)
540     {
541         HelpLinks::Impl::LinkList::const_iterator link;
542         for (link  = state_->links_->impl_->links_.begin();
543              link != state_->links_->impl_->links_.end(); ++link)
544         {
545             result = replaceAllWords(result, link->linkName, link->replacement);
546         }
547     }
548     return result;
549 }
550
551 void HelpWriterContext::Impl::processMarkup(const std::string &text,
552                                             WrapperInterface  *wrapper) const
553 {
554     std::string result(text);
555     for (ReplaceList::const_iterator i = replacements_.begin();
556          i != replacements_.end(); ++i)
557     {
558         result = replaceAll(result, i->search, i->replace);
559     }
560     switch (state_->format_)
561     {
562         case eHelpOutputFormat_Console:
563         {
564             const int   baseFirstLineIndent = wrapper->settings().firstLineIndent();
565             const int   baseIndent          = wrapper->settings().indent();
566             result = repall(result, sandrTty);
567             result = replaceLinks(result);
568             std::string          paragraph;
569             paragraph.reserve(result.length());
570             RstParagraphIterator iter(result);
571             while (iter.nextParagraph())
572             {
573                 iter.getParagraphText(&paragraph);
574                 wrapper->settings().setFirstLineIndent(baseFirstLineIndent + iter.firstLineIndent());
575                 wrapper->settings().setIndent(baseIndent + iter.indent());
576                 wrapper->wrap(paragraph);
577             }
578             wrapper->settings().setFirstLineIndent(baseFirstLineIndent);
579             wrapper->settings().setIndent(baseIndent);
580             break;
581         }
582         case eHelpOutputFormat_Rst:
583         {
584             result = repall(result, sandrRst);
585             result = replaceLinks(result);
586             result = replaceAll(result, "[REF]", "");
587             result = replaceAll(result, "[ref]", "");
588             result = removeExtraNewlinesRst(result);
589             wrapper->wrap(result);
590             break;
591         }
592         default:
593             GMX_THROW(InternalError("Invalid help output format"));
594     }
595 }
596
597 /********************************************************************
598  * HelpWriterContext
599  */
600
601 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format)
602     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, NULL)), 0))
603 {
604 }
605
606 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format,
607                                      const HelpLinks *links)
608     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, links)), 0))
609 {
610     if (links != NULL)
611     {
612         GMX_RELEASE_ASSERT(links->impl_->format_ == format,
613                            "Links must have the same output format as the context");
614     }
615 }
616
617 HelpWriterContext::HelpWriterContext(Impl *impl)
618     : impl_(impl)
619 {
620 }
621
622 HelpWriterContext::HelpWriterContext(const HelpWriterContext &other)
623     : impl_(new Impl(*other.impl_))
624 {
625 }
626
627 HelpWriterContext::~HelpWriterContext()
628 {
629 }
630
631 void HelpWriterContext::setReplacement(const std::string &search,
632                                        const std::string &replace)
633 {
634     impl_->addReplacement(search, replace);
635 }
636
637 HelpOutputFormat HelpWriterContext::outputFormat() const
638 {
639     return impl_->state_->format_;
640 }
641
642 File &HelpWriterContext::outputFile() const
643 {
644     return impl_->state_->file_;
645 }
646
647 void HelpWriterContext::enterSubSection(const std::string &title)
648 {
649     GMX_RELEASE_ASSERT(impl_->sectionDepth_ - 1 < static_cast<int>(std::strlen(g_titleChars)),
650                        "Too deeply nested subsections");
651     writeTitle(title);
652     ++impl_->sectionDepth_;
653 }
654
655 std::string
656 HelpWriterContext::substituteMarkupAndWrapToString(
657         const TextLineWrapperSettings &settings, const std::string &text) const
658 {
659     WrapperToString wrapper(settings);
660     impl_->processMarkup(text, &wrapper);
661     return wrapper.result();
662 }
663
664 std::vector<std::string>
665 HelpWriterContext::substituteMarkupAndWrapToVector(
666         const TextLineWrapperSettings &settings, const std::string &text) const
667 {
668     WrapperToVector wrapper(settings);
669     impl_->processMarkup(text, &wrapper);
670     return wrapper.result();
671 }
672
673 void HelpWriterContext::writeTitle(const std::string &title) const
674 {
675     if (title.empty())
676     {
677         return;
678     }
679     File &file = outputFile();
680     switch (outputFormat())
681     {
682         case eHelpOutputFormat_Console:
683             file.writeLine(toUpperCase(title));
684             file.writeLine();
685             break;
686         case eHelpOutputFormat_Rst:
687             file.writeLine(title);
688             file.writeLine(std::string(title.length(),
689                                        g_titleChars[impl_->sectionDepth_]));
690             break;
691         default:
692             GMX_THROW(NotImplementedError(
693                               "This output format is not implemented"));
694     }
695 }
696
697 void HelpWriterContext::writeTextBlock(const std::string &text) const
698 {
699     TextLineWrapperSettings settings;
700     if (outputFormat() == eHelpOutputFormat_Console)
701     {
702         settings.setLineLength(78);
703     }
704     outputFile().writeLine(substituteMarkupAndWrapToString(settings, text));
705 }
706
707 void HelpWriterContext::writeOptionListStart() const
708 {
709 }
710
711 void HelpWriterContext::writeOptionItem(const std::string &name,
712                                         const std::string &value,
713                                         const std::string &defaultValue,
714                                         const std::string &info,
715                                         const std::string &description) const
716 {
717     File &file = outputFile();
718     switch (outputFormat())
719     {
720         case eHelpOutputFormat_Console:
721         {
722             TextTableFormatter &formatter(impl_->state_->consoleOptionsFormatter());
723             formatter.clear();
724             formatter.addColumnLine(0, name);
725             formatter.addColumnLine(1, value);
726             if (!defaultValue.empty())
727             {
728                 formatter.addColumnLine(2, "(" + defaultValue + ")");
729             }
730             if (!info.empty())
731             {
732                 formatter.addColumnLine(3, "(" + info + ")");
733             }
734             TextLineWrapperSettings settings;
735             settings.setIndent(11);
736             settings.setLineLength(78);
737             std::string formattedDescription
738                 = substituteMarkupAndWrapToString(settings, description);
739             file.writeLine(formatter.formatRow());
740             file.writeLine(formattedDescription);
741             break;
742         }
743         case eHelpOutputFormat_Rst:
744         {
745             std::string args(value);
746             if (!defaultValue.empty())
747             {
748                 args.append(" (");
749                 args.append(defaultValue);
750                 args.append(")");
751             }
752             if (!info.empty())
753             {
754                 args.append(" (");
755                 args.append(info);
756                 args.append(")");
757             }
758             file.writeLine(formatString("``%s`` %s", name.c_str(), args.c_str()));
759             TextLineWrapperSettings settings;
760             settings.setIndent(4);
761             file.writeLine(substituteMarkupAndWrapToString(settings, description));
762             break;
763         }
764         default:
765             GMX_THROW(NotImplementedError(
766                               "This output format is not implemented"));
767     }
768 }
769
770 void HelpWriterContext::writeOptionListEnd() const
771 {
772 }
773
774 } // namespace gmx