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