2 * This file is part of the GROMACS molecular simulation package.
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.
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.
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.
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.
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.
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.
37 * Implements functions in helpformat.h.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_onlinehelp
44 #include "helpformat.h"
50 #include "gromacs/onlinehelp/helpwritercontext.h"
51 #include "gromacs/utility/gmxassert.h"
52 #include "gromacs/utility/stringutil.h"
57 /********************************************************************
58 * TextTableFormatter::Impl
62 * Private implementation class for TextTableFormatter.
64 * \ingroup module_onlinehelp
66 class TextTableFormatter::Impl
70 * Manages a single column for TextTableFormatter.
72 * \ingroup module_onlinehelp
76 //! Initializes a text table column with given values.
77 ColumnData(const char* title, int width, bool bWrap) :
78 title_(title != nullptr ? title : ""),
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");
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_; }
95 * Returns the first line offset for the current row.
97 * Note that the return value may be outside the printed lines if
100 int firstLine() const { return firstLine_; }
103 * Resets the formatting state.
105 * After this call, textForNextLine() and hasLinesRemaining() can
106 * be used to format the lines for the column.
108 void startFormatting()
110 nextLineIndex_ = (!lines_.empty() ? -firstLine_ : 0);
113 //! Whether there are lines remaining for textForNextLine().
114 bool hasLinesRemaining() const { return nextLineIndex_ < ssize(lines_); }
116 * Returns the text for the next line.
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.
122 std::string textForNextLine(int columnWidth)
124 if (nextLineIndex_ < 0 || !hasLinesRemaining())
127 return std::string();
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())
144 nextLineOffset_ = nextOffset;
146 return wrapper.formatLine(currentLine, prevOffset, nextOffset);
150 return lines_[nextLineIndex_++];
154 //! Statit data: title of the column.
156 //! Static data: width of the column.
158 //! Static data: whether to automatically wrap input text.
160 //! First line offset for the current row.
162 //! Text lines for the current row.
163 std::vector<std::string> lines_;
164 //! Formatting state: index in `lines_` for the next line.
166 //! Formatting state: offset within line `nextLineIndex_` for the next line.
167 size_t nextLineOffset_;
170 //! Container type for column data.
171 typedef std::vector<ColumnData> ColumnList;
173 //! Initializes data for an empty formatter.
177 * Convenience method for checked access to data for a column.
179 * \param[in] index Zero-based column index.
180 * \returns \c columns_[index]
182 ColumnData& columnData(int index)
184 GMX_ASSERT(index >= 0 && index < ssize(columns_), "Invalid column index");
185 return columns_[index];
187 //! \copydoc columnData()
188 const ColumnData& columnData(int index) const
190 return const_cast<Impl*>(this)->columnData(index);
193 //! Container for column data.
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.
201 //! If true, a header will be printed before the first row.
205 TextTableFormatter::Impl::Impl() :
206 firstColumnIndent_(0),
207 foldLastColumnToNextLineIndent_(-1),
213 /********************************************************************
217 TextTableFormatter::TextTableFormatter() : impl_(new Impl) {}
219 TextTableFormatter::~TextTableFormatter() {}
221 void TextTableFormatter::addColumn(const char* title, int width, bool bWrap)
223 if (title != nullptr && title[0] != '\0')
225 impl_->bPrintHeader_ = true;
227 impl_->columns_.emplace_back(title, width, bWrap);
230 void TextTableFormatter::setFirstColumnIndent(int indent)
232 GMX_RELEASE_ASSERT(indent >= 0, "Negative indentation not allowed");
233 impl_->firstColumnIndent_ = indent;
236 void TextTableFormatter::setFoldLastColumnToNextLine(int indent)
238 impl_->foldLastColumnToNextLineIndent_ = indent;
241 bool TextTableFormatter::didOutput() const
243 return !impl_->bFirstRow_;
246 void TextTableFormatter::clear()
248 Impl::ColumnList::iterator i;
249 for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
256 void TextTableFormatter::addColumnLine(int index, const std::string& text)
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());
264 void TextTableFormatter::addColumnHelpTextBlock(int index,
265 const HelpWriterContext& context,
266 const std::string& text)
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());
277 void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
279 GMX_ASSERT(firstLine >= 0, "Invalid first line");
280 Impl::ColumnData& column = impl_->columnData(index);
281 column.firstLine_ = firstLine;
284 std::string TextTableFormatter::formatRow()
287 Impl::ColumnList::iterator column;
288 // Print a header if this is the first line.
289 if (impl_->bPrintHeader_ && impl_->bFirstRow_)
291 size_t totalWidth = 0;
292 result.append(impl_->firstColumnIndent_, ' ');
293 for (column = impl_->columns_.begin(); column != impl_->columns_.end(); ++column)
295 std::string title(column->title());
296 if (column != impl_->columns_.end() - 1)
298 title.resize(column->width() + 1, ' ');
299 totalWidth += title.length();
303 totalWidth += std::min(column->width(), static_cast<int>(title.length() + 13));
305 result.append(title);
308 result.append(impl_->firstColumnIndent_, ' ');
309 result.append(totalWidth, '-');
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)
320 // Format the column into columnLines.
321 column->startFormatting();
323 columnLines.reserve(lines.size());
324 for (size_t line = 0; column->hasLinesRemaining(); ++line)
326 int columnWidth = column->width();
327 if (line < lines.size())
329 const int overflow = static_cast<int>(lines[line].length()) - currentWidth;
332 if (overflow > columnWidth && column->bWrap_)
334 columnLines.emplace_back();
337 columnWidth -= overflow;
340 columnLines.push_back(column->textForNextLine(columnWidth));
342 if (column == impl_->columns_.end() - 1 && impl_->foldLastColumnToNextLineIndent_ >= 0
343 && columnLines.size() >= lines.size() + column->lines_.size())
345 bFoldLastColumn = true;
346 currentWidth += column->width();
349 // Add columnLines into lines.
350 if (lines.size() < columnLines.size())
352 lines.resize(columnLines.size());
354 for (size_t line = 0; line < columnLines.size(); ++line)
356 if (column != impl_->columns_.begin() && !columnLines[line].empty())
358 lines[line].append(" ");
359 if (static_cast<int>(lines[line].length()) < currentWidth)
361 lines[line].resize(currentWidth, ' ');
364 lines[line].append(columnLines[line]);
366 currentWidth += column->width() + 1;
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)
373 result.append(impl_->firstColumnIndent_, ' ');
374 result.append(*line);
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())
386 result.append(totalIndent, ' ');
387 result.append(lastColumn.textForNextLine(currentWidth));
392 impl_->bFirstRow_ = false;