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
: ""),
79 width_(width
), bWrap_(bWrap
), firstLine_(0),
80 nextLineIndex_(0), nextLineOffset_(0)
82 GMX_ASSERT(width
>= 0, "Negative width not possible");
83 GMX_ASSERT(title_
.length() <= static_cast<size_t>(width
),
84 "Title too long for column width");
87 //! Returns the title of the column.
88 const std::string
&title() const { return title_
; }
89 //! Returns the width of the column.
90 int width() const { return width_
; }
92 * Returns the first line offset for the current row.
94 * Note that the return value may be outside the printed lines if
97 int firstLine() const { return firstLine_
; }
100 * Resets the formatting state.
102 * After this call, textForNextLine() and hasLinesRemaining() can
103 * be used to format the lines for the column.
105 void startFormatting()
107 nextLineIndex_
= (!lines_
.empty() ? -firstLine_
: 0);
110 //! Whether there are lines remaining for textForNextLine().
111 bool hasLinesRemaining() const
113 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
¤tLine
= lines_
[nextLineIndex_
];
135 const size_t prevOffset
= nextLineOffset_
;
136 const size_t nextOffset
137 = 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_
),
186 "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), foldLastColumnToNextLineIndent_(-1),
209 bFirstRow_(true), bPrintHeader_(false)
213 /********************************************************************
217 TextTableFormatter::TextTableFormatter()
222 TextTableFormatter::~TextTableFormatter()
226 void TextTableFormatter::addColumn(const char *title
, int width
, bool bWrap
)
228 if (title
!= nullptr && title
[0] != '\0')
230 impl_
->bPrintHeader_
= true;
232 impl_
->columns_
.emplace_back(title
, width
, bWrap
);
235 void TextTableFormatter::setFirstColumnIndent(int indent
)
237 GMX_RELEASE_ASSERT(indent
>= 0, "Negative indentation not allowed");
238 impl_
->firstColumnIndent_
= indent
;
241 void TextTableFormatter::setFoldLastColumnToNextLine(int indent
)
243 impl_
->foldLastColumnToNextLineIndent_
= indent
;
246 bool TextTableFormatter::didOutput() const
248 return !impl_
->bFirstRow_
;
251 void TextTableFormatter::clear()
253 Impl::ColumnList::iterator i
;
254 for (i
= impl_
->columns_
.begin(); i
!= impl_
->columns_
.end(); ++i
)
261 void TextTableFormatter::addColumnLine(int index
, const std::string
&text
)
263 Impl::ColumnData
&column
= impl_
->columnData(index
);
264 TextLineWrapper wrapper
;
265 std::vector
<std::string
> lines(wrapper
.wrapToVector(text
));
266 column
.lines_
.insert(column
.lines_
.end(), lines
.begin(), lines
.end());
269 void TextTableFormatter::addColumnHelpTextBlock(
270 int index
, const HelpWriterContext
&context
, const std::string
&text
)
272 Impl::ColumnData
&column
= impl_
->columnData(index
);
273 TextLineWrapperSettings settings
;
274 // TODO: If in the future, there is actually a coupling between the markup
275 // and the wrapping, this must be postponed into formatRow(), where we do
276 // the actual line wrapping.
277 std::vector
<std::string
> lines(
278 context
.substituteMarkupAndWrapToVector(settings
, text
));
279 column
.lines_
.insert(column
.lines_
.end(), lines
.begin(), lines
.end());
282 void TextTableFormatter::setColumnFirstLineOffset(int index
, int firstLine
)
284 GMX_ASSERT(firstLine
>= 0, "Invalid first line");
285 Impl::ColumnData
&column
= impl_
->columnData(index
);
286 column
.firstLine_
= firstLine
;
289 std::string
TextTableFormatter::formatRow()
292 Impl::ColumnList::iterator column
;
293 // Print a header if this is the first line.
294 if (impl_
->bPrintHeader_
&& impl_
->bFirstRow_
)
296 size_t totalWidth
= 0;
297 result
.append(impl_
->firstColumnIndent_
, ' ');
298 for (column
= impl_
->columns_
.begin();
299 column
!= impl_
->columns_
.end();
302 std::string
title(column
->title());
303 if (column
!= impl_
->columns_
.end() - 1)
305 title
.resize(column
->width() + 1, ' ');
306 totalWidth
+= title
.length();
310 totalWidth
+= std::min(column
->width(),
311 static_cast<int>(title
.length() + 13));
313 result
.append(title
);
316 result
.append(impl_
->firstColumnIndent_
, ' ');
317 result
.append(totalWidth
, '-');
321 // Format all the lines, one column at a time.
322 std::vector
<std::string
> lines
;
323 std::vector
<std::string
> columnLines
;
324 int currentWidth
= 0;
325 bool bFoldLastColumn
= false;
326 for (column
= impl_
->columns_
.begin();
327 column
!= impl_
->columns_
.end();
330 // Format the column into columnLines.
331 column
->startFormatting();
333 columnLines
.reserve(lines
.size());
334 for (size_t line
= 0; column
->hasLinesRemaining(); ++line
)
336 int columnWidth
= column
->width();
337 if (line
< lines
.size())
339 const int overflow
= static_cast<int>(lines
[line
].length()) - currentWidth
;
342 if (overflow
> columnWidth
&& column
->bWrap_
)
344 columnLines
.emplace_back();
347 columnWidth
-= overflow
;
350 columnLines
.push_back(column
->textForNextLine(columnWidth
));
352 if (column
== impl_
->columns_
.end() - 1
353 && impl_
->foldLastColumnToNextLineIndent_
>= 0
354 && columnLines
.size() >= lines
.size() + column
->lines_
.size())
356 bFoldLastColumn
= true;
357 currentWidth
+= column
->width();
360 // Add columnLines into lines.
361 if (lines
.size() < columnLines
.size())
363 lines
.resize(columnLines
.size());
365 for (size_t line
= 0; line
< columnLines
.size(); ++line
)
367 if (column
!= impl_
->columns_
.begin() && !columnLines
[line
].empty())
369 lines
[line
].append(" ");
370 if (static_cast<int>(lines
[line
].length()) < currentWidth
)
372 lines
[line
].resize(currentWidth
, ' ');
375 lines
[line
].append(columnLines
[line
]);
377 currentWidth
+= column
->width() + 1;
380 // Construct the result by concatenating all the lines.
381 std::vector
<std::string
>::const_iterator line
;
382 for (line
= lines
.begin(); line
!= lines
.end(); ++line
)
384 result
.append(impl_
->firstColumnIndent_
, ' ');
385 result
.append(*line
);
391 Impl::ColumnList::reference lastColumn
= impl_
->columns_
.back();
392 const int totalIndent
393 = impl_
->firstColumnIndent_
+ impl_
->foldLastColumnToNextLineIndent_
;
394 lastColumn
.startFormatting();
395 currentWidth
-= impl_
->foldLastColumnToNextLineIndent_
;
396 while (lastColumn
.hasLinesRemaining())
398 result
.append(totalIndent
, ' ');
399 result
.append(lastColumn
.textForNextLine(currentWidth
));
404 impl_
->bFirstRow_
= false;