f348952c9952a595f75eac3fdec2c118bd9127ab
[alexxy/gromacs.git] / src / gromacs / onlinehelp / helpformat.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2016,2017,2019, 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 functions in helpformat.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_onlinehelp
41  */
42 #include "gmxpre.h"
43
44 #include "helpformat.h"
45
46 #include <algorithm>
47 #include <string>
48 #include <vector>
49
50 #include "gromacs/onlinehelp/helpwritercontext.h"
51 #include "gromacs/utility/gmxassert.h"
52 #include "gromacs/utility/stringutil.h"
53
54 namespace gmx
55 {
56
57 /********************************************************************
58  * TextTableFormatter::Impl
59  */
60
61 /*! \internal \brief
62  * Private implementation class for TextTableFormatter.
63  *
64  * \ingroup module_onlinehelp
65  */
66 class TextTableFormatter::Impl
67 {
68 public:
69     /*! \internal \brief
70      * Manages a single column for TextTableFormatter.
71      *
72      * \ingroup module_onlinehelp
73      */
74     struct ColumnData
75     {
76         //! Initializes a text table column with given values.
77         ColumnData(const char* title, int width, bool bWrap) :
78             title_(title != nullptr ? title : ""),
79             width_(width),
80             bWrap_(bWrap),
81             firstLine_(0),
82             nextLineIndex_(0),
83             nextLineOffset_(0)
84         {
85             GMX_ASSERT(width >= 0, "Negative width not possible");
86             GMX_ASSERT(title_.length() <= static_cast<size_t>(width),
87                        "Title too long for column width");
88         }
89
90         //! Returns the title of the column.
91         const std::string& title() const { return title_; }
92         //! Returns the width of the column.
93         int width() const { return width_; }
94         /*! \brief
95          * Returns the first line offset for the current row.
96          *
97          * Note that the return value may be outside the printed lines if
98          * there is no text.
99          */
100         int firstLine() const { return firstLine_; }
101
102         /*! \brief
103          * Resets the formatting state.
104          *
105          * After this call, textForNextLine() and hasLinesRemaining() can
106          * be used to format the lines for the column.
107          */
108         void startFormatting()
109         {
110             nextLineIndex_  = (!lines_.empty() ? -firstLine_ : 0);
111             nextLineOffset_ = 0;
112         }
113         //! Whether there are lines remaining for textForNextLine().
114         bool hasLinesRemaining() const { return nextLineIndex_ < ssize(lines_); }
115         /*! \brief
116          * Returns the text for the next line.
117          *
118          * \param[in] columnWidth  Width to wrap the text to.
119          * \returns   Text for the next line, or empty string if there is
120          *   no text for this column.
121          */
122         std::string textForNextLine(int columnWidth)
123         {
124             if (nextLineIndex_ < 0 || !hasLinesRemaining())
125             {
126                 ++nextLineIndex_;
127                 return std::string();
128             }
129             if (bWrap_)
130             {
131                 TextLineWrapperSettings settings;
132                 settings.setLineLength(columnWidth);
133                 TextLineWrapper    wrapper(settings);
134                 const std::string& currentLine = lines_[nextLineIndex_];
135                 const size_t       prevOffset  = nextLineOffset_;
136                 const size_t       nextOffset  = wrapper.findNextLine(currentLine, prevOffset);
137                 if (nextOffset >= currentLine.size())
138                 {
139                     ++nextLineIndex_;
140                     nextLineOffset_ = 0;
141                 }
142                 else
143                 {
144                     nextLineOffset_ = nextOffset;
145                 }
146                 return wrapper.formatLine(currentLine, prevOffset, nextOffset);
147             }
148             else
149             {
150                 return lines_[nextLineIndex_++];
151             }
152         }
153
154         //! Statit data: title of the column.
155         std::string title_;
156         //! Static data: width of the column.
157         int width_;
158         //! Static data: whether to automatically wrap input text.
159         bool bWrap_;
160         //! First line offset for the current row.
161         int firstLine_;
162         //! Text lines for the current row.
163         std::vector<std::string> lines_;
164         //! Formatting state: index in `lines_` for the next line.
165         int nextLineIndex_;
166         //! Formatting state: offset within line `nextLineIndex_` for the next line.
167         size_t nextLineOffset_;
168     };
169
170     //! Container type for column data.
171     typedef std::vector<ColumnData> ColumnList;
172
173     //! Initializes data for an empty formatter.
174     Impl();
175
176     /*! \brief
177      * Convenience method for checked access to data for a column.
178      *
179      * \param[in] index  Zero-based column index.
180      * \returns   \c columns_[index]
181      */
182     ColumnData& columnData(int index)
183     {
184         GMX_ASSERT(index >= 0 && index < ssize(columns_), "Invalid column index");
185         return columns_[index];
186     }
187     //! \copydoc columnData()
188     const ColumnData& columnData(int index) const
189     {
190         return const_cast<Impl*>(this)->columnData(index);
191     }
192
193     //! Container for column data.
194     ColumnList columns_;
195     //! Indentation before the first column.
196     int firstColumnIndent_;
197     //! Indentation before the last column if folded.
198     int foldLastColumnToNextLineIndent_;
199     //! If true, no output has yet been produced.
200     bool bFirstRow_;
201     //! If true, a header will be printed before the first row.
202     bool bPrintHeader_;
203 };
204
205 TextTableFormatter::Impl::Impl() :
206     firstColumnIndent_(0),
207     foldLastColumnToNextLineIndent_(-1),
208     bFirstRow_(true),
209     bPrintHeader_(false)
210 {
211 }
212
213 /********************************************************************
214  * TextTableFormatter
215  */
216
217 TextTableFormatter::TextTableFormatter() : impl_(new Impl) {}
218
219 TextTableFormatter::~TextTableFormatter() {}
220
221 void TextTableFormatter::addColumn(const char* title, int width, bool bWrap)
222 {
223     if (title != nullptr && title[0] != '\0')
224     {
225         impl_->bPrintHeader_ = true;
226     }
227     impl_->columns_.emplace_back(title, width, bWrap);
228 }
229
230 void TextTableFormatter::setFirstColumnIndent(int indent)
231 {
232     GMX_RELEASE_ASSERT(indent >= 0, "Negative indentation not allowed");
233     impl_->firstColumnIndent_ = indent;
234 }
235
236 void TextTableFormatter::setFoldLastColumnToNextLine(int indent)
237 {
238     impl_->foldLastColumnToNextLineIndent_ = indent;
239 }
240
241 bool TextTableFormatter::didOutput() const
242 {
243     return !impl_->bFirstRow_;
244 }
245
246 void TextTableFormatter::clear()
247 {
248     Impl::ColumnList::iterator i;
249     for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
250     {
251         i->firstLine_ = 0;
252         i->lines_.clear();
253     }
254 }
255
256 void TextTableFormatter::addColumnLine(int index, const std::string& text)
257 {
258     Impl::ColumnData&        column = impl_->columnData(index);
259     TextLineWrapper          wrapper;
260     std::vector<std::string> lines(wrapper.wrapToVector(text));
261     column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
262 }
263
264 void TextTableFormatter::addColumnHelpTextBlock(int                      index,
265                                                 const HelpWriterContext& context,
266                                                 const std::string&       text)
267 {
268     Impl::ColumnData&       column = impl_->columnData(index);
269     TextLineWrapperSettings settings;
270     // TODO: If in the future, there is actually a coupling between the markup
271     // and the wrapping, this must be postponed into formatRow(), where we do
272     // the actual line wrapping.
273     std::vector<std::string> lines(context.substituteMarkupAndWrapToVector(settings, text));
274     column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
275 }
276
277 void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
278 {
279     GMX_ASSERT(firstLine >= 0, "Invalid first line");
280     Impl::ColumnData& column = impl_->columnData(index);
281     column.firstLine_        = firstLine;
282 }
283
284 std::string TextTableFormatter::formatRow()
285 {
286     std::string                result;
287     Impl::ColumnList::iterator column;
288     // Print a header if this is the first line.
289     if (impl_->bPrintHeader_ && impl_->bFirstRow_)
290     {
291         size_t totalWidth = 0;
292         result.append(impl_->firstColumnIndent_, ' ');
293         for (column = impl_->columns_.begin(); column != impl_->columns_.end(); ++column)
294         {
295             std::string title(column->title());
296             if (column != impl_->columns_.end() - 1)
297             {
298                 title.resize(column->width() + 1, ' ');
299                 totalWidth += title.length();
300             }
301             else
302             {
303                 totalWidth += std::min(column->width(), static_cast<int>(title.length() + 13));
304             }
305             result.append(title);
306         }
307         result.append("\n");
308         result.append(impl_->firstColumnIndent_, ' ');
309         result.append(totalWidth, '-');
310         result.append("\n");
311     }
312
313     // Format all the lines, one column at a time.
314     std::vector<std::string> lines;
315     std::vector<std::string> columnLines;
316     int                      currentWidth    = 0;
317     bool                     bFoldLastColumn = false;
318     for (column = impl_->columns_.begin(); column != impl_->columns_.end(); ++column)
319     {
320         // Format the column into columnLines.
321         column->startFormatting();
322         columnLines.clear();
323         columnLines.reserve(lines.size());
324         for (size_t line = 0; column->hasLinesRemaining(); ++line)
325         {
326             int columnWidth = column->width();
327             if (line < lines.size())
328             {
329                 const int overflow = static_cast<int>(lines[line].length()) - currentWidth;
330                 if (overflow > 0)
331                 {
332                     if (overflow > columnWidth && column->bWrap_)
333                     {
334                         columnLines.emplace_back();
335                         continue;
336                     }
337                     columnWidth -= overflow;
338                 }
339             }
340             columnLines.push_back(column->textForNextLine(columnWidth));
341         }
342         if (column == impl_->columns_.end() - 1 && impl_->foldLastColumnToNextLineIndent_ >= 0
343             && columnLines.size() >= lines.size() + column->lines_.size())
344         {
345             bFoldLastColumn = true;
346             currentWidth += column->width();
347             break;
348         }
349         // Add columnLines into lines.
350         if (lines.size() < columnLines.size())
351         {
352             lines.resize(columnLines.size());
353         }
354         for (size_t line = 0; line < columnLines.size(); ++line)
355         {
356             if (column != impl_->columns_.begin() && !columnLines[line].empty())
357             {
358                 lines[line].append(" ");
359                 if (static_cast<int>(lines[line].length()) < currentWidth)
360                 {
361                     lines[line].resize(currentWidth, ' ');
362                 }
363             }
364             lines[line].append(columnLines[line]);
365         }
366         currentWidth += column->width() + 1;
367     }
368
369     // Construct the result by concatenating all the lines.
370     std::vector<std::string>::const_iterator line;
371     for (line = lines.begin(); line != lines.end(); ++line)
372     {
373         result.append(impl_->firstColumnIndent_, ' ');
374         result.append(*line);
375         result.append("\n");
376     }
377
378     if (bFoldLastColumn)
379     {
380         Impl::ColumnList::reference lastColumn = impl_->columns_.back();
381         const int totalIndent = impl_->firstColumnIndent_ + impl_->foldLastColumnToNextLineIndent_;
382         lastColumn.startFormatting();
383         currentWidth -= impl_->foldLastColumnToNextLineIndent_;
384         while (lastColumn.hasLinesRemaining())
385         {
386             result.append(totalIndent, ' ');
387             result.append(lastColumn.textForNextLine(currentWidth));
388             result.append("\n");
389         }
390     }
391
392     impl_->bFirstRow_ = false;
393     clear();
394     return result;
395 }
396
397 } // namespace gmx