Generate man pages through Sphinx
[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     { "[TT]", "" },
87     { "[tt]", "" },
88     { "[BB]", "" },
89     { "[bb]", "" },
90     { "[IT]", "" },
91     { "[it]", "" },
92     { "[MATH]", "" },
93     { "[math]", "" },
94     { "[CHEVRON]", "<" },
95     { "[chevron]", ">" },
96     { "[MAG]", "|" },
97     { "[mag]", "|" },
98     { "[INT]", "integral" },
99     { "[FROM]", " from " },
100     { "[from]", "" },
101     { "[TO]", " to " },
102     { "[to]", " of" },
103     { "[int]", "" },
104     { "[SUM]", "sum" },
105     { "[sum]", "" },
106     { "[SUB]", "_" },
107     { "[sub]", "" },
108     { "[SQRT]", "sqrt(" },
109     { "[sqrt]", ")" },
110     { "[EXP]", "exp(" },
111     { "[exp]", ")" },
112     { "[LN]", "ln(" },
113     { "[ln]", ")" },
114     { "[LOG]", "log(" },
115     { "[log]", ")" },
116     { "[COS]", "cos(" },
117     { "[cos]", ")" },
118     { "[SIN]", "sin(" },
119     { "[sin]", ")" },
120     { "[TAN]", "tan(" },
121     { "[tan]", ")" },
122     { "[COSH]", "cosh(" },
123     { "[cosh]", ")" },
124     { "[SINH]", "sinh(" },
125     { "[sinh]", ")" },
126     { "[TANH]", "tanh(" },
127     { "[tanh]", ")" },
128     { "[PAR]", "\n\n" },
129     { "[BR]", "\n"},
130     { "[GRK]", "" },
131     { "[grk]", "" }
132 };
133
134 //! List of replacements for reStructuredText output.
135 const t_sandr sandrRst[] = {
136     { "[TT]", "``" },
137     { "[tt]", "``" },
138     { "[BB]", "**" },
139     { "[bb]", "**" },
140     { "[IT]", "*" },
141     { "[it]", "*" },
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     // [BR] is fundamentally incompatible with rst
180     { "[BR]", "\n\n"},
181     { "[GRK]", "" },
182     { "[grk]", "" }
183 };
184
185 /*! \brief
186  * Replaces all entries from a list of replacements.
187  */
188 std::string repall(const std::string &s, int nsr, const t_sandr sa[])
189 {
190     std::string result(s);
191     for (int i = 0; i < nsr; ++i)
192     {
193         result = replaceAll(result, sa[i].search, sa[i].replace);
194     }
195     return result;
196 }
197
198 /*! \brief
199  * Replaces all entries from a list of replacements.
200  */
201 template <size_t nsr>
202 std::string repall(const std::string &s, const t_sandr (&sa)[nsr])
203 {
204     return repall(s, nsr, sa);
205 }
206
207 /*! \brief
208  * Custom output interface for HelpWriterContext::Impl::processMarkup().
209  *
210  * Provides an interface that is used to implement different types of output
211  * from HelpWriterContext::Impl::processMarkup().
212  */
213 class WrapperInterface
214 {
215     public:
216         virtual ~WrapperInterface() {}
217
218         /*! \brief
219          * Provides the wrapping settings.
220          *
221          * HelpWriterContext::Impl::processMarkup() may provide some default
222          * values for the settings if they are not set; this is the reason the
223          * return value is not const.
224          */
225         virtual TextLineWrapperSettings &settings() = 0;
226         //! Appends the given string to output.
227         virtual void wrap(const std::string &text)  = 0;
228 };
229
230 /*! \brief
231  * Wraps markup output into a single string.
232  */
233 class WrapperToString : public WrapperInterface
234 {
235     public:
236         //! Creates a wrapper with the given settings.
237         explicit WrapperToString(const TextLineWrapperSettings &settings)
238             : wrapper_(settings)
239         {
240         }
241
242         virtual TextLineWrapperSettings &settings()
243         {
244             return wrapper_.settings();
245         }
246         virtual void wrap(const std::string &text)
247         {
248             result_.append(wrapper_.wrapToString(text));
249         }
250         //! Returns the result string.
251         const std::string &result() const { return result_; }
252
253     private:
254         TextLineWrapper         wrapper_;
255         std::string             result_;
256 };
257
258 /*! \brief
259  * Wraps markup output into a vector of string (one line per element).
260  */
261 class WrapperToVector : public WrapperInterface
262 {
263     public:
264         //! Creates a wrapper with the given settings.
265         explicit WrapperToVector(const TextLineWrapperSettings &settings)
266             : wrapper_(settings)
267         {
268         }
269
270         virtual TextLineWrapperSettings &settings()
271         {
272             return wrapper_.settings();
273         }
274         virtual void wrap(const std::string &text)
275         {
276             const std::vector<std::string> &lines = wrapper_.wrapToVector(text);
277             result_.insert(result_.end(), lines.begin(), lines.end());
278         }
279         //! Returns a vector with the output lines.
280         const std::vector<std::string> &result() const { return result_; }
281
282     private:
283         TextLineWrapper          wrapper_;
284         std::vector<std::string> result_;
285 };
286
287 /*! \brief
288  * Makes the string uppercase.
289  *
290  * \param[in] text  Input text.
291  * \returns   \p text with all characters transformed to uppercase.
292  * \throws    std::bad_alloc if out of memory.
293  */
294 std::string toUpperCase(const std::string &text)
295 {
296     std::string result(text);
297     std::transform(result.begin(), result.end(), result.begin(), toupper);
298     return result;
299 }
300
301 //! \}
302
303 }   // namespace
304
305 /********************************************************************
306  * HelpLinks::Impl
307  */
308
309 /*! \internal \brief
310  * Private implementation class for HelpLinks.
311  *
312  * \ingroup module_onlinehelp
313  */
314 class HelpLinks::Impl
315 {
316     public:
317         struct LinkItem
318         {
319             LinkItem(const std::string &linkName,
320                      const std::string &replacement)
321                 : linkName(linkName), replacement(replacement)
322             {
323             }
324             std::string         linkName;
325             std::string         replacement;
326         };
327
328         //! Shorthand for a list of links.
329         typedef std::vector<LinkItem> LinkList;
330
331         //! Initializes empty links with the given format.
332         explicit Impl(HelpOutputFormat format) : format_(format)
333         {
334         }
335
336         //! List of links.
337         LinkList          links_;
338         //! Output format for which the links are formatted.
339         HelpOutputFormat  format_;
340 };
341
342 /********************************************************************
343  * HelpLinks
344  */
345
346 HelpLinks::HelpLinks(HelpOutputFormat format) : impl_(new Impl(format))
347 {
348 }
349
350 HelpLinks::~HelpLinks()
351 {
352 }
353
354 void HelpLinks::addLink(const std::string &linkName,
355                         const std::string &targetName,
356                         const std::string &displayName)
357 {
358     std::string replacement;
359     switch (impl_->format_)
360     {
361         case eHelpOutputFormat_Console:
362             replacement = repall(displayName, sandrTty);
363             break;
364         case eHelpOutputFormat_Rst:
365             replacement = formatString(
366                         ":doc:`%s <%s>`", repall(displayName, sandrTty).c_str(),
367                         targetName.c_str());
368             break;
369         default:
370             GMX_RELEASE_ASSERT(false, "Output format not implemented for links");
371     }
372     impl_->links_.push_back(Impl::LinkItem(linkName, replacement));
373 }
374
375 /********************************************************************
376  * HelpWriterContext::Impl
377  */
378
379 /*! \internal \brief
380  * Private implementation class for HelpWriterContext.
381  *
382  * \ingroup module_onlinehelp
383  */
384 class HelpWriterContext::Impl
385 {
386     public:
387         /*! \brief
388          * Shared, non-modifiable state for context objects.
389          *
390          * Contents of this structure are shared between all context objects
391          * that are created from a common parent.
392          * This state should not be modified after construction.
393          *
394          * \ingroup module_onlinehelp
395          */
396         struct SharedState
397         {
398             //! Initializes the state with the given parameters.
399             SharedState(File *file, HelpOutputFormat format,
400                         const HelpLinks *links)
401                 : file_(*file), format_(format), links_(links)
402             {
403             }
404
405             //! Output file to which the help is written.
406             File                   &file_;
407             //! Output format for the help output.
408             HelpOutputFormat        format_;
409             //! Links to use.
410             const HelpLinks        *links_;
411         };
412
413         struct ReplaceItem
414         {
415             ReplaceItem(const std::string &search,
416                         const std::string &replace)
417                 : search(search), replace(replace)
418             {
419             }
420             std::string         search;
421             std::string         replace;
422         };
423
424         //! Smart pointer type for managing the shared state.
425         typedef boost::shared_ptr<const SharedState> StatePointer;
426         //! Shorthand for a list of markup/other replacements.
427         typedef std::vector<ReplaceItem> ReplaceList;
428
429         //! Initializes the context with the given state.
430         explicit Impl(const StatePointer &state)
431             : state_(state)
432         {
433             initDefaultReplacements();
434         }
435
436         //! Initializes default replacements for the chosen output format.
437         void initDefaultReplacements();
438         //! Adds a new replacement.
439         void addReplacement(const std::string &search,
440                             const std::string &replace)
441         {
442             replacements_.push_back(ReplaceItem(search, replace));
443         }
444
445         //! Replaces links in a given string.
446         std::string replaceLinks(const std::string &input) const;
447
448         /*! \brief
449          * Process markup and wrap lines within a block of text.
450          *
451          * \param[in] text     Text to process.
452          * \param     wrapper  Object used to wrap the text.
453          *
454          * The \p wrapper should take care of either writing the text to output
455          * or providing an interface for the caller to retrieve the output.
456          */
457         void processMarkup(const std::string &text,
458                            WrapperInterface  *wrapper) const;
459
460         //! Constant state shared by all child context objects.
461         StatePointer            state_;
462         //! List of markup/other replacements.
463         ReplaceList             replacements_;
464
465     private:
466         GMX_DISALLOW_ASSIGN(Impl);
467 };
468
469 void HelpWriterContext::Impl::initDefaultReplacements()
470 {
471     const char *program = getProgramContext().programName();
472     addReplacement("[PROGRAM]", program);
473 }
474
475 std::string HelpWriterContext::Impl::replaceLinks(const std::string &input) const
476 {
477     std::string result(input);
478     if (state_->links_ != NULL)
479     {
480         HelpLinks::Impl::LinkList::const_iterator link;
481         for (link  = state_->links_->impl_->links_.begin();
482              link != state_->links_->impl_->links_.end(); ++link)
483         {
484             result = replaceAllWords(result, link->linkName, link->replacement);
485         }
486     }
487     return result;
488 }
489
490 void HelpWriterContext::Impl::processMarkup(const std::string &text,
491                                             WrapperInterface  *wrapper) const
492 {
493     std::string result(text);
494     for (ReplaceList::const_iterator i = replacements_.begin();
495          i != replacements_.end(); ++i)
496     {
497         result = replaceAll(result, i->search, i->replace);
498     }
499     switch (state_->format_)
500     {
501         case eHelpOutputFormat_Console:
502         {
503             result = repall(result, sandrTty);
504             result = replaceLinks(result);
505             return wrapper->wrap(result);
506         }
507         case eHelpOutputFormat_Rst:
508         {
509             result = repall(result, sandrRst);
510             result = replaceLinks(result);
511             return wrapper->wrap(result);
512         }
513         default:
514             GMX_THROW(InternalError("Invalid help output format"));
515     }
516 }
517
518 /********************************************************************
519  * HelpWriterContext
520  */
521
522 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format)
523     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, NULL))))
524 {
525 }
526
527 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format,
528                                      const HelpLinks *links)
529     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, links))))
530 {
531     if (links != NULL)
532     {
533         GMX_RELEASE_ASSERT(links->impl_->format_ == format,
534                            "Links must have the same output format as the context");
535     }
536 }
537
538 HelpWriterContext::HelpWriterContext(Impl *impl)
539     : impl_(impl)
540 {
541 }
542
543 HelpWriterContext::HelpWriterContext(const HelpWriterContext &other)
544     : impl_(new Impl(*other.impl_))
545 {
546 }
547
548 HelpWriterContext::~HelpWriterContext()
549 {
550 }
551
552 void HelpWriterContext::setReplacement(const std::string &search,
553                                        const std::string &replace)
554 {
555     impl_->addReplacement(search, replace);
556 }
557
558 HelpOutputFormat HelpWriterContext::outputFormat() const
559 {
560     return impl_->state_->format_;
561 }
562
563 File &HelpWriterContext::outputFile() const
564 {
565     return impl_->state_->file_;
566 }
567
568 std::string
569 HelpWriterContext::substituteMarkupAndWrapToString(
570         const TextLineWrapperSettings &settings, const std::string &text) const
571 {
572     WrapperToString wrapper(settings);
573     impl_->processMarkup(text, &wrapper);
574     return wrapper.result();
575 }
576
577 std::vector<std::string>
578 HelpWriterContext::substituteMarkupAndWrapToVector(
579         const TextLineWrapperSettings &settings, const std::string &text) const
580 {
581     WrapperToVector wrapper(settings);
582     impl_->processMarkup(text, &wrapper);
583     return wrapper.result();
584 }
585
586 void HelpWriterContext::writeTitle(const std::string &title) const
587 {
588     File &file = outputFile();
589     switch (outputFormat())
590     {
591         case eHelpOutputFormat_Console:
592             file.writeLine(toUpperCase(title));
593             file.writeLine();
594             break;
595         case eHelpOutputFormat_Rst:
596             file.writeLine(title);
597             file.writeLine(std::string(title.length(), '-'));
598             break;
599         default:
600             GMX_THROW(NotImplementedError(
601                               "This output format is not implemented"));
602     }
603 }
604
605 void HelpWriterContext::writeTextBlock(const std::string &text) const
606 {
607     TextLineWrapperSettings settings;
608     if (outputFormat() == eHelpOutputFormat_Console)
609     {
610         settings.setLineLength(78);
611     }
612     outputFile().writeLine(substituteMarkupAndWrapToString(settings, text));
613 }
614
615 void HelpWriterContext::writeOptionListStart() const
616 {
617 }
618
619 void HelpWriterContext::writeOptionItem(const std::string &name,
620                                         const std::string &args,
621                                         const std::string &description) const
622 {
623     File &file = outputFile();
624     switch (outputFormat())
625     {
626         case eHelpOutputFormat_Console:
627             // TODO: Generalize this when there is need for it; the current,
628             // special implementation is in CommandLineHelpWriter.
629             GMX_THROW(NotImplementedError("Option item formatting for console output not implemented"));
630             break;
631         case eHelpOutputFormat_Rst:
632         {
633             file.writeLine(formatString("``%s`` %s", name.c_str(), args.c_str()));
634             TextLineWrapperSettings settings;
635             settings.setIndent(4);
636             file.writeLine(substituteMarkupAndWrapToString(settings, description));
637             break;
638         }
639         default:
640             GMX_THROW(NotImplementedError(
641                               "This output format is not implemented"));
642     }
643 }
644
645 void HelpWriterContext::writeOptionListEnd() const
646 {
647 }
648
649 } // namespace gmx