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