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,2021, 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"
54 #include "gromacs/utility/basedefinitions.h"
59 /********************************************************************
60 * TextTableFormatter::Impl
64 * Private implementation class for TextTableFormatter.
66 * \ingroup module_onlinehelp
68 class TextTableFormatter::Impl
72 * Manages a single column for TextTableFormatter.
74 * \ingroup module_onlinehelp
78 //! Initializes a text table column with given values.
79 ColumnData(const char* title, int width, bool bWrap) :
80 title_(title != nullptr ? title : ""),
87 GMX_ASSERT(width >= 0, "Negative width not possible");
88 GMX_ASSERT(title_.length() <= static_cast<size_t>(width),
89 "Title too long for column width");
92 //! Returns the title of the column.
93 const std::string& title() const { return title_; }
94 //! Returns the width of the column.
95 int width() const { return width_; }
97 * Returns the first line offset for the current row.
99 * Note that the return value may be outside the printed lines if
102 int firstLine() const { return firstLine_; }
105 * Resets the formatting state.
107 * After this call, textForNextLine() and hasLinesRemaining() can
108 * be used to format the lines for the column.
110 void startFormatting()
112 nextLineIndex_ = (!lines_.empty() ? -firstLine_ : 0);
115 //! Whether there are lines remaining for textForNextLine().
116 bool hasLinesRemaining() const { return nextLineIndex_ < ssize(lines_); }
118 * Returns the text for the next line.
120 * \param[in] columnWidth Width to wrap the text to.
121 * \returns Text for the next line, or empty string if there is
122 * no text for this column.
124 std::string textForNextLine(int columnWidth)
126 if (nextLineIndex_ < 0 || !hasLinesRemaining())
129 return std::string();
133 TextLineWrapperSettings settings;
134 settings.setLineLength(columnWidth);
135 TextLineWrapper wrapper(settings);
136 const std::string& currentLine = lines_[nextLineIndex_];
137 const size_t prevOffset = nextLineOffset_;
138 const size_t nextOffset = wrapper.findNextLine(currentLine, prevOffset);
139 if (nextOffset >= currentLine.size())
146 nextLineOffset_ = nextOffset;
148 return wrapper.formatLine(currentLine, prevOffset, nextOffset);
152 return lines_[nextLineIndex_++];
156 //! Statit data: title of the column.
158 //! Static data: width of the column.
160 //! Static data: whether to automatically wrap input text.
162 //! First line offset for the current row.
164 //! Text lines for the current row.
165 std::vector<std::string> lines_;
166 //! Formatting state: index in `lines_` for the next line.
168 //! Formatting state: offset within line `nextLineIndex_` for the next line.
169 size_t nextLineOffset_;
172 //! Container type for column data.
173 typedef std::vector<ColumnData> ColumnList;
175 //! Initializes data for an empty formatter.
179 * Convenience method for checked access to data for a column.
181 * \param[in] index Zero-based column index.
182 * \returns \c columns_[index]
184 ColumnData& columnData(int index)
186 GMX_ASSERT(index >= 0 && index < ssize(columns_), "Invalid column index");
187 return columns_[index];
189 //! \copydoc columnData()
190 const ColumnData& columnData(int index) const
192 return const_cast<Impl*>(this)->columnData(index);
195 //! Container for column data.
197 //! Indentation before the first column.
198 int firstColumnIndent_;
199 //! Indentation before the last column if folded.
200 int foldLastColumnToNextLineIndent_;
201 //! If true, no output has yet been produced.
203 //! If true, a header will be printed before the first row.
207 TextTableFormatter::Impl::Impl() :
208 firstColumnIndent_(0),
209 foldLastColumnToNextLineIndent_(-1),
215 /********************************************************************
219 TextTableFormatter::TextTableFormatter() : impl_(new Impl) {}
221 TextTableFormatter::~TextTableFormatter() {}
223 void TextTableFormatter::addColumn(const char* title, int width, bool bWrap)
225 if (title != nullptr && title[0] != '\0')
227 impl_->bPrintHeader_ = true;
229 impl_->columns_.emplace_back(title, width, bWrap);
232 void TextTableFormatter::setFirstColumnIndent(int indent)
234 GMX_RELEASE_ASSERT(indent >= 0, "Negative indentation not allowed");
235 impl_->firstColumnIndent_ = indent;
238 void TextTableFormatter::setFoldLastColumnToNextLine(int indent)
240 impl_->foldLastColumnToNextLineIndent_ = indent;
243 bool TextTableFormatter::didOutput() const
245 return !impl_->bFirstRow_;
248 void TextTableFormatter::clear()
250 Impl::ColumnList::iterator i;
251 for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
258 void TextTableFormatter::addColumnLine(int index, const std::string& text)
260 Impl::ColumnData& column = impl_->columnData(index);
261 TextLineWrapper wrapper;
262 std::vector<std::string> lines(wrapper.wrapToVector(text));
263 column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
266 void TextTableFormatter::addColumnHelpTextBlock(int index,
267 const HelpWriterContext& context,
268 const std::string& text)
270 Impl::ColumnData& column = impl_->columnData(index);
271 TextLineWrapperSettings settings;
272 // TODO: If in the future, there is actually a coupling between the markup
273 // and the wrapping, this must be postponed into formatRow(), where we do
274 // the actual line wrapping.
275 std::vector<std::string> lines(context.substituteMarkupAndWrapToVector(settings, text));
276 column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
279 void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
281 GMX_ASSERT(firstLine >= 0, "Invalid first line");
282 Impl::ColumnData& column = impl_->columnData(index);
283 column.firstLine_ = firstLine;
286 std::string TextTableFormatter::formatRow()
289 Impl::ColumnList::iterator column;
290 // Print a header if this is the first line.
291 if (impl_->bPrintHeader_ && impl_->bFirstRow_)
293 size_t totalWidth = 0;
294 result.append(impl_->firstColumnIndent_, ' ');
295 for (column = impl_->columns_.begin(); column != impl_->columns_.end(); ++column)
297 std::string title(column->title());
298 if (column != impl_->columns_.end() - 1)
300 title.resize(column->width() + 1, ' ');
301 totalWidth += title.length();
305 totalWidth += std::min(column->width(), static_cast<int>(title.length() + 13));
307 result.append(title);
310 result.append(impl_->firstColumnIndent_, ' ');
311 result.append(totalWidth, '-');
315 // Format all the lines, one column at a time.
316 std::vector<std::string> lines;
317 std::vector<std::string> columnLines;
318 int currentWidth = 0;
319 bool bFoldLastColumn = false;
320 for (column = impl_->columns_.begin(); column != impl_->columns_.end(); ++column)
322 // Format the column into columnLines.
323 column->startFormatting();
325 columnLines.reserve(lines.size());
326 for (size_t line = 0; column->hasLinesRemaining(); ++line)
328 int columnWidth = column->width();
329 if (line < lines.size())
331 const int overflow = static_cast<int>(lines[line].length()) - currentWidth;
334 if (overflow > columnWidth && column->bWrap_)
336 columnLines.emplace_back();
339 columnWidth -= overflow;
342 columnLines.push_back(column->textForNextLine(columnWidth));
344 if (column == impl_->columns_.end() - 1 && impl_->foldLastColumnToNextLineIndent_ >= 0
345 && columnLines.size() >= lines.size() + column->lines_.size())
347 bFoldLastColumn = true;
348 currentWidth += column->width();
351 // Add columnLines into lines.
352 if (lines.size() < columnLines.size())
354 lines.resize(columnLines.size());
356 for (size_t line = 0; line < columnLines.size(); ++line)
358 if (column != impl_->columns_.begin() && !columnLines[line].empty())
360 lines[line].append(" ");
361 if (static_cast<int>(lines[line].length()) < currentWidth)
363 lines[line].resize(currentWidth, ' ');
366 lines[line].append(columnLines[line]);
368 currentWidth += column->width() + 1;
371 // Construct the result by concatenating all the lines.
372 std::vector<std::string>::const_iterator line;
373 for (line = lines.begin(); line != lines.end(); ++line)
375 result.append(impl_->firstColumnIndent_, ' ');
376 result.append(*line);
382 Impl::ColumnList::reference lastColumn = impl_->columns_.back();
383 const int totalIndent = impl_->firstColumnIndent_ + impl_->foldLastColumnToNextLineIndent_;
384 lastColumn.startFormatting();
385 currentWidth -= impl_->foldLastColumnToNextLineIndent_;
386 while (lastColumn.hasLinesRemaining())
388 result.append(totalIndent, ' ');
389 result.append(lastColumn.textForNextLine(currentWidth));
394 impl_->bFirstRow_ = false;