2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013,2014,2016,2017 by the GROMACS development team.
5 * Copyright (c) 2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Implements functions in helpformat.h.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_onlinehelp
45 #include "helpformat.h"
51 #include "gromacs/onlinehelp/helpwritercontext.h"
52 #include "gromacs/utility/gmxassert.h"
53 #include "gromacs/utility/stringutil.h"
58 /********************************************************************
59 * TextTableFormatter::Impl
63 * Private implementation class for TextTableFormatter.
65 * \ingroup module_onlinehelp
67 class TextTableFormatter::Impl
71 * Manages a single column for TextTableFormatter.
73 * \ingroup module_onlinehelp
77 //! Initializes a text table column with given values.
78 ColumnData(const char* title, int width, bool bWrap) :
79 title_(title != nullptr ? title : ""),
86 GMX_ASSERT(width >= 0, "Negative width not possible");
87 GMX_ASSERT(title_.length() <= static_cast<size_t>(width),
88 "Title too long for column width");
91 //! Returns the title of the column.
92 const std::string& title() const { return title_; }
93 //! Returns the width of the column.
94 int width() const { return width_; }
96 * Returns the first line offset for the current row.
98 * Note that the return value may be outside the printed lines if
101 int firstLine() const { return firstLine_; }
104 * Resets the formatting state.
106 * After this call, textForNextLine() and hasLinesRemaining() can
107 * be used to format the lines for the column.
109 void startFormatting()
111 nextLineIndex_ = (!lines_.empty() ? -firstLine_ : 0);
114 //! Whether there are lines remaining for textForNextLine().
115 bool hasLinesRemaining() const { return nextLineIndex_ < ssize(lines_); }
117 * Returns the text for the next line.
119 * \param[in] columnWidth Width to wrap the text to.
120 * \returns Text for the next line, or empty string if there is
121 * no text for this column.
123 std::string textForNextLine(int columnWidth)
125 if (nextLineIndex_ < 0 || !hasLinesRemaining())
128 return std::string();
132 TextLineWrapperSettings settings;
133 settings.setLineLength(columnWidth);
134 TextLineWrapper wrapper(settings);
135 const std::string& currentLine = lines_[nextLineIndex_];
136 const size_t prevOffset = nextLineOffset_;
137 const size_t nextOffset = wrapper.findNextLine(currentLine, prevOffset);
138 if (nextOffset >= currentLine.size())
145 nextLineOffset_ = nextOffset;
147 return wrapper.formatLine(currentLine, prevOffset, nextOffset);
151 return lines_[nextLineIndex_++];
155 //! Statit data: title of the column.
157 //! Static data: width of the column.
159 //! Static data: whether to automatically wrap input text.
161 //! First line offset for the current row.
163 //! Text lines for the current row.
164 std::vector<std::string> lines_;
165 //! Formatting state: index in `lines_` for the next line.
167 //! Formatting state: offset within line `nextLineIndex_` for the next line.
168 size_t nextLineOffset_;
171 //! Container type for column data.
172 typedef std::vector<ColumnData> ColumnList;
174 //! Initializes data for an empty formatter.
178 * Convenience method for checked access to data for a column.
180 * \param[in] index Zero-based column index.
181 * \returns \c columns_[index]
183 ColumnData& columnData(int index)
185 GMX_ASSERT(index >= 0 && index < ssize(columns_), "Invalid column index");
186 return columns_[index];
188 //! \copydoc columnData()
189 const ColumnData& columnData(int index) const
191 return const_cast<Impl*>(this)->columnData(index);
194 //! Container for column data.
196 //! Indentation before the first column.
197 int firstColumnIndent_;
198 //! Indentation before the last column if folded.
199 int foldLastColumnToNextLineIndent_;
200 //! If true, no output has yet been produced.
202 //! If true, a header will be printed before the first row.
206 TextTableFormatter::Impl::Impl() :
207 firstColumnIndent_(0),
208 foldLastColumnToNextLineIndent_(-1),
214 /********************************************************************
218 TextTableFormatter::TextTableFormatter() : impl_(new Impl) {}
220 TextTableFormatter::~TextTableFormatter() {}
222 void TextTableFormatter::addColumn(const char* title, int width, bool bWrap)
224 if (title != nullptr && title[0] != '\0')
226 impl_->bPrintHeader_ = true;
228 impl_->columns_.emplace_back(title, width, bWrap);
231 void TextTableFormatter::setFirstColumnIndent(int indent)
233 GMX_RELEASE_ASSERT(indent >= 0, "Negative indentation not allowed");
234 impl_->firstColumnIndent_ = indent;
237 void TextTableFormatter::setFoldLastColumnToNextLine(int indent)
239 impl_->foldLastColumnToNextLineIndent_ = indent;
242 bool TextTableFormatter::didOutput() const
244 return !impl_->bFirstRow_;
247 void TextTableFormatter::clear()
249 Impl::ColumnList::iterator i;
250 for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
257 void TextTableFormatter::addColumnLine(int index, const std::string& text)
259 Impl::ColumnData& column = impl_->columnData(index);
260 TextLineWrapper wrapper;
261 std::vector<std::string> lines(wrapper.wrapToVector(text));
262 column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
265 void TextTableFormatter::addColumnHelpTextBlock(int index,
266 const HelpWriterContext& context,
267 const std::string& text)
269 Impl::ColumnData& column = impl_->columnData(index);
270 TextLineWrapperSettings settings;
271 // TODO: If in the future, there is actually a coupling between the markup
272 // and the wrapping, this must be postponed into formatRow(), where we do
273 // the actual line wrapping.
274 std::vector<std::string> lines(context.substituteMarkupAndWrapToVector(settings, text));
275 column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
278 void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
280 GMX_ASSERT(firstLine >= 0, "Invalid first line");
281 Impl::ColumnData& column = impl_->columnData(index);
282 column.firstLine_ = firstLine;
285 std::string TextTableFormatter::formatRow()
288 Impl::ColumnList::iterator column;
289 // Print a header if this is the first line.
290 if (impl_->bPrintHeader_ && impl_->bFirstRow_)
292 size_t totalWidth = 0;
293 result.append(impl_->firstColumnIndent_, ' ');
294 for (column = impl_->columns_.begin(); column != impl_->columns_.end(); ++column)
296 std::string title(column->title());
297 if (column != impl_->columns_.end() - 1)
299 title.resize(column->width() + 1, ' ');
300 totalWidth += title.length();
304 totalWidth += std::min(column->width(), static_cast<int>(title.length() + 13));
306 result.append(title);
309 result.append(impl_->firstColumnIndent_, ' ');
310 result.append(totalWidth, '-');
314 // Format all the lines, one column at a time.
315 std::vector<std::string> lines;
316 std::vector<std::string> columnLines;
317 int currentWidth = 0;
318 bool bFoldLastColumn = false;
319 for (column = impl_->columns_.begin(); column != impl_->columns_.end(); ++column)
321 // Format the column into columnLines.
322 column->startFormatting();
324 columnLines.reserve(lines.size());
325 for (size_t line = 0; column->hasLinesRemaining(); ++line)
327 int columnWidth = column->width();
328 if (line < lines.size())
330 const int overflow = static_cast<int>(lines[line].length()) - currentWidth;
333 if (overflow > columnWidth && column->bWrap_)
335 columnLines.emplace_back();
338 columnWidth -= overflow;
341 columnLines.push_back(column->textForNextLine(columnWidth));
343 if (column == impl_->columns_.end() - 1 && impl_->foldLastColumnToNextLineIndent_ >= 0
344 && columnLines.size() >= lines.size() + column->lines_.size())
346 bFoldLastColumn = true;
347 currentWidth += column->width();
350 // Add columnLines into lines.
351 if (lines.size() < columnLines.size())
353 lines.resize(columnLines.size());
355 for (size_t line = 0; line < columnLines.size(); ++line)
357 if (column != impl_->columns_.begin() && !columnLines[line].empty())
359 lines[line].append(" ");
360 if (static_cast<int>(lines[line].length()) < currentWidth)
362 lines[line].resize(currentWidth, ' ');
365 lines[line].append(columnLines[line]);
367 currentWidth += column->width() + 1;
370 // Construct the result by concatenating all the lines.
371 std::vector<std::string>::const_iterator line;
372 for (line = lines.begin(); line != lines.end(); ++line)
374 result.append(impl_->firstColumnIndent_, ' ');
375 result.append(*line);
381 Impl::ColumnList::reference lastColumn = impl_->columns_.back();
382 const int totalIndent = impl_->firstColumnIndent_ + impl_->foldLastColumnToNextLineIndent_;
383 lastColumn.startFormatting();
384 currentWidth -= impl_->foldLastColumnToNextLineIndent_;
385 while (lastColumn.hasLinesRemaining())
387 result.append(totalIndent, ' ');
388 result.append(lastColumn.textForNextLine(currentWidth));
393 impl_->bFirstRow_ = false;