d549ffda7965f194a0bd0613401918f2bfe96066
[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 //! \}
304
305 }   // namespace
306
307 /********************************************************************
308  * HelpLinks::Impl
309  */
310
311 /*! \internal \brief
312  * Private implementation class for HelpLinks.
313  *
314  * \ingroup module_onlinehelp
315  */
316 class HelpLinks::Impl
317 {
318     public:
319         struct LinkItem
320         {
321             LinkItem(const std::string &linkName,
322                      const std::string &replacement)
323                 : linkName(linkName), replacement(replacement)
324             {
325             }
326             std::string         linkName;
327             std::string         replacement;
328         };
329
330         //! Shorthand for a list of links.
331         typedef std::vector<LinkItem> LinkList;
332
333         //! Initializes empty links with the given format.
334         explicit Impl(HelpOutputFormat format) : format_(format)
335         {
336         }
337
338         //! List of links.
339         LinkList          links_;
340         //! Output format for which the links are formatted.
341         HelpOutputFormat  format_;
342 };
343
344 /********************************************************************
345  * HelpLinks
346  */
347
348 HelpLinks::HelpLinks(HelpOutputFormat format) : impl_(new Impl(format))
349 {
350 }
351
352 HelpLinks::~HelpLinks()
353 {
354 }
355
356 void HelpLinks::addLink(const std::string &linkName,
357                         const std::string &targetName,
358                         const std::string &displayName)
359 {
360     std::string replacement;
361     switch (impl_->format_)
362     {
363         case eHelpOutputFormat_Console:
364             replacement = repall(displayName, sandrTty);
365             break;
366         case eHelpOutputFormat_Rst:
367             replacement = targetName;
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             result = replaceAll(result, "[REF]", "");
512             result = replaceAll(result, "[ref]", "");
513             return wrapper->wrap(result);
514         }
515         default:
516             GMX_THROW(InternalError("Invalid help output format"));
517     }
518 }
519
520 /********************************************************************
521  * HelpWriterContext
522  */
523
524 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format)
525     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, NULL))))
526 {
527 }
528
529 HelpWriterContext::HelpWriterContext(File *file, HelpOutputFormat format,
530                                      const HelpLinks *links)
531     : impl_(new Impl(Impl::StatePointer(new Impl::SharedState(file, format, links))))
532 {
533     if (links != NULL)
534     {
535         GMX_RELEASE_ASSERT(links->impl_->format_ == format,
536                            "Links must have the same output format as the context");
537     }
538 }
539
540 HelpWriterContext::HelpWriterContext(Impl *impl)
541     : impl_(impl)
542 {
543 }
544
545 HelpWriterContext::HelpWriterContext(const HelpWriterContext &other)
546     : impl_(new Impl(*other.impl_))
547 {
548 }
549
550 HelpWriterContext::~HelpWriterContext()
551 {
552 }
553
554 void HelpWriterContext::setReplacement(const std::string &search,
555                                        const std::string &replace)
556 {
557     impl_->addReplacement(search, replace);
558 }
559
560 HelpOutputFormat HelpWriterContext::outputFormat() const
561 {
562     return impl_->state_->format_;
563 }
564
565 File &HelpWriterContext::outputFile() const
566 {
567     return impl_->state_->file_;
568 }
569
570 std::string
571 HelpWriterContext::substituteMarkupAndWrapToString(
572         const TextLineWrapperSettings &settings, const std::string &text) const
573 {
574     WrapperToString wrapper(settings);
575     impl_->processMarkup(text, &wrapper);
576     return wrapper.result();
577 }
578
579 std::vector<std::string>
580 HelpWriterContext::substituteMarkupAndWrapToVector(
581         const TextLineWrapperSettings &settings, const std::string &text) const
582 {
583     WrapperToVector wrapper(settings);
584     impl_->processMarkup(text, &wrapper);
585     return wrapper.result();
586 }
587
588 void HelpWriterContext::writeTitle(const std::string &title) const
589 {
590     File &file = outputFile();
591     switch (outputFormat())
592     {
593         case eHelpOutputFormat_Console:
594             file.writeLine(toUpperCase(title));
595             file.writeLine();
596             break;
597         case eHelpOutputFormat_Rst:
598             file.writeLine(title);
599             file.writeLine(std::string(title.length(), '-'));
600             break;
601         default:
602             GMX_THROW(NotImplementedError(
603                               "This output format is not implemented"));
604     }
605 }
606
607 void HelpWriterContext::writeTextBlock(const std::string &text) const
608 {
609     TextLineWrapperSettings settings;
610     if (outputFormat() == eHelpOutputFormat_Console)
611     {
612         settings.setLineLength(78);
613     }
614     outputFile().writeLine(substituteMarkupAndWrapToString(settings, text));
615 }
616
617 void HelpWriterContext::writeOptionListStart() const
618 {
619 }
620
621 void HelpWriterContext::writeOptionItem(const std::string &name,
622                                         const std::string &args,
623                                         const std::string &description) const
624 {
625     File &file = outputFile();
626     switch (outputFormat())
627     {
628         case eHelpOutputFormat_Console:
629             // TODO: Generalize this when there is need for it; the current,
630             // special implementation is in CommandLineHelpWriter.
631             GMX_THROW(NotImplementedError("Option item formatting for console output not implemented"));
632             break;
633         case eHelpOutputFormat_Rst:
634         {
635             file.writeLine(formatString("``%s`` %s", name.c_str(), args.c_str()));
636             TextLineWrapperSettings settings;
637             settings.setIndent(4);
638             file.writeLine(substituteMarkupAndWrapToString(settings, description));
639             break;
640         }
641         default:
642             GMX_THROW(NotImplementedError(
643                               "This output format is not implemented"));
644     }
645 }
646
647 void HelpWriterContext::writeOptionListEnd() const
648 {
649 }
650
651 } // namespace gmx