option(GMX_PREFER_STATIC_LIBS "When finding libraries prefer static system libraries (MT instead of MD)!" ON)
mark_as_advanced(GMX_PREFER_STATIC_LIBS)
SET(SHARED_LIBS_DEFAULT OFF) #is currently not working on Windows
+ # This makes windows.h not declare min/max as macros that would break
+ # C++ code using std::min/std::max.
+ add_definitions(-DNOMINMAX)
IF (GMX_PREFER_STATIC_LIBS)
#Only setting Debug and Release flags. Others configurations current not used.
add_subdirectory(analysisdata)
add_subdirectory(commandline)
add_subdirectory(linearalgebra)
+add_subdirectory(onlinehelp)
add_subdirectory(options)
add_subdirectory(selection)
add_subdirectory(trajectoryanalysis)
#include <string>
-#include "gromacs/legacyheaders/smalloc.h"
-#include "gromacs/legacyheaders/wman.h"
-
+#include "gromacs/onlinehelp/helpformat.h"
#include "gromacs/options/basicoptioninfo.h"
#include "gromacs/options/filenameoptioninfo.h"
#include "gromacs/options/options.h"
namespace
{
-std::string substituteMarkup(const std::string &text)
-{
- char *resultStr = check_tty(text.c_str());
- try
- {
- std::string result(resultStr);
- sfree(resultStr);
- return result;
- }
- catch (...)
- {
- sfree(resultStr);
- throw;
- }
-}
-
/********************************************************************
* DescriptionWriter
*/
file_.writeLine(title);
file_.writeLine();
}
- TextLineWrapper wrapper;
- wrapper.setLineLength(78);
- std::string description(substituteMarkup(section.description()));
- file_.writeLine(wrapper.wrapToString(description));
+ writeHelpTextForConsole(&file_, section.description());
file_.writeLine();
}
OptionsIterator(section).acceptSubSections(this);
}
bool bLongType = (type.length() > 12U);
formatter_.addColumnLine(2, type);
- formatter_.addColumnLine(3, substituteMarkup(option.description()));
+ formatter_.addColumnLine(3, substituteMarkupForConsole(option.description()));
// Compute layout.
if (name.length() > 6U || firstShortValue > 0)
values.append(option.formatValue(i));
}
formatter_.addColumnLine(2, values);
- std::string description(substituteMarkup(option.description()));
+ std::string description(substituteMarkupForConsole(option.description()));
const DoubleOptionInfo *doubleOption = option.toType<DoubleOptionInfo>();
if (doubleOption != NULL && doubleOption->isTime())
{
formatter_.clear();
std::string name(formatString("-%s", option.name().c_str()));
formatter_.addColumnLine(0, name);
- formatter_.addColumnLine(1, substituteMarkup(option.description()));
+ formatter_.addColumnLine(1, substituteMarkupForConsole(option.description()));
file_.writeString(formatter_.formatRow());
// TODO: What to do with selection variables?
--- /dev/null
+file(GLOB ONLINEHELP_SOURCES *.cpp)
+set(LIBGROMACS_SOURCES ${LIBGROMACS_SOURCES} ${ONLINEHELP_SOURCES} PARENT_SCOPE)
+
+if (BUILD_TESTING)
+ add_subdirectory(tests)
+endif (BUILD_TESTING)
--- /dev/null
+/*
+ *
+ * This source code is part of
+ *
+ * G R O M A C S
+ *
+ * GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \internal \file
+ * \brief
+ * Implements functions in helpformat.h.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \ingroup module_onlinehelp
+ */
+#include "helpformat.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "gromacs/legacyheaders/smalloc.h"
+#include "gromacs/legacyheaders/wman.h"
+
+#include "gromacs/utility/file.h"
+#include "gromacs/utility/format.h"
+#include "gromacs/utility/gmxassert.h"
+
+namespace gmx
+{
+
+/*! \cond libapi */
+std::string substituteMarkupForConsole(const std::string &text)
+{
+ char *resultStr = check_tty(text.c_str());
+ try
+ {
+ std::string result(resultStr);
+ sfree(resultStr);
+ return result;
+ }
+ catch (...)
+ {
+ sfree(resultStr);
+ throw;
+ }
+}
+
+void writeHelpTextForConsole(File *file, const std::string &text)
+{
+ TextLineWrapper wrapper;
+ wrapper.setLineLength(78);
+ file->writeLine(wrapper.wrapToString(substituteMarkupForConsole(text)));
+}
+//! \endcond
+
+/********************************************************************
+ * TextTableFormatter::Impl
+ */
+
+/*! \internal \brief
+ * Private implementation class for TextTableFormatter.
+ *
+ * \ingroup module_onlinehelp
+ */
+class TextTableFormatter::Impl
+{
+ public:
+ struct ColumnData
+ {
+ ColumnData(const char *title, int width, bool bWrap)
+ : title_(title != NULL ? title : ""),
+ width_(width), bWrap_(bWrap), firstLine_(0)
+ {
+ GMX_ASSERT(width >= 0, "Negative width not possible");
+ GMX_ASSERT(title_.length() <= static_cast<size_t>(width),
+ "Title too long for column width");
+ }
+
+ //! Returns the title of the column.
+ const std::string &title() const { return title_; }
+ //! Returns the width of the column.
+ int width() const { return width_; }
+ /*! \brief
+ * Returns the first line offset for the current row.
+ *
+ * Note that the return value may be outside the printed lines if
+ * there is no text.
+ */
+ int firstLine() const { return firstLine_; }
+ /*! \brief
+ * Returns the index of the last line with text for the current row.
+ *
+ * If there is no text, returns -1.
+ */
+ int lastLine() const
+ {
+ if (lines_.empty())
+ {
+ return -1;
+ }
+ return firstLine_ + static_cast<int>(lines_.size()) - 1;
+ }
+ /*! \brief
+ * Returns the text for a line.
+ *
+ * \param[in] line Zero-based line index.
+ * \returns Text for line \p line, or empty string if \p line has
+ * no text for this column.
+ */
+ std::string textForLine(int line) const
+ {
+ // The second conditional matches if there are no lines
+ if (line < firstLine() || line > lastLine())
+ {
+ return std::string();
+ }
+ return lines_[line - firstLine()];
+ }
+
+ //! Statit data: title of the column.
+ std::string title_;
+ //! Static data: width of the column.
+ int width_;
+ //! Static data: whether to automatically wrap input text.
+ bool bWrap_;
+ //! First line offset for the current row.
+ int firstLine_;
+ //! Text lines for the current row.
+ std::vector<std::string> lines_;
+ };
+
+ //! Container type for column data.
+ typedef std::vector<ColumnData> ColumnList;
+
+ //! Initializes data for an empty formatter.
+ Impl();
+
+ /*! \brief
+ * Convenience method for checked access to data for a column.
+ *
+ * \param[in] index Zero-based column index.
+ * \returns \c columns_[index]
+ */
+ ColumnData &columnData(int index)
+ {
+ GMX_ASSERT(index >= 0 && index < static_cast<int>(columns_.size()),
+ "Invalid column index");
+ return columns_[index];
+ }
+ //! \copydoc columnData()
+ const ColumnData &columnData(int index) const
+ {
+ return const_cast<Impl *>(this)->columnData(index);
+ }
+
+ //! Container for column data.
+ ColumnList columns_;
+ //! If true, no output has yet been produced.
+ bool bFirstRow_;
+};
+
+TextTableFormatter::Impl::Impl()
+ : bFirstRow_(true)
+{
+}
+
+/********************************************************************
+ * TextTableFormatter
+ */
+
+TextTableFormatter::TextTableFormatter()
+ : impl_(new Impl)
+{
+}
+
+TextTableFormatter::~TextTableFormatter()
+{
+}
+
+void TextTableFormatter::addColumn(const char *title, int width, bool bWrap)
+{
+ impl_->columns_.push_back(Impl::ColumnData(title, width, bWrap));
+}
+
+bool TextTableFormatter::didOutput() const
+{
+ return !impl_->bFirstRow_;
+}
+
+void TextTableFormatter::clear()
+{
+ Impl::ColumnList::iterator i;
+ for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
+ {
+ i->firstLine_ = 0;
+ i->lines_.clear();
+ }
+}
+
+void TextTableFormatter::addColumnLine(int index, const std::string &text)
+{
+ Impl::ColumnData &column = impl_->columnData(index);
+ TextLineWrapper wrapper;
+ if (column.bWrap_)
+ {
+ wrapper.setLineLength(column.width());
+ }
+ std::vector<std::string> lines(wrapper.wrapToVector(text));
+ column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
+}
+
+void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
+{
+ GMX_ASSERT(firstLine >= 0, "Invalid first line");
+ Impl::ColumnData &column = impl_->columnData(index);
+ column.firstLine_ = firstLine;
+}
+
+int TextTableFormatter::lastColumnLine(int index) const
+{
+ return impl_->columnData(index).lastLine();
+}
+
+std::string TextTableFormatter::formatRow()
+{
+ std::string result;
+ Impl::ColumnList::const_iterator column;
+ // Print a header if this is the first line.
+ if (impl_->bFirstRow_)
+ {
+ size_t totalWidth = 0;
+ for (column = impl_->columns_.begin();
+ column != impl_->columns_.end();
+ ++column)
+ {
+ std::string title(column->title());
+ if (column != impl_->columns_.end() - 1)
+ {
+ title.resize(column->width() + 1, ' ');
+ totalWidth += title.length();
+ }
+ else
+ {
+ totalWidth += std::min(column->width(),
+ static_cast<int>(title.length() + 13));
+ }
+ result.append(title);
+ }
+ result.append("\n");
+ result.append(totalWidth, '-');
+ result.append("\n");
+ }
+
+ // Compute the last applicable line.
+ int lastLine = -1;
+ for (column = impl_->columns_.begin();
+ column != impl_->columns_.end();
+ ++column)
+ {
+ lastLine = std::max(lastLine, column->lastLine());
+ }
+
+ // Format the actual row data.
+ for (int line = 0; line <= lastLine; ++line)
+ {
+ std::string lineResult;
+ size_t currentWidth = 0;
+ for (column = impl_->columns_.begin();
+ column != impl_->columns_.end();
+ ++column)
+ {
+ std::string value(column->textForLine(line));
+ if (column != impl_->columns_.begin())
+ {
+ ++currentWidth;
+ if (!value.empty())
+ {
+ lineResult.append(" ");
+ if (lineResult.length() < currentWidth)
+ {
+ lineResult.resize(currentWidth, ' ');
+ }
+ }
+ }
+ // TODO: Rewrap the text if wrapping is on and the previous columns
+ // overflow.
+ lineResult.append(value);
+ currentWidth += column->width();
+ }
+ result.append(lineResult);
+ result.append("\n");
+ }
+
+ impl_->bFirstRow_ = false;
+ clear();
+ return result;
+}
+
+} // namespace gmx
--- /dev/null
+/*
+ *
+ * This source code is part of
+ *
+ * G R O M A C S
+ *
+ * GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \libinternal \file
+ * \brief
+ * Declares common string formatting routines for online help.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
+#ifndef GMX_ONLINEHELP_HELPFORMAT_H
+#define GMX_ONLINEHELP_HELPFORMAT_H
+
+#include <string>
+
+#include "../utility/common.h"
+
+namespace gmx
+{
+
+class File;
+
+/*! \cond libapi */
+/*! \libinternal \brief
+ * Substitute markup used in help text for console output.
+ *
+ * \param[in] text Text to substitute.
+ * \returns \p text with markup substituted.
+ * \throws std::bad_alloc if out of memory.
+ *
+ * \inlibraryapi
+ */
+std::string substituteMarkupForConsole(const std::string &text);
+/*! \libinternal \brief
+ * Format a help text block for console output.
+ *
+ * \param file File to write the formatted text to.
+ * \param[in] text Text to format.
+ * \throws std::bad_alloc if out of memory.
+ * \throws FileIOError on any I/O error.
+ *
+ * Calls substituteMarkupForConsole(), and also wraps the lines to 78
+ * characters.
+ *
+ * \inlibraryapi
+ */
+void writeHelpTextForConsole(File *file, const std::string &text);
+//! \endcond
+
+/*! \libinternal \brief
+ * Formats rows of a table for text output.
+ *
+ * This utility class formats tabular data, mainly for console output.
+ * Each row in the table can take multiple lines, and automatic text wrapping
+ * is supported. If text overflows the allocated width, the remaining columns
+ * on that line become shifted. To avoid this, it is possible to start the
+ * output for different columns from different lines (it is the caller's
+ * responsibility to check that overflows are avoided or are acceptable).
+ *
+ * Column definitions are first set with addColumn().
+ * To format a fow, first call clear(). Then call addColumnLine() to add text
+ * to each column (can be called multiple times on a single column to add
+ * multiple lines), and possibly setColumnFirstLineOffset() to adjust the line
+ * from which the column output should start. Finally, call formatRow() to
+ * obtain the formatted row.
+ *
+ * A header (column titles and a horizontal line) is printed before the first
+ * line.
+ *
+ * Typical usage:
+ * \code
+gmx::TextTableFormatter formatter;
+formatter.addColumn("Name", 10, false);
+formatter.addColumn("Type", 10, false);
+formatter.addColumn("Description", 50, true);
+
+formatter.clear();
+formatter.addColumnLine(0, "name");
+formatter.addColumnLine(1, "type");
+formatter.addColumnLine(2, "Description for name");
+printf("%s", formatter.formatRow().c_str());
+
+formatter.clear();
+formatter.addColumnLine(0, "averylongname");
+formatter.addColumnLine(1, "type");
+formatter.setColumnFirstLineOffset(1, 1);
+formatter.addColumnLine(2, "Description for name");
+printf("%s", formatter.formatRow().c_str());
+
+// format other rows by repeating the above code
+ * \endcode
+ *
+ * Methods in this class may throw std::bad_alloc if out of memory.
+ * Other exceptions are not thrown.
+ *
+ * \inlibraryapi
+ * \ingroup module_onlinehelp
+ */
+class TextTableFormatter
+{
+ public:
+ //! Constructs an empty formatter.
+ TextTableFormatter();
+ ~TextTableFormatter();
+
+ /*! \brief
+ * Adds a column to the table.
+ *
+ * \param[in] title Title string for the column (used for header).
+ * \param[in] width Width of the column (must be > 0).
+ * \param[in] bWrap Whether text that exceeds \p width is
+ * automatically wrapped.
+ *
+ * The length of \p title must not exceed \p width.
+ */
+ void addColumn(const char *title, int width, bool bWrap);
+
+ /*! \brief
+ * Whether formatRow() has been successfully called.
+ *
+ * This method can be used to determine after-the-fact whether anything
+ * was written in the table.
+ *
+ * Does not throw.
+ */
+ bool didOutput() const;
+
+ /*! \brief
+ * Removes all text from all columns and resets the line offsets.
+ *
+ * Removes all text added using addColumnLine() and resets line offsets
+ * set with setColumnFirstLineOffset() to zero.
+ * Should be called before starting to add data for a row.
+ *
+ * Does not throw.
+ */
+ void clear();
+ /*! \brief
+ * Adds text to be printed in a column.
+ *
+ * \param[in] index Zero-based column index.
+ * \param[in] text Text to add.
+ *
+ * Can be called multiple times. Additional calls append \p text as
+ * additional lines. Any calls with \p text empty have no effect.
+ * To add an empty line, use "\n" as \p text.
+ *
+ * If \p text contains newlines, the text is automatically splitted to
+ * multiple lines. The same happens if automatic wrapping is on for
+ * the column and the text contains lines that are longer than what
+ * fits the column.
+ */
+ void addColumnLine(int index, const std::string &text);
+ /*! \brief
+ * Sets the first line to which text is printed for a column.
+ *
+ * \param[in] index Zero-based column index.
+ * \param[in] firstLine Zero-based line index from which to start the
+ * output.
+ *
+ * Can be called if there is no text for column \p index.
+ * Does not affect the output in this case.
+ *
+ * Does not throw.
+ */
+ void setColumnFirstLineOffset(int index, int firstLine);
+ /*! \brief
+ * Formats the lines for the current row.
+ *
+ * \returns Current row formatted as a single string
+ * (contains newlines).
+ *
+ * Formats the data as set after the previous clear()/formatRow() using
+ * addColumnLine() and setColumnFirstLineOffset().
+ *
+ * If this is the first line to be formatted, a header is also added to
+ * the beginning of the returned string.
+ *
+ * The return value always terminates with a newline.
+ *
+ * Calls clear() on successful return.
+ */
+ std::string formatRow();
+
+ /*! \brief
+ * Returns the last line on which column \p index has text.
+ *
+ * \param[in] index Zero-based column index.
+ * \returns Last line index (zero-based) on which \p index has text.
+ *
+ * The return value is the sum of the number of lines added with
+ * addColumnLine() (taking into account possible wrapping) and the line
+ * offset set with setColumnFirstLineOffset().
+ *
+ * Does not throw.
+ */
+ int lastColumnLine(int index) const;
+
+ private:
+ class Impl;
+
+ PrivateImplPointer<Impl> impl_;
+};
+
+} // namespace gmx
+
+#endif
--- /dev/null
+gmx_add_unit_test(OnlineHelpUnitTests onlinehelp-test
+ helpformat.cpp)
--- /dev/null
+/*
+ *
+ * This source code is part of
+ *
+ * G R O M A C S
+ *
+ * GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, please consider that
+ * scientific software is very special. Version control is crucial -
+ * bugs must be traceable. We will be happy to consider code for
+ * inclusion in the official distribution, but derived work must not
+ * be called official GROMACS. Details are found in the README & COPYING
+ * files - if they are missing, get the official version at www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \internal \file
+ * \brief
+ * Tests for help string formatting functions and classes.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \ingroup module_onlinehelp
+ */
+#include <gtest/gtest.h>
+
+#include "gromacs/onlinehelp/helpformat.h"
+
+#include "testutils/stringtest.h"
+
+namespace
+{
+
+const char g_wrapText[] = "A quick brown fox jumps over the lazy dog";
+const char g_wrapText2[] = "A quick brown fox jumps\nover the lazy dog";
+
+/********************************************************************
+ * Tests for TextTableFormatter
+ */
+
+class TextTableFormatterTest : public gmx::test::StringTestBase
+{
+ public:
+ TextTableFormatterTest();
+
+ gmx::TextTableFormatter formatter_;
+};
+
+TextTableFormatterTest::TextTableFormatterTest()
+{
+ formatter_.addColumn("Col1", 4, false);
+ formatter_.addColumn("Col2", 4, false);
+ formatter_.addColumn("Col3Wrap", 14, true);
+ formatter_.addColumn("Col4Wrap", 14, true);
+}
+
+TEST_F(TextTableFormatterTest, HandlesBasicCase)
+{
+ formatter_.clear();
+ formatter_.addColumnLine(0, "foo");
+ formatter_.addColumnLine(1, "bar");
+ formatter_.addColumnLine(2, g_wrapText);
+ formatter_.addColumnLine(3, g_wrapText2);
+ checkText(formatter_.formatRow(), "FormattedTable");
+}
+
+TEST_F(TextTableFormatterTest, HandlesOverflowingLines)
+{
+ formatter_.clear();
+ formatter_.addColumnLine(0, "foobar");
+ formatter_.addColumnLine(1, "barfoo");
+ formatter_.addColumnLine(2, g_wrapText);
+ formatter_.addColumnLine(3, g_wrapText2);
+ checkText(formatter_.formatRow(), "FormattedTable");
+ formatter_.clear();
+ formatter_.addColumnLine(0, "foobar");
+ formatter_.addColumnLine(1, "barfoo");
+ formatter_.setColumnFirstLineOffset(1, 1);
+ formatter_.addColumnLine(2, g_wrapText);
+ formatter_.addColumnLine(3, g_wrapText2);
+ checkText(formatter_.formatRow(), "FormattedRow2");
+ formatter_.clear();
+ formatter_.addColumnLine(0, "foobar");
+ formatter_.addColumnLine(1, "barfoo");
+ formatter_.setColumnFirstLineOffset(1, 1);
+ formatter_.addColumnLine(2, g_wrapText);
+ formatter_.setColumnFirstLineOffset(2, 2);
+ formatter_.addColumnLine(3, g_wrapText2);
+ checkText(formatter_.formatRow(), "FormattedRow3");
+}
+
+TEST_F(TextTableFormatterTest, HandlesEmptyColumns)
+{
+ formatter_.clear();
+ formatter_.addColumnLine(0, "foo");
+ formatter_.addColumnLine(1, "bar");
+ formatter_.addColumnLine(3, "Text");
+ checkText(formatter_.formatRow(), "FormattedTable");
+ formatter_.clear();
+ formatter_.addColumnLine(0, "foo");
+ formatter_.addColumnLine(1, "bar");
+ formatter_.setColumnFirstLineOffset(2, 1);
+ formatter_.addColumnLine(3, "Text");
+ checkText(formatter_.formatRow(), "FormattedRow2");
+ formatter_.clear();
+ formatter_.addColumnLine(0, "foo");
+ formatter_.addColumnLine(1, "bar");
+ formatter_.addColumnLine(2, "");
+ formatter_.setColumnFirstLineOffset(2, 1);
+ formatter_.addColumnLine(3, "Text");
+ checkText(formatter_.formatRow(), "FormattedRow3");
+}
+
+} // namespace
return result;
}
-/********************************************************************
- * TextTableFormatter::Impl
- */
-
-/*! \internal \brief
- * Private implementation class for TextTableFormatter.
- *
- * \ingroup module_utility
- */
-class TextTableFormatter::Impl
-{
- public:
- struct ColumnData
- {
- ColumnData(const char *title, int width, bool bWrap)
- : title_(title != NULL ? title : ""),
- width_(width), bWrap_(bWrap), firstLine_(0)
- {
- GMX_ASSERT(width >= 0, "Negative width not possible");
- GMX_ASSERT(title_.length() <= static_cast<size_t>(width),
- "Title too long for column width");
- }
-
- //! Returns the title of the column.
- const std::string &title() const { return title_; }
- //! Returns the width of the column.
- int width() const { return width_; }
- /*! \brief
- * Returns the first line offset for the current row.
- *
- * Note that the return value may be outside the printed lines if
- * there is no text.
- */
- int firstLine() const { return firstLine_; }
- /*! \brief
- * Returns the index of the last line with text for the current row.
- *
- * If there is no text, returns -1.
- */
- int lastLine() const
- {
- if (lines_.empty())
- {
- return -1;
- }
- return firstLine_ + static_cast<int>(lines_.size()) - 1;
- }
- /*! \brief
- * Returns the text for a line.
- *
- * \param[in] line Zero-based line index.
- * \returns Text for line \p line, or empty string if \p line has
- * no text for this column.
- */
- std::string textForLine(int line) const
- {
- // The second conditional matches if there are no lines
- if (line < firstLine() || line > lastLine())
- {
- return std::string();
- }
- return lines_[line - firstLine()];
- }
-
- //! Statit data: title of the column.
- std::string title_;
- //! Static data: width of the column.
- int width_;
- //! Static data: whether to automatically wrap input text.
- bool bWrap_;
- //! First line offset for the current row.
- int firstLine_;
- //! Text lines for the current row.
- std::vector<std::string> lines_;
- };
-
- //! Container type for column data.
- typedef std::vector<ColumnData> ColumnList;
-
- //! Initializes data for an empty formatter.
- Impl();
-
- /*! \brief
- * Convenience method for checked access to data for a column.
- *
- * \param[in] index Zero-based column index.
- * \returns \c columns_[index]
- */
- ColumnData &columnData(int index)
- {
- GMX_ASSERT(index >= 0 && index < static_cast<int>(columns_.size()),
- "Invalid column index");
- return columns_[index];
- }
- //! \copydoc columnData()
- const ColumnData &columnData(int index) const
- {
- return const_cast<Impl *>(this)->columnData(index);
- }
-
- //! Container for column data.
- ColumnList columns_;
- //! If true, no output has yet been produced.
- bool bFirstRow_;
-};
-
-TextTableFormatter::Impl::Impl()
- : bFirstRow_(true)
-{
-}
-
-/********************************************************************
- * TextTableFormatter
- */
-
-TextTableFormatter::TextTableFormatter()
- : impl_(new Impl)
-{
-}
-
-TextTableFormatter::~TextTableFormatter()
-{
-}
-
-void TextTableFormatter::addColumn(const char *title, int width, bool bWrap)
-{
- impl_->columns_.push_back(Impl::ColumnData(title, width, bWrap));
-}
-
-bool TextTableFormatter::didOutput() const
-{
- return !impl_->bFirstRow_;
-}
-
-void TextTableFormatter::clear()
-{
- Impl::ColumnList::iterator i;
- for (i = impl_->columns_.begin(); i != impl_->columns_.end(); ++i)
- {
- i->firstLine_ = 0;
- i->lines_.clear();
- }
-}
-
-void TextTableFormatter::addColumnLine(int index, const std::string &text)
-{
- Impl::ColumnData &column = impl_->columnData(index);
- TextLineWrapper wrapper;
- if (column.bWrap_)
- {
- wrapper.setLineLength(column.width());
- }
- std::vector<std::string> lines(wrapper.wrapToVector(text));
- column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
-}
-
-void TextTableFormatter::setColumnFirstLineOffset(int index, int firstLine)
-{
- GMX_ASSERT(firstLine >= 0, "Invalid first line");
- Impl::ColumnData &column = impl_->columnData(index);
- column.firstLine_ = firstLine;
-}
-
-int TextTableFormatter::lastColumnLine(int index) const
-{
- return impl_->columnData(index).lastLine();
-}
-
-std::string TextTableFormatter::formatRow()
-{
- std::string result;
- Impl::ColumnList::const_iterator column;
- // Print a header if this is the first line.
- if (impl_->bFirstRow_)
- {
- size_t totalWidth = 0;
- for (column = impl_->columns_.begin();
- column != impl_->columns_.end();
- ++column)
- {
- std::string title(column->title());
- if (column != impl_->columns_.end() - 1)
- {
- title.resize(column->width() + 1, ' ');
- totalWidth += title.length();
- }
- else
- {
- totalWidth += std::min(column->width(),
- static_cast<int>(title.length() + 13));
- }
- result.append(title);
- }
- result.append("\n");
- result.append(totalWidth, '-');
- result.append("\n");
- }
-
- // Compute the last applicable line.
- int lastLine = -1;
- for (column = impl_->columns_.begin();
- column != impl_->columns_.end();
- ++column)
- {
- lastLine = std::max(lastLine, column->lastLine());
- }
-
- // Format the actual row data.
- for (int line = 0; line <= lastLine; ++line)
- {
- std::string lineResult;
- size_t currentWidth = 0;
- for (column = impl_->columns_.begin();
- column != impl_->columns_.end();
- ++column)
- {
- std::string value(column->textForLine(line));
- if (column != impl_->columns_.begin())
- {
- ++currentWidth;
- if (!value.empty())
- {
- lineResult.append(" ");
- if (lineResult.length() < currentWidth)
- {
- lineResult.resize(currentWidth, ' ');
- }
- }
- }
- // TODO: Rewrap the text if wrapping is on and the previous columns
- // overflow.
- lineResult.append(value);
- currentWidth += column->width();
- }
- result.append(lineResult);
- result.append("\n");
- }
-
- impl_->bFirstRow_ = false;
- clear();
- return result;
-}
-
} // namespace gmx
PrivateImplPointer<Impl> impl_;
};
-/*! \brief
- * Formats rows of a table for text output.
- *
- * This utility class formats tabular data, mainly for console output.
- * Each row in the table can take multiple lines, and automatic text wrapping
- * is supported. If text overflows the allocated width, the remaining columns
- * on that line become shifted. To avoid this, it is possible to start the
- * output for different columns from different lines (it is the caller's
- * responsibility to check that overflows are avoided or are acceptable).
- *
- * Column definitions are first set with addColumn().
- * To format a fow, first call clear(). Then call addColumnLine() to add text
- * to each column (can be called multiple times on a single column to add
- * multiple lines), and possibly setColumnFirstLineOffset() to adjust the line
- * from which the column output should start. Finally, call formatRow() to
- * obtain the formatted row.
- *
- * A header (column titles and a horizontal line) is printed before the first
- * line.
- *
- * Typical usage:
- * \code
-gmx::TextTableFormatter formatter;
-formatter.addColumn("Name", 10, false);
-formatter.addColumn("Type", 10, false);
-formatter.addColumn("Description", 50, true);
-
-formatter.clear();
-formatter.addColumnLine(0, "name");
-formatter.addColumnLine(1, "type");
-formatter.addColumnLine(2, "Description for name");
-printf("%s", formatter.formatRow().c_str());
-
-formatter.clear();
-formatter.addColumnLine(0, "averylongname");
-formatter.addColumnLine(1, "type");
-formatter.setColumnFirstLineOffset(1, 1);
-formatter.addColumnLine(2, "Description for name");
-printf("%s", formatter.formatRow().c_str());
-
-// format other rows by repeating the above code
- * \endcode
- *
- * Methods in this class may throw std::bad_alloc if out of memory.
- * Other exceptions are not thrown.
- *
- * \inpublicapi
- * \ingroup module_utility
- */
-class TextTableFormatter
-{
- public:
- //! Constructs an empty formatter.
- TextTableFormatter();
- ~TextTableFormatter();
-
- /*! \brief
- * Adds a column to the table.
- *
- * \param[in] title Title string for the column (used for header).
- * \param[in] width Width of the column (must be > 0).
- * \param[in] bWrap Whether text that exceeds \p width is
- * automatically wrapped.
- *
- * The length of \p title must not exceed \p width.
- */
- void addColumn(const char *title, int width, bool bWrap);
-
- /*! \brief
- * Whether formatRow() has been successfully called.
- *
- * This method can be used to determine after-the-fact whether anything
- * was written in the table.
- *
- * Does not throw.
- */
- bool didOutput() const;
-
- /*! \brief
- * Removes all text from all columns and resets the line offsets.
- *
- * Removes all text added using addColumnLine() and resets line offsets
- * set with setColumnFirstLineOffset() to zero.
- * Should be called before starting to add data for a row.
- *
- * Does not throw.
- */
- void clear();
- /*! \brief
- * Adds text to be printed in a column.
- *
- * \param[in] index Zero-based column index.
- * \param[in] text Text to add.
- *
- * Can be called multiple times. Additional calls append \p text as
- * additional lines. Any calls with \p text empty have no effect.
- * To add an empty line, use "\n" as \p text.
- *
- * If \p text contains newlines, the text is automatically splitted to
- * multiple lines. The same happens if automatic wrapping is on for
- * the column and the text contains lines that are longer than what
- * fits the column.
- */
- void addColumnLine(int index, const std::string &text);
- /*! \brief
- * Sets the first line to which text is printed for a column.
- *
- * \param[in] index Zero-based column index.
- * \param[in] firstLine Zero-based line index from which to start the
- * output.
- *
- * Can be called if there is no text for column \p index.
- * Does not affect the output in this case.
- *
- * Does not throw.
- */
- void setColumnFirstLineOffset(int index, int firstLine);
- /*! \brief
- * Formats the lines for the current row.
- *
- * \returns Current row formatted as a single string
- * (contains newlines).
- *
- * Formats the data as set after the previous clear()/formatRow() using
- * addColumnLine() and setColumnFirstLineOffset().
- *
- * If this is the first line to be formatted, a header is also added to
- * the beginning of the returned string.
- *
- * The return value always terminates with a newline.
- *
- * Calls clear() on successful return.
- */
- std::string formatRow();
-
- /*! \brief
- * Returns the last line on which column \p index has text.
- *
- * \param[in] index Zero-based column index.
- * \returns Last line index (zero-based) on which \p index has text.
- *
- * The return value is the sum of the number of lines added with
- * addColumnLine() (taking into account possible wrapping) and the line
- * offset set with setColumnFirstLineOffset().
- *
- * Does not throw.
- */
- int lastColumnLine(int index) const;
-
- private:
- class Impl;
-
- PrivateImplPointer<Impl> impl_;
-};
-
} // namespace gmx
#endif
checkText(wrapper.wrapToString(g_wrapTextWhitespace), "WrappedAt14");
}
-/********************************************************************
- * Tests for TextTableFormatter
- */
-
-class TextTableFormatterTest : public gmx::test::StringTestBase
-{
- public:
- TextTableFormatterTest();
-
- gmx::TextTableFormatter formatter_;
-};
-
-TextTableFormatterTest::TextTableFormatterTest()
-{
- formatter_.addColumn("Col1", 4, false);
- formatter_.addColumn("Col2", 4, false);
- formatter_.addColumn("Col3Wrap", 14, true);
- formatter_.addColumn("Col4Wrap", 14, true);
-}
-
-TEST_F(TextTableFormatterTest, HandlesBasicCase)
-{
- formatter_.clear();
- formatter_.addColumnLine(0, "foo");
- formatter_.addColumnLine(1, "bar");
- formatter_.addColumnLine(2, g_wrapText);
- formatter_.addColumnLine(3, g_wrapText2);
- checkText(formatter_.formatRow(), "FormattedTable");
-}
-
-TEST_F(TextTableFormatterTest, HandlesOverflowingLines)
-{
- formatter_.clear();
- formatter_.addColumnLine(0, "foobar");
- formatter_.addColumnLine(1, "barfoo");
- formatter_.addColumnLine(2, g_wrapText);
- formatter_.addColumnLine(3, g_wrapText2);
- checkText(formatter_.formatRow(), "FormattedTable");
- formatter_.clear();
- formatter_.addColumnLine(0, "foobar");
- formatter_.addColumnLine(1, "barfoo");
- formatter_.setColumnFirstLineOffset(1, 1);
- formatter_.addColumnLine(2, g_wrapText);
- formatter_.addColumnLine(3, g_wrapText2);
- checkText(formatter_.formatRow(), "FormattedRow2");
- formatter_.clear();
- formatter_.addColumnLine(0, "foobar");
- formatter_.addColumnLine(1, "barfoo");
- formatter_.setColumnFirstLineOffset(1, 1);
- formatter_.addColumnLine(2, g_wrapText);
- formatter_.setColumnFirstLineOffset(2, 2);
- formatter_.addColumnLine(3, g_wrapText2);
- checkText(formatter_.formatRow(), "FormattedRow3");
-}
-
-TEST_F(TextTableFormatterTest, HandlesEmptyColumns)
-{
- formatter_.clear();
- formatter_.addColumnLine(0, "foo");
- formatter_.addColumnLine(1, "bar");
- formatter_.addColumnLine(3, "Text");
- checkText(formatter_.formatRow(), "FormattedTable");
- formatter_.clear();
- formatter_.addColumnLine(0, "foo");
- formatter_.addColumnLine(1, "bar");
- formatter_.setColumnFirstLineOffset(2, 1);
- formatter_.addColumnLine(3, "Text");
- checkText(formatter_.formatRow(), "FormattedRow2");
- formatter_.clear();
- formatter_.addColumnLine(0, "foo");
- formatter_.addColumnLine(1, "bar");
- formatter_.addColumnLine(2, "");
- formatter_.setColumnFirstLineOffset(2, 1);
- formatter_.addColumnLine(3, "Text");
- checkText(formatter_.formatRow(), "FormattedRow3");
-}
-
} // namespace