Merge release-5-0 into master
[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, 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     { "[TT]", "" },
85     { "[tt]", "" },
86     { "[BB]", "" },
87     { "[bb]", "" },
88     { "[IT]", "" },
89     { "[it]", "" },
90     { "[MATH]", "" },
91     { "[math]", "" },
92     { "[CHEVRON]", "<" },
93     { "[chevron]", ">" },
94     { "[MAG]", "|" },
95     { "[mag]", "|" },
96     { "[INT]", "integral" },
97     { "[FROM]", " from " },
98     { "[from]", "" },
99     { "[TO]", " to " },
100     { "[to]", " of" },
101     { "[int]", "" },
102     { "[SUM]", "sum" },
103     { "[sum]", "" },
104     { "[SUB]", "_" },
105     { "[sub]", "" },
106     { "[SQRT]", "sqrt(" },
107     { "[sqrt]", ")" },
108     { "[EXP]", "exp(" },
109     { "[exp]", ")" },
110     { "[LN]", "ln(" },
111     { "[ln]", ")" },
112     { "[LOG]", "log(" },
113     { "[log]", ")" },
114     { "[COS]", "cos(" },
115     { "[cos]", ")" },
116     { "[SIN]", "sin(" },
117     { "[sin]", ")" },
118     { "[TAN]", "tan(" },
119     { "[tan]", ")" },
120     { "[COSH]", "cosh(" },
121     { "[cosh]", ")" },
122     { "[SINH]", "sinh(" },
123     { "[sinh]", ")" },
124     { "[TANH]", "tanh(" },
125     { "[tanh]", ")" },
126     { "[PAR]", "\n\n" },
127     { "[BR]", "\n"},
128     /* [UL], [LI], [ul] cannot be implemented properly with the current
129      * approach. */
130     { "[GRK]", "" },
131     { "[grk]", "" }
132 };
133
134 //! List of replacements for man page output.
135 const t_sandr sandrMan[] = {
136     { "[TT]", "\\fB" },
137     { "[tt]", "\\fR" },
138     { "[BB]", "\\fB" },
139     { "[bb]", "\\fR" },
140     { "[IT]", "\\fI" },
141     { "[it]", "\\fR" },
142     { "[MATH]", "" },
143     { "[math]", "" },
144     { "[CHEVRON]", "<" },
145     { "[chevron]", ">" },
146     { "[MAG]", "|" },
147     { "[mag]", "|" },
148     { "[INT]", "integral" },
149     { "[FROM]", " from " },
150     { "[from]", "" },
151     { "[TO]", " to " },
152     { "[to]", " of" },
153     { "[int]", "" },
154     { "[SUM]", "sum" },
155     { "[sum]", "" },
156     { "[SUB]", "_" },
157     { "[sub]", "" },
158     { "[SQRT]", "sqrt(" },
159     { "[sqrt]", ")", },
160     { "[EXP]", "exp(" },
161     { "[exp]", ")" },
162     { "[LN]", "ln(" },
163     { "[ln]", ")" },
164     { "[LOG]", "log(" },
165     { "[log]", ")" },
166     { "[COS]", "cos(" },
167     { "[cos]", ")" },
168     { "[SIN]", "sin(" },
169     { "[sin]", ")" },
170     { "[TAN]", "tan(" },
171     { "[tan]", ")" },
172     { "[COSH]", "cosh(" },
173     { "[cosh]", ")" },
174     { "[SINH]", "sinh(" },
175     { "[sinh]", ")" },
176     { "[TANH]", "tanh(" },
177     { "[tanh]", ")" },
178     { "[PAR]", "\n\n" },
179     { "\n ",    "\n" },
180     // The following three work only in the specific context in which they are
181     // currently used.
182     { "[UL]", "" },
183     { "[LI]", "\n- " },
184     { "[ul]", "" },
185     { "<",    "" },
186     { ">",    "" },
187     { "^",    "" },
188     { "#",    "" },
189     { "[BR]", "\n"},
190     { "-",    "\\-"},
191     { "[GRK]", "" },
192     { "[grk]", "" }
193 };
194
195 //! List of replacements for HTML output.
196 const t_sandr sandrHtml[] = {
197     { "<",    "&lt;" },
198     { ">",    "&gt;" },
199     { "[TT]", "<tt>" },
200     { "[tt]", "</tt>" },
201     { "[BB]", "<b>" },
202     { "[bb]", "</b>" },
203     { "[IT]", "<it>" },
204     { "[it]", "</it>" },
205     { "[MATH]", "" },
206     { "[math]", "" },
207     { "[CHEVRON]", "&lt;" },
208     { "[chevron]", "&gt;" },
209     { "[MAG]", "|" },
210     { "[mag]", "|" },
211     { "[INT]", "integral" },
212     { "[FROM]", " from " },
213     { "[from]", "" },
214     { "[TO]", " to " },
215     { "[to]", " of" },
216     { "[int]", "" },
217     { "[SUM]", "sum" },
218     { "[sum]", "" },
219     { "[SUB]", "_" },
220     { "[sub]", "" },
221     { "[SQRT]", "sqrt(" },
222     { "[sqrt]", ")", },
223     { "[EXP]", "exp(" },
224     { "[exp]", ")" },
225     { "[LN]", "ln(" },
226     { "[ln]", ")" },
227     { "[LOG]", "log(" },
228     { "[log]", ")" },
229     { "[COS]", "cos(" },
230     { "[cos]", ")" },
231     { "[SIN]", "sin(" },
232     { "[sin]", ")" },
233     { "[TAN]", "tan(" },
234     { "[tan]", ")" },
235     { "[COSH]", "cosh(" },
236     { "[cosh]", ")" },
237     { "[SINH]", "sinh(" },
238     { "[sinh]", ")" },
239     { "[TANH]", "tanh(" },
240     { "[tanh]", ")" },
241     { "[PAR]", "<p>" },
242     { "[BR]", "<br>" },
243     { "[UL]", "<ul>" },
244     { "[LI]", "<li>" },
245     { "[ul]", "</ul>" },
246     { "[GRK]", "&"  },
247     { "[grk]", ";"  }
248 };
249
250 /*! \brief
251  * Replaces all entries from a list of replacements.
252  */
253 std::string repall(const std::string &s, int nsr, const t_sandr sa[])
254 {
255     std::string result(s);
256     for (int i = 0; i < nsr; ++i)
257     {
258         result = replaceAll(result, sa[i].search, sa[i].replace);
259     }
260     return result;
261 }
262
263 /*! \brief
264  * Replaces all entries from a list of replacements.
265  */
266 template <size_t nsr>
267 std::string repall(const std::string &s, const t_sandr (&sa)[nsr])
268 {
269     return repall(s, nsr, sa);
270 }
271
272 /*! \brief
273  * Custom output interface for HelpWriterContext::Impl::processMarkup().
274  *
275  * Provides an interface that is used to implement different types of output
276  * from HelpWriterContext::Impl::processMarkup().
277  */
278 class WrapperInterface
279 {
280     public:
281         virtual ~WrapperInterface() {}
282
283         /*! \brief
284          * Provides the wrapping settings.
285          *
286          * HelpWriterContext::Impl::processMarkup() may provide some default
287          * values for the settings if they are not set; this is the reason the
288          * return value is not const.
289          */
290         virtual TextLineWrapperSettings &settings() = 0;
291         //! Appends the given string to output.
292         virtual void wrap(const std::string &text)  = 0;
293 };
294
295 /*! \brief
296  * Wraps markup output into a single string.
297  */
298 class WrapperToString : public WrapperInterface
299 {
300     public:
301         //! Creates a wrapper with the given settings.
302         explicit WrapperToString(const TextLineWrapperSettings &settings)
303             : wrapper_(settings)
304         {
305         }
306
307         virtual TextLineWrapperSettings &settings()
308         {
309             return wrapper_.settings();
310         }
311         virtual void wrap(const std::string &text)
312         {
313             result_.append(wrapper_.wrapToString(text));
314         }
315         //! Returns the result string.
316         const std::string &result() const { return result_; }
317
318     private:
319         TextLineWrapper         wrapper_;
320         std::string             result_;
321 };
322
323 /*! \brief
324  * Wraps markup output into a vector of string (one line per element).
325  */
326 class WrapperToVector : public WrapperInterface
327 {
328     public:
329         //! Creates a wrapper with the given settings.
330         explicit WrapperToVector(const TextLineWrapperSettings &settings)
331             : wrapper_(settings)
332         {
333         }
334
335         virtual TextLineWrapperSettings &settings()
336         {
337             return wrapper_.settings();
338         }
339         virtual void wrap(const std::string &text)
340         {
341             const std::vector<std::string> &lines = wrapper_.wrapToVector(text);
342             result_.insert(result_.end(), lines.begin(), lines.end());
343         }
344         //! Returns a vector with the output lines.
345         const std::vector<std::string> &result() const { return result_; }
346
347     private:
348         TextLineWrapper          wrapper_;
349         std::vector<std::string> result_;
350 };
351
352 /*! \brief
353  * Makes the string uppercase.
354  *
355  * \param[in] text  Input text.
356  * \returns   \p text with all characters transformed to uppercase.
357  * \throws    std::bad_alloc if out of memory.
358  */
359 std::string toUpperCase(const std::string &text)
360 {
361     std::string result(text);
362     std::transform(result.begin(), result.end(), result.begin(), toupper);
363     return result;
364 }
365
366 //! \}
367
368 }   // namespace
369
370 /********************************************************************
371  * HelpLinks::Impl
372  */
373
374 /*! \internal \brief
375  * Private implementation class for HelpLinks.
376  *
377  * \ingroup module_onlinehelp
378  */
379 class HelpLinks::Impl
380 {
381     public:
382         struct LinkItem
383         {
384             LinkItem(const std::string &linkName,
385                      const std::string &replacement)
386                 : linkName(linkName), replacement(replacement)
387             {
388             }
389             std::string         linkName;
390             std::string         replacement;
391         };
392
393         //! Shorthand for a list of links.
394         typedef std::vector<LinkItem> LinkList;
395
396         //! Initializes empty links with the given format.
397         explicit Impl(HelpOutputFormat format) : format_(format)
398         {
399         }
400
401         //! List of links.
402         LinkList          links_;
403         //! Output format for which the links are formatted.
404         HelpOutputFormat  format_;
405 };
406
407 /********************************************************************
408  * HelpLinks
409  */
410
411 HelpLinks::HelpLinks(HelpOutputFormat format) : impl_(new Impl(format))
412 {
413 }
414
415 HelpLinks::~HelpLinks()
416 {
417 }
418
419 void HelpLinks::addLink(const std::string &linkName,
420                         const std::string &targetName,
421                         const std::string &displayName)
422 {
423     std::string replacement;
424     switch (impl_->format_)
425     {
426         case eHelpOutputFormat_Console:
427             replacement = repall(displayName, sandrTty);
428             break;
429         case eHelpOutputFormat_Man:
430             replacement = repall(displayName, sandrMan);
431             break;
432         case eHelpOutputFormat_Html:
433             replacement = formatString(
434                         "<a href=\"%s.html\">%s</a>", targetName.c_str(),
435                         repall(displayName, sandrHtml).c_str());
436             break;
437         default:
438             GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
439     }
440     impl_->links_.push_back(Impl::LinkItem(linkName, replacement));
441 }
442
443 /********************************************************************
444  * HelpWriterContext::Impl
445  */
446
447 /*! \internal \brief
448  * Private implementation class for HelpWriterContext.
449  *
450  * \ingroup module_onlinehelp
451  */
452 class HelpWriterContext::Impl
453 {
454     public:
455         /*! \brief
456          * Shared, non-modifiable state for context objects.
457          *
458          * Contents of this structure are shared between all context objects
459          * that are created from a common parent.
460          * This state should not be modified after construction.
461          *
462          * \ingroup module_onlinehelp
463          */
464         struct SharedState
465         {
466             //! Initializes the state with the given parameters.
467             SharedState(File *file, HelpOutputFormat format,
468                         const HelpLinks *links)
469                 : file_(*file), format_(format), links_(links)
470             {
471             }
472
473             //! Output file to which the help is written.
474             File                   &file_;
475             //! Output format for the help output.
476             HelpOutputFormat        format_;
477             //! Links to use.
478             const HelpLinks        *links_;
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.
498         explicit Impl(const StatePointer &state)
499             : state_(state)
500         {
501             initDefaultReplacements();
502         }
503
504         //! Initializes default replacements for the chosen output format.
505         void initDefaultReplacements();
506         //! Adds a new replacement.
507         void addReplacement(const std::string &search,
508                             const std::string &replace)
509         {
510             replacements_.push_back(ReplaceItem(search, replace));
511         }
512
513         //! Replaces links in a given string.
514         std::string replaceLinks(const std::string &input) const;
515
516         /*! \brief
517          * Process markup and wrap lines within a block of text.
518          *
519          * \param[in] text     Text to process.
520          * \param     wrapper  Object used to wrap the text.
521          *
522          * The \p wrapper should take care of either writing the text to output
523          * or providing an interface for the caller to retrieve the output.
524          */
525         void processMarkup(const std::string &text,
526                            WrapperInterface  *wrapper) const;
527
528         //! Constant state shared by all child context objects.
529         StatePointer            state_;
530         //! List of markup/other replacements.
531         ReplaceList             replacements_;
532
533     private:
534         GMX_DISALLOW_ASSIGN(Impl);
535 };
536
537 void HelpWriterContext::Impl::initDefaultReplacements()
538 {
539     const char *program = getProgramContext().programName();
540     addReplacement("[PROGRAM]", program);
541 }
542
543 std::string HelpWriterContext::Impl::replaceLinks(const std::string &input) const
544 {
545     std::string result(input);
546     if (state_->links_ != NULL)
547     {
548         HelpLinks::Impl::LinkList::const_iterator link;
549         for (link  = state_->links_->impl_->links_.begin();
550              link != state_->links_->impl_->links_.end(); ++link)
551         {
552             result = replaceAllWords(result, link->linkName, link->replacement);
553         }
554     }
555     return result;
556 }
557
558 void HelpWriterContext::Impl::processMarkup(const std::string &text,
559                                             WrapperInterface  *wrapper) const
560 {
561     std::string result(text);
562     for (ReplaceList::const_iterator i = replacements_.begin();
563          i != replacements_.end(); ++i)
564     {
565         result = replaceAll(result, i->search, i->replace);
566     }
567     switch (state_->format_)
568     {
569         case eHelpOutputFormat_Console:
570         {
571             result = repall(result, sandrTty);
572             result = replaceLinks(result);
573             return wrapper->wrap(result);
574         }
575         case eHelpOutputFormat_Man:
576         {
577             // Needs to be done first to avoid '-' -> '\-' messing up the links.
578             result = replaceLinks(result);
579             result = repall(result, sandrMan);
580             return wrapper->wrap(result);
581         }
582         case eHelpOutputFormat_Html:
583         {
584             result = repall(result, sandrHtml);
585             result = replaceLinks(result);
586             return wrapper->wrap(result);
587         }
588         default:
589             GMX_THROW(InternalError("Invalid help output format"));
590     }
591 }
592
593 /********************************************************************
594  * HelpWriterContext
595  */
596
597 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format)
598     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, NULL))))
599 {
600 }
601
602 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format,
603                                      const HelpLinks *links)
604     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, links))))
605 {
606     if (links != NULL)
607     {
608         GMX_RELEASE_ASSERT(links->impl_->format_ == format,
609                            "Links must have the same output format as the context");
610     }
611 }
612
613 HelpWriterContext::HelpWriterContext(Impl *impl)
614     : impl_(impl)
615 {
616 }
617
618 HelpWriterContext::HelpWriterContext(const HelpWriterContext &other)
619     : impl_(new Impl(*other.impl_))
620 {
621 }
622
623 HelpWriterContext::~HelpWriterContext()
624 {
625 }
626
627 void HelpWriterContext::setReplacement(const std::string &search,
628                                        const std::string &replace)
629 {
630     impl_->addReplacement(search, replace);
631 }
632
633 HelpOutputFormat HelpWriterContext::outputFormat() const
634 {
635     return impl_->state_->format_;
636 }
637
638 File &HelpWriterContext::outputFile() const
639 {
640     return impl_->state_->file_;
641 }
642
643 std::string
644 HelpWriterContext::substituteMarkupAndWrapToString(
645         const TextLineWrapperSettings &settings, const std::string &text) const
646 {
647     WrapperToString wrapper(settings);
648     impl_->processMarkup(text, &wrapper);
649     return wrapper.result();
650 }
651
652 std::vector<std::string>
653 HelpWriterContext::substituteMarkupAndWrapToVector(
654         const TextLineWrapperSettings &settings, const std::string &text) const
655 {
656     WrapperToVector wrapper(settings);
657     impl_->processMarkup(text, &wrapper);
658     return wrapper.result();
659 }
660
661 void HelpWriterContext::writeTitle(const std::string &title) const
662 {
663     File &file = outputFile();
664     switch (outputFormat())
665     {
666         case eHelpOutputFormat_Console:
667             file.writeLine(toUpperCase(title));
668             file.writeLine();
669             break;
670         case eHelpOutputFormat_Man:
671             file.writeLine(formatString(".SH %s", toUpperCase(title).c_str()));
672             break;
673         case eHelpOutputFormat_Html:
674             file.writeLine(formatString("<H3>%s</H3>", title.c_str()));
675             break;
676         default:
677             GMX_THROW(NotImplementedError(
678                               "This output format is not implemented"));
679     }
680 }
681
682 void HelpWriterContext::writeTextBlock(const std::string &text) const
683 {
684     TextLineWrapperSettings settings;
685     if (outputFormat() == eHelpOutputFormat_Console)
686     {
687         settings.setLineLength(78);
688     }
689     outputFile().writeLine(substituteMarkupAndWrapToString(settings, text));
690 }
691
692 void HelpWriterContext::writeOptionListStart() const
693 {
694     if (outputFormat() == eHelpOutputFormat_Html)
695     {
696         outputFile().writeLine("<dl>");
697     }
698 }
699
700 void HelpWriterContext::writeOptionItem(const std::string &name,
701                                         const std::string &args,
702                                         const std::string &description) const
703 {
704     File &file = outputFile();
705     switch (outputFormat())
706     {
707         case eHelpOutputFormat_Console:
708             // TODO: Generalize this when there is need for it; the current,
709             // special implementation is in CommandLineHelpWriter.
710             GMX_THROW(NotImplementedError("Option item formatting for console output not implemented"));
711             break;
712         case eHelpOutputFormat_Man:
713             file.writeLine(formatString(".BI \"\\%s\" \" %s\"", name.c_str(), args.c_str()));
714             file.writeString("    ");
715             writeTextBlock(description);
716             file.writeLine();
717             break;
718         case eHelpOutputFormat_Html:
719         {
720             std::string substArgs =
721                 substituteMarkupAndWrapToString(TextLineWrapperSettings(), args);
722             file.writeLine(formatString("<dt><b><tt>%s</tt></b> %s</dt>", name.c_str(),
723                                         substArgs.c_str()));
724             file.writeLine("<dd>");
725             writeTextBlock(description);
726             file.writeLine("</dd>");
727             break;
728         }
729         default:
730             GMX_THROW(NotImplementedError(
731                               "This output format is not implemented"));
732     }
733 }
734
735 void HelpWriterContext::writeOptionListEnd() const
736 {
737     if (outputFormat() == eHelpOutputFormat_Html)
738     {
739         outputFile().writeLine("</dl>");
740     }
741 }
742
743 } // namespace gmx