+++ /dev/null
-template
-Makefile.*-*
+++ /dev/null
-analysisdata-tests
{
/********************************************************************
- * DescriptionWriter
+ * OptionsFormatterInterface
*/
/*! \internal \brief
- * Helper object for writing section descriptions to help.
+ * Interface for output format specific formatting of options.
+ *
+ * \see OptionsFilter
+ *
+ * \ingroup module_commandline
+ */
+class OptionsFormatterInterface
+{
+ public:
+ virtual ~OptionsFormatterInterface() {}
+
+ //! Formats the description text block for a section.
+ virtual void formatDescription(const HelpWriterContext &context,
+ const Options §ion) = 0;
+ //! Formats a single file option.
+ virtual void formatFileOption(const HelpWriterContext &context,
+ const FileNameOptionInfo &option) = 0;
+ //! Formats a single non-file, non-selection option.
+ virtual void formatOption(const HelpWriterContext &context,
+ const OptionInfo &option) = 0;
+ //! Formats a single selection option.
+ virtual void formatSelectionOption(const HelpWriterContext &context,
+ const OptionInfo &option) = 0;
+};
+
+/********************************************************************
+ * OptionsFilter
+ */
+
+/*! \internal \brief
+ * Output format independent processing of options.
+ *
+ * Together with code in CommandLineHelpWriter::writeHelp(), this class
+ * implements the common logic for writing out the help.
+ * An object implementing the OptionsFormatterInterface must be provided to the
+ * constructor, and does the actual formatting that is specific to the output
+ * format.
*
* \ingroup module_commandline
*/
-class DescriptionWriter : public OptionsVisitor
+class OptionsFilter : public OptionsVisitor
{
public:
- //! Creates a helper object for writing section descriptions.
- explicit DescriptionWriter(const HelpWriterContext &context)
- : context_(context)
+ //! Specifies the type of output that formatSelected() produces.
+ enum FilterType
+ {
+ eSelectDescriptions,
+ eSelectFileOptions,
+ eSelectSelectionOptions,
+ eSelectOtherOptions
+ };
+
+ /*! \brief
+ * Creates a new filtering object.
+ *
+ * \param[in] context Help context to use to write the help.
+ * \param[in] formatter Output format specific formatter.
+ *
+ * Does not throw.
+ */
+ OptionsFilter(const HelpWriterContext &context,
+ OptionsFormatterInterface *formatter)
+ : context_(context), formatter_(*formatter),
+ filterType_(eSelectOtherOptions), bShowHidden_(false),
+ bDidOutput_(false)
+ {
+ }
+
+ //! Sets whether hidden options will be shown.
+ void setShowHidden(bool bShowHidden)
{
+ bShowHidden_ = bShowHidden;
}
+ //! Formats selected options using the formatter.
+ void formatSelected(FilterType type, const Options &options);
+
virtual void visitSubSection(const Options §ion);
- virtual void visitOption(const OptionInfo & /*option*/) { }
+ virtual void visitOption(const OptionInfo &option);
private:
- const HelpWriterContext &context_;
+ const HelpWriterContext &context_;
+ OptionsFormatterInterface &formatter_;
+ FilterType filterType_;
+ bool bShowHidden_;
+ bool bDidOutput_;
};
-void DescriptionWriter::visitSubSection(const Options §ion)
+void OptionsFilter::formatSelected(FilterType type, const Options &options)
{
- if (!section.description().empty())
+ filterType_ = type;
+ bDidOutput_ = false;
+ visitSubSection(options);
+ if (bDidOutput_)
{
- File &file = context_.outputFile();
- const std::string &title = section.title();
- if (!title.empty())
+ context_.outputFile().writeLine();
+ }
+}
+
+void OptionsFilter::visitSubSection(const Options §ion)
+{
+ if (filterType_ == eSelectDescriptions)
+ {
+ if (bDidOutput_)
{
- file.writeLine(title);
- file.writeLine();
+ context_.outputFile().writeLine();
}
- context_.writeTextBlock(section.description());
- file.writeLine();
+ formatter_.formatDescription(context_, section);
+ bDidOutput_ = true;
}
- OptionsIterator(section).acceptSubSections(this);
+
+ OptionsIterator iterator(section);
+ iterator.acceptSubSections(this);
+ iterator.acceptOptions(this);
}
+void OptionsFilter::visitOption(const OptionInfo &option)
+{
+ if (!bShowHidden_ && option.isHidden())
+ {
+ return;
+ }
+ if (option.isType<FileNameOptionInfo>())
+ {
+ if (filterType_ == eSelectFileOptions)
+ {
+ formatter_.formatFileOption(context_,
+ *option.toType<FileNameOptionInfo>());
+ bDidOutput_ = true;
+ }
+ return;
+ }
+ if (option.isType<SelectionFileOptionInfo>()
+ || option.isType<SelectionOptionInfo>())
+ {
+ if (filterType_ == eSelectSelectionOptions)
+ {
+ formatter_.formatSelectionOption(context_, option);
+ bDidOutput_ = true;
+ }
+ return;
+ }
+ if (filterType_ == eSelectOtherOptions)
+ {
+ formatter_.formatOption(context_, option);
+ bDidOutput_ = true;
+ return;
+ }
+}
/********************************************************************
- * FileParameterWriter
+ * CommonFormatterData
+ */
+
+class CommonFormatterData
+{
+ public:
+ explicit CommonFormatterData(const char *timeUnit)
+ : timeUnit(timeUnit)
+ {
+ }
+
+ const char *timeUnit;
+};
+
+/********************************************************************
+ * OptionsConsoleFormatter
*/
/*! \internal \brief
- * Helper object for writing help for file parameters.
+ * Formatter implementation for console help.
*
* \ingroup module_commandline
*/
-class FileParameterWriter : public OptionsTypeVisitor<FileNameOptionInfo>
+class OptionsConsoleFormatter : public OptionsFormatterInterface
{
public:
- //! Creates a helper object for writing file parameters.
- explicit FileParameterWriter(const HelpWriterContext &context);
-
- //! Returns true if anything was written out.
- bool didOutput() const { return formatter_.didOutput(); }
-
- virtual void visitSubSection(const Options §ion);
- virtual void visitOptionType(const FileNameOptionInfo &option);
+ //! Creates a helper object for formatting options help for console.
+ explicit OptionsConsoleFormatter(const CommonFormatterData &common);
+
+ virtual void formatDescription(const HelpWriterContext &context,
+ const Options §ion);
+ virtual void formatFileOption(const HelpWriterContext &context,
+ const FileNameOptionInfo &option);
+ virtual void formatOption(const HelpWriterContext &context,
+ const OptionInfo &option);
+ virtual void formatSelectionOption(const HelpWriterContext &context,
+ const OptionInfo &option);
private:
- const HelpWriterContext &context_;
- TextTableFormatter formatter_;
+ const CommonFormatterData &common_;
+ TextTableFormatter fileOptionFormatter_;
+ TextTableFormatter genericOptionFormatter_;
+ TextTableFormatter selectionOptionFormatter_;
};
-FileParameterWriter::FileParameterWriter(const HelpWriterContext &context)
- : context_(context)
+OptionsConsoleFormatter::OptionsConsoleFormatter(const CommonFormatterData &common)
+ : common_(common)
{
- formatter_.addColumn("Option", 6, false);
- formatter_.addColumn("Filename", 12, false);
- formatter_.addColumn("Type", 12, false);
- formatter_.addColumn("Description", 45, true);
+ fileOptionFormatter_.addColumn("Option", 6, false);
+ fileOptionFormatter_.addColumn("Filename", 12, false);
+ fileOptionFormatter_.addColumn("Type", 12, false);
+ fileOptionFormatter_.addColumn("Description", 45, true);
+
+ genericOptionFormatter_.addColumn("Option", 12, false);
+ genericOptionFormatter_.addColumn("Type", 6, false);
+ genericOptionFormatter_.addColumn("Value", 6, false);
+ genericOptionFormatter_.addColumn("Description", 51, true);
+
+ selectionOptionFormatter_.addColumn("Selection", 10, false);
+ selectionOptionFormatter_.addColumn("Description", 67, true);
}
-void FileParameterWriter::visitSubSection(const Options §ion)
+void OptionsConsoleFormatter::formatDescription(
+ const HelpWriterContext &context, const Options §ion)
{
- OptionsIterator iterator(section);
- iterator.acceptSubSections(this);
- iterator.acceptOptions(this);
+ if (!section.description().empty())
+ {
+ File &file = context.outputFile();
+ const std::string &title = section.title();
+ if (!title.empty())
+ {
+ file.writeLine(title);
+ file.writeLine();
+ }
+ context.writeTextBlock(section.description());
+ }
}
-void FileParameterWriter::visitOptionType(const FileNameOptionInfo &option)
+void OptionsConsoleFormatter::formatFileOption(
+ const HelpWriterContext &context, const FileNameOptionInfo &option)
{
int firstShortValue = 0; // The first value after which the type fits.
int firstLongValue = -1; // First value that overlaps description column.
int lastLongValue = -1; // Last value like the above.
// Get the values to write and check where text overflows the columns.
- formatter_.clear();
+ fileOptionFormatter_.clear();
std::string name(formatString("-%s", option.name().c_str()));
- formatter_.addColumnLine(0, name);
+ fileOptionFormatter_.addColumnLine(0, name);
for (int i = 0; i < option.valueCount() || i == 0; ++i)
{
std::string value;
{
value = option.formatValue(i);
}
- formatter_.addColumnLine(1, value);
+ fileOptionFormatter_.addColumnLine(1, value);
if (value.length() > 12U && i == firstShortValue)
{
firstShortValue = i + 1;
type += ", Lib.";
}
bool bLongType = (type.length() > 12U);
- formatter_.addColumnLine(2, type);
- formatter_.addColumnLine(3, context_.substituteMarkup(option.description()));
+ fileOptionFormatter_.addColumnLine(2, type);
+ fileOptionFormatter_.addColumnLine(3, context.substituteMarkup(option.description()));
// Compute layout.
if (name.length() > 6U || firstShortValue > 0)
{
- formatter_.setColumnFirstLineOffset(1, 1);
+ fileOptionFormatter_.setColumnFirstLineOffset(1, 1);
// Assume that the name is <20 chars, so that the type fits
if (firstLongValue >= 0)
{
{
firstDescriptionLine = 1;
}
- formatter_.setColumnFirstLineOffset(3, firstDescriptionLine);
- if (firstLongValue >= 0 && formatter_.lastColumnLine(3) >= firstLongValue)
+ fileOptionFormatter_.setColumnFirstLineOffset(3, firstDescriptionLine);
+ if (firstLongValue >= 0
+ && fileOptionFormatter_.lastColumnLine(3) >= firstLongValue)
{
firstDescriptionLine = lastLongValue + 1;
- formatter_.setColumnFirstLineOffset(3, firstDescriptionLine);
+ fileOptionFormatter_.setColumnFirstLineOffset(3, firstDescriptionLine);
}
// Do the formatting.
- context_.outputFile().writeString(formatter_.formatRow());
-}
-
-
-/********************************************************************
- * ParameterWriter
- */
-
-/*! \internal \brief
- * Helper object for writing help for non-file parameters.
- *
- * \ingroup module_commandline
- */
-class ParameterWriter : public OptionsVisitor
-{
- public:
- //! Creates a helper object for writing non-file parameters.
- ParameterWriter(const HelpWriterContext &context,
- const char *timeUnit);
-
- //! Sets the writer to show hidden options.
- void setShowHidden(bool bSet) { bShowHidden_ = bSet; }
- //! Returns true if anything was written out.
- bool didOutput() const { return formatter_.didOutput(); }
-
- virtual void visitSubSection(const Options §ion);
- virtual void visitOption(const OptionInfo &option);
-
- private:
- const HelpWriterContext &context_;
- TextTableFormatter formatter_;
- const char *timeUnit_;
- bool bShowHidden_;
-};
-
-ParameterWriter::ParameterWriter(const HelpWriterContext &context,
- const char *timeUnit)
- : context_(context), timeUnit_(timeUnit), bShowHidden_(false)
-{
- formatter_.addColumn("Option", 12, false);
- formatter_.addColumn("Type", 6, false);
- formatter_.addColumn("Value", 6, false);
- formatter_.addColumn("Description", 51, true);
-}
-
-void ParameterWriter::visitSubSection(const Options §ion)
-{
- OptionsIterator iterator(section);
- iterator.acceptSubSections(this);
- iterator.acceptOptions(this);
+ context.outputFile().writeString(fileOptionFormatter_.formatRow());
}
-void ParameterWriter::visitOption(const OptionInfo &option)
+void OptionsConsoleFormatter::formatOption(
+ const HelpWriterContext &context, const OptionInfo &option)
{
- if (option.isType<FileNameOptionInfo>()
- || option.isType<SelectionFileOptionInfo>()
- || option.isType<SelectionOptionInfo>()
- || (!bShowHidden_ && option.isHidden()))
- {
- return;
- }
-
- formatter_.clear();
+ genericOptionFormatter_.clear();
bool bIsBool = option.isType<BooleanOptionInfo>();
std::string name(formatString("-%s%s", bIsBool ? "[no]" : "",
option.name().c_str()));
- formatter_.addColumnLine(0, name);
- formatter_.addColumnLine(1, option.type());
+ genericOptionFormatter_.addColumnLine(0, name);
+ genericOptionFormatter_.addColumnLine(1, option.type());
if (name.length() > 12U)
{
- formatter_.setColumnFirstLineOffset(1, 1);
+ genericOptionFormatter_.setColumnFirstLineOffset(1, 1);
}
// TODO: Better handling of multiple long values
std::string values;
}
values.append(option.formatValue(i));
}
- formatter_.addColumnLine(2, values);
- std::string description(context_.substituteMarkup(option.description()));
+ genericOptionFormatter_.addColumnLine(2, values);
+ std::string description(context.substituteMarkup(option.description()));
const DoubleOptionInfo *doubleOption = option.toType<DoubleOptionInfo>();
if (doubleOption != NULL && doubleOption->isTime())
{
- description = replaceAll(description, "%t", timeUnit_);
+ description = replaceAll(description, "%t", common_.timeUnit);
}
- formatter_.addColumnLine(3, description);
+ genericOptionFormatter_.addColumnLine(3, description);
if (values.length() > 6U)
{
- formatter_.setColumnFirstLineOffset(3, 1);
+ genericOptionFormatter_.setColumnFirstLineOffset(3, 1);
}
- context_.outputFile().writeString(formatter_.formatRow());
+ context.outputFile().writeString(genericOptionFormatter_.formatRow());
}
-
-/********************************************************************
- * SelectionParameterWriter
- */
-
-/*! \internal \brief
- * Helper object for writing help for selection parameters.
- *
- * \ingroup module_commandline
- */
-class SelectionParameterWriter : public OptionsVisitor
+void OptionsConsoleFormatter::formatSelectionOption(
+ const HelpWriterContext &context, const OptionInfo &option)
{
- public:
- //! Creates a helper object for writing selection parameters.
- explicit SelectionParameterWriter(const HelpWriterContext &context);
-
- //! Returns true if anything was written out.
- bool didOutput() const { return formatter_.didOutput(); }
-
- virtual void visitSubSection(const Options §ion);
- virtual void visitOption(const OptionInfo &option);
-
- private:
- const HelpWriterContext &context_;
- TextTableFormatter formatter_;
-};
-
-SelectionParameterWriter::SelectionParameterWriter(const HelpWriterContext &context)
- : context_(context)
-{
- formatter_.addColumn("Selection", 10, false);
- formatter_.addColumn("Description", 67, true);
-}
-
-void SelectionParameterWriter::visitSubSection(const Options §ion)
-{
- OptionsIterator iterator(section);
- iterator.acceptSubSections(this);
- iterator.acceptOptions(this);
-}
-
-void SelectionParameterWriter::visitOption(const OptionInfo &option)
-{
- if (!option.isType<SelectionFileOptionInfo>()
- && !option.isType<SelectionOptionInfo>())
- {
- return;
- }
-
- File &file = context_.outputFile();
+ File &file = context.outputFile();
- formatter_.clear();
+ selectionOptionFormatter_.clear();
std::string name(formatString("-%s", option.name().c_str()));
- formatter_.addColumnLine(0, name);
- formatter_.addColumnLine(1, context_.substituteMarkup(option.description()));
- file.writeString(formatter_.formatRow());
-
+ selectionOptionFormatter_.addColumnLine(0, name);
+ selectionOptionFormatter_.addColumnLine(1, context.substituteMarkup(option.description()));
+ file.writeString(selectionOptionFormatter_.formatRow());
+
+ TextLineWrapper wrapper;
+ wrapper.settings().setLineLength(77);
+ wrapper.settings().setFirstLineIndent(4);
+ wrapper.settings().setIndent(8);
+ wrapper.settings().setContinuationChar('\\');
// TODO: What to do with selection variables?
// They are not printed as values for any option.
for (int i = 0; i < option.valueCount(); ++i)
{
std::string value(option.formatValue(i));
- // TODO: Wrapping
- file.writeLine(formatString(" %s", value.c_str()));
+ file.writeLine(wrapper.wrapToString(value));
}
}
void CommandLineHelpWriter::writeHelp(const HelpWriterContext &context)
{
- if (context.outputFormat() != eHelpOutputFormat_Console)
+ boost::scoped_ptr<OptionsFormatterInterface> formatter;
+ CommonFormatterData common(impl_->timeUnit_.c_str());
+ switch (context.outputFormat())
{
- // TODO: Implement once the situation with Redmine issue #969 is more
- // clear.
- GMX_THROW(NotImplementedError(
- "Command-line help is not implemented for this output format"));
+ case eHelpOutputFormat_Console:
+ formatter.reset(new OptionsConsoleFormatter(common));
+ break;
+ default:
+ // TODO: Implement once the situation with Redmine issue #969 is
+ // more clear.
+ GMX_THROW(NotImplementedError(
+ "Command-line help is not implemented for this output format"));
}
- File &file = context.outputFile();
+ OptionsFilter filter(context, formatter.get());
+ filter.setShowHidden(impl_->bShowHidden_);
+
if (impl_->bShowDescriptions_)
{
+ File &file = context.outputFile();
file.writeLine("DESCRIPTION");
file.writeLine("-----------");
file.writeLine();
- DescriptionWriter(context).visitSubSection(impl_->options_);
- }
- {
- FileParameterWriter writer(context);
- writer.visitSubSection(impl_->options_);
- if (writer.didOutput())
- {
- file.writeLine();
- }
- }
- {
- ParameterWriter writer(context, impl_->timeUnit_.c_str());
- writer.setShowHidden(impl_->bShowHidden_);
- writer.visitSubSection(impl_->options_);
- if (writer.didOutput())
- {
- file.writeLine();
- }
- }
- {
- SelectionParameterWriter writer(context);
- writer.visitSubSection(impl_->options_);
- if (writer.didOutput())
- {
- file.writeLine();
- }
+ filter.formatSelected(OptionsFilter::eSelectDescriptions,
+ impl_->options_);
}
+ filter.formatSelected(OptionsFilter::eSelectFileOptions,
+ impl_->options_);
+ filter.formatSelected(OptionsFilter::eSelectOtherOptions,
+ impl_->options_);
+ filter.formatSelected(OptionsFilter::eSelectSelectionOptions,
+ impl_->options_);
}
} // namespace gmx
#include <cctype>
+#include <string>
+#include <vector>
+
#include "gromacs/options/optionsassigner.h"
#include "gromacs/utility/exceptions.h"
-#include "gromacs/utility/messagestringcollector.h"
namespace gmx
{
void CommandLineParser::parse(std::vector<std::string> *commandLine)
{
- MessageStringCollector errors;
+ ExceptionInitializer errors("Invalid command-line options");
+ std::string currentContext;
// Start in the discard phase to skip options that can't be understood.
bool bDiscard = true;
{
impl_->assigner_.finishOption();
}
- catch (const UserInputError &ex)
+ catch (UserInputError &ex)
{
- errors.append(ex.what());
+ ex.prependContext(currentContext);
+ errors.addCurrentExceptionAsNested();
}
- errors.finishContext();
+ currentContext.clear();
}
- errors.startContext("In command-line option " + *arg);
+ currentContext = "In command-line option " + *arg;
bDiscard = false;
try
{
const char *name = arg->c_str() + 1;
impl_->assigner_.startOption(name);
}
- catch (const UserInputError &ex)
+ catch (UserInputError &ex)
{
bDiscard = true;
- errors.append(ex.what());
- errors.finishContext();
+ ex.prependContext(currentContext);
+ errors.addCurrentExceptionAsNested();
+ currentContext.clear();
}
}
else if (!bDiscard)
{
impl_->assigner_.appendValue(*arg);
}
- catch (const UserInputError &ex)
+ catch (UserInputError &ex)
{
- errors.append(ex.what());
+ ex.prependContext(currentContext);
+ errors.addCurrentExceptionAsNested();
}
}
}
{
impl_->assigner_.finishOption();
}
- catch (const UserInputError &ex)
+ catch (UserInputError &ex)
{
- errors.append(ex.what());
+ ex.prependContext(currentContext);
+ errors.addCurrentExceptionAsNested();
}
- errors.finishContext();
}
impl_->assigner_.finish();
- if (!errors.isEmpty())
+ if (errors.hasNestedExceptions())
{
// TODO: This exception type may not always be appropriate.
- GMX_THROW(InvalidInputError(errors.toString()));
+ GMX_THROW(InvalidInputError(errors));
}
}
TextLineWrapper wrapper;
if (column.bWrap_)
{
- wrapper.setLineLength(column.width());
+ wrapper.settings().setLineLength(column.width());
}
std::vector<std::string> lines(wrapper.wrapToVector(text));
column.lines_.insert(column.lines_.end(), lines.begin(), lines.end());
#ifndef GMX_ONLINEHELP_HELPTOPIC_H
#define GMX_ONLINEHELP_HELPTOPIC_H
+#include "../utility/common.h"
#include "../utility/stringutil.h"
#include "../utility/uniqueptr.h"
void HelpWriterContext::writeTextBlock(const std::string &text) const
{
TextLineWrapper wrapper;
- wrapper.setLineLength(78);
+ wrapper.settings().setLineLength(78);
const char *program = ProgramInfo::getInstance().programName().c_str();
std::string newText = replaceAll(text, "[PROGRAM]", program);
outputFile().writeLine(wrapper.wrapToString(substituteMarkup(newText)));
void Options::finish()
{
- MessageStringCollector errors;
+ // TODO: Consider how to customize these error messages based on context.
+ ExceptionInitializer errors("Invalid input values");
Impl::OptionList::const_iterator i;
for (i = impl_->options_.begin(); i != impl_->options_.end(); ++i)
{
{
option.finish();
}
- catch (const UserInputError &ex)
+ catch (UserInputError &ex)
{
- MessageStringContext context(&errors, "In option " + option.name());
- errors.append(ex.what());
+ ex.prependContext("In option " + option.name());
+ errors.addCurrentExceptionAsNested();
}
}
Impl::SubSectionList::const_iterator j;
{
section.finish();
}
- catch (const UserInputError &ex)
+ catch (const UserInputError &)
{
- errors.append(ex.what());
+ errors.addCurrentExceptionAsNested();
}
}
- if (!errors.isEmpty())
+ if (errors.hasNestedExceptions())
{
// TODO: This exception type may not always be appropriate.
- GMX_THROW(InvalidInputError(errors.toString()));
+ GMX_THROW(InvalidInputError(errors));
}
}
+++ /dev/null
-options-test
{
}
-SelectionParserParameter::~SelectionParserParameter()
-{
-}
-
} // namespace gmx
/*!
*/
SelectionParserParameter(const char *name,
SelectionParserValueListPointer values);
- ~SelectionParserParameter();
//! Returns the name of the parameter (may be empty).
const std::string &name() const { return name_; }
SelectionList
SelectionCollection::parseFromFile(const std::string &filename)
{
- yyscan_t scanner;
- File file(filename, "r");
- // TODO: Exception-safe way of using the lexer.
- _gmx_sel_init_lexer(&scanner, &impl_->sc_, false, -1,
- impl_->bExternalGroupsSet_,
- impl_->grps_);
- _gmx_sel_set_lex_input_file(scanner, file.handle());
- return runParser(scanner, false, -1);
+ try
+ {
+ yyscan_t scanner;
+ File file(filename, "r");
+ // TODO: Exception-safe way of using the lexer.
+ _gmx_sel_init_lexer(&scanner, &impl_->sc_, false, -1,
+ impl_->bExternalGroupsSet_,
+ impl_->grps_);
+ _gmx_sel_set_lex_input_file(scanner, file.handle());
+ return runParser(scanner, false, -1);
+ }
+ catch (GromacsException &ex)
+ {
+ ex.prependContext(formatString(
+ "Error in parsing selections from file '%s'",
+ filename.c_str()));
+ throw;
+ }
}
*
* Public methods in this class do not throw.
*
+ * \todo
+ * Support for specifying that an option accepts, e.g., two to four selections.
+ * Currently, only a fixed count or any number of selections is possible.
+ * \if internal
+ * In addition to allowing this in OptionTemplate, also SelectionOptionManager
+ * needs to be updated.
+ * \endif
+ *
* \inpublicapi
* \ingroup module_selection
*/
SelectionList::const_iterator first = selections.begin();
SelectionList::const_iterator last = first;
RequestList::const_iterator i;
- // TODO: Improve error messages.
for (i = requests_.begin(); i != requests_.end(); ++i)
{
const SelectionRequest &request = *i;
if (request.count() > 0)
{
- if (selections.end() - first < request.count())
+ int remaining = selections.end() - first;
+ if (remaining < request.count())
{
- GMX_THROW(InvalidInputError("Too few selections provided"));
+ int assigned = first - selections.begin();
+ GMX_THROW(InvalidInputError(formatString(
+ "Too few selections provided for '%s': "
+ "Expected %d selections, but only %d left "
+ "after assigning the first %d to other selections.",
+ request.name().c_str(), request.count(),
+ remaining, assigned)));
}
last = first + request.count();
}
else
{
- if (i != requests_.end() - 1)
+ RequestList::const_iterator nextRequest = i;
+ ++nextRequest;
+ if (nextRequest != requests_.end())
{
- GMX_THROW(InvalidInputError(
- formatString("Request for selection '%s' must "
- "not be followed by others",
- request.name().c_str())));
+ const char *name = request.name().c_str();
+ const char *conflictName = nextRequest->name().c_str();
+ GMX_THROW(InvalidInputError(formatString(
+ "Ambiguous selections for '%s' and '%s': "
+ "Any number of selections is acceptable for "
+ "'%s', but you have requested subsequent "
+ "selections to be assigned to '%s'. "
+ "Resolution for such cases is not implemented, "
+ "and may be impossible.",
+ name, conflictName, name, conflictName)));
}
last = selections.end();
}
}
if (last != selections.end())
{
- GMX_THROW(InvalidInputError("Too many selections provided"));
+ int count = selections.end() - selections.begin();
+ int remaining = selections.end() - last;
+ int assigned = last - selections.begin();
+ GMX_THROW(InvalidInputError(formatString(
+ "Too many selections provided: "
+ "Expected %d selections, but %d provided. "
+ "Last %d selections could not be assigned to any option.",
+ assigned, count, remaining)));
}
}
SelectionOptionManager::parseRequestedFromFile(const std::string &filename)
{
SelectionList selections = impl_->collection_.parseFromFile(filename);
- impl_->placeSelectionsInRequests(selections);
+ try
+ {
+ impl_->placeSelectionsInRequests(selections);
+ }
+ catch (GromacsException &ex)
+ {
+ ex.prependContext(formatString(
+ "Error in adding selections from file '%s'",
+ filename.c_str()));
+ throw;
+ }
}
void
+++ /dev/null
-selection-test
const char *file, int line)
{
const char *title = getErrorCodeString(retcode);
- internal::printFatalError(stderr, title, msg, NULL, file, line);
+ internal::printFatalErrorHeader(stderr, title, NULL, file, line);
+ internal::printFatalErrorMessageLine(stderr, msg, 0);
+ internal::printFatalErrorFooter(stderr);
std::exit(1);
}
* \author Teemu Murtola <teemu.murtola@cbr.su.se>
* \ingroup module_utility
*/
-#include "gromacs/utility/errorformat.h"
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "errorformat.h"
+
+#include <cctype>
#include <cstdio>
+#include <cstring>
#include "gromacs/legacyheaders/copyrite.h"
namespace internal
{
-void printFatalError(FILE *fp, const char *title, const char *details,
- const char *func, const char *file, int line)
+void printFatalErrorHeader(FILE *fp, const char *title,
+ const char *func, const char *file, int line)
{
// In case ProgramInfo is not initialized and there is an issue with the
// initialization, fall back to "GROMACS".
}
std::fprintf(fp, "\n-------------------------------------------------------\n");
- std::fprintf(fp, "Program %s, %s\n", programName, GromacsVersion());
- if (func != NULL)
+ std::fprintf(fp, "Program: %s, %s\n", programName, GromacsVersion());
+ if (file != NULL)
{
- std::fprintf(fp, "In function: %s\n", func);
+ // TODO: Check whether this works on Windows. If it doesn't, perhaps
+ // add Path::startsWith().
+ if (startsWith(file, CMAKE_SOURCE_DIR))
+ {
+ file += std::strlen(CMAKE_SOURCE_DIR);
+ if (file[0] == '/' || file[0] == '\\')
+ {
+ ++file;
+ }
+ }
+ std::fprintf(fp, "Source file: %s (line %d)\n", file, line);
}
- // TODO: Strip away absolute paths from file names (CMake seems to generate those)
- if (file != NULL)
+ if (func != NULL)
{
- std::fprintf(fp, "Source file %s, line %d\n", file, line);
+ std::fprintf(fp, "Function: %s\n", func);
}
std::fprintf(fp, "\n");
std::fprintf(fp, "%s:\n", title);
- // TODO: Line wrapping
- std::fprintf(fp, "%s\n", details);
+}
+
+void printFatalErrorMessageLine(FILE *fp, const char *text, int indent)
+{
+ gmx::TextLineWrapper wrapper;
+ wrapper.settings().setLineLength(78 - indent);
+ size_t lineStart = 0;
+ size_t length = std::strlen(text);
+ while (lineStart < length)
+ {
+ size_t nextLineStart = wrapper.findNextLine(text, lineStart);
+ int lineLength = static_cast<int>(nextLineStart - lineStart);
+ while (lineLength > 0 && std::isspace(text[lineStart + lineLength - 1]))
+ {
+ --lineLength;
+ }
+ std::fprintf(fp, "%*s%.*s\n", indent, "", lineLength, text + lineStart);
+ lineStart = nextLineStart;
+ }
+}
+
+void printFatalErrorFooter(FILE *fp)
+{
+ std::fprintf(fp, "\n");
std::fprintf(fp, "For more information and tips for troubleshooting, please check the GROMACS\n"
"website at http://www.gromacs.org/Documentation/Errors");
std::fprintf(fp, "\n-------------------------------------------------------\n");
{
/*! \internal \brief
- * Formats common headers and footers for error messages.
+ * Formats a common header for fatal error messages.
*
* Does not throw.
*
* \ingroup module_utility
*/
-void printFatalError(FILE *fp, const char *title, const char *details,
- const char *func, const char *file, int line);
+void printFatalErrorHeader(FILE *fp, const char *title,
+ const char *func, const char *file, int line);
+/*! \internal \brief
+ * Formats a line of fatal error message text.
+ *
+ * Does not throw.
+ *
+ * \ingroup module_utility
+ */
+void printFatalErrorMessageLine(FILE *fp, const char *text, int indent);
+/*! \internal \brief
+ * Formats a common footer for fatal error messages.
+ *
+ * Does not throw.
+ *
+ * \ingroup module_utility
+ */
+void printFatalErrorFooter(FILE *fp);
} // namespace internal
//! \endcond
* \author Teemu Murtola <teemu.murtola@cbr.su.se>
* \ingroup module_utility
*/
-#include "gromacs/utility/exceptions.h"
+#include "exceptions.h"
+
+#include <cstring>
+
+#include <new>
+#include <stdexcept>
+#include <typeinfo>
#include <boost/exception/get_error_info.hpp>
+#include <boost/shared_ptr.hpp>
+#include "gromacs/legacyheaders/thread_mpi/system_error.h"
#include "gromacs/utility/errorcodes.h"
+#include "gromacs/utility/gmxassert.h"
#include "errorformat.h"
namespace gmx
{
+namespace
+{
+
+/********************************************************************
+ * ErrorMessage
+ */
+
+class ErrorMessage
+{
+ public:
+ /*! \brief
+ * Creates an error message object with the specified text.
+ *
+ * \param[in] text Text for the message.
+ */
+ explicit ErrorMessage(const std::string &text);
+
+ //! Whether this object is a context string.
+ bool isContext() const { return child_; }
+ //! Returns the text for this object.
+ const std::string &text() const { return text_; }
+ /*! \brief
+ * Returns the child object for a context object.
+ *
+ * Must not be called if isContext() returns false.
+ */
+ const ErrorMessage &child() const
+ {
+ GMX_ASSERT(isContext(),
+ "Attempting to access nonexistent message object");
+ return *child_;
+ }
+
+ /*! \brief
+ * Creates a new message object with context prepended.
+ *
+ * \param[in] context Context string to add.
+ * \returns New error message object that has \p context as its text
+ * and \c this as its child.
+ * \throws std::bad_alloc if out of memory.
+ */
+ ErrorMessage prependContext(const std::string &context) const;
+
+ private:
+ std::string text_;
+ boost::shared_ptr<ErrorMessage> child_;
+};
+
+/*! \internal \brief
+ * Stores a reason or the top-most context string of an exception.
+ *
+ * \ingroup module_utility
+ */
+typedef boost::error_info<struct errinfo_message_, ErrorMessage>
+ errinfo_message;
+
+ErrorMessage::ErrorMessage(const std::string &text)
+ : text_(text)
+{
+ size_t length = text_.find_last_not_of(" \n");
+ if (length == std::string::npos)
+ {
+ length = text_.length() - 1;
+ }
+ text_.resize(length + 1);
+}
+
+ErrorMessage
+ErrorMessage::prependContext(const std::string &context) const
+{
+ ErrorMessage newMessage(context);
+ newMessage.child_.reset(new ErrorMessage(*this));
+ return newMessage;
+}
+
+/*! \brief
+ * Stores list of nested exceptions for Gromacs exceptions.
+ *
+ * \ingroup module_utility
+ */
+typedef boost::error_info<struct errinfo_message_, internal::NestedExceptionList>
+ errinfo_nested_exceptions;
+
+} // namespace
+
/********************************************************************
* GromacsException
*/
-GromacsException::GromacsException(const std::string &reason)
+GromacsException::GromacsException(const ExceptionInitializer &details)
{
- *this << errinfo_message(reason);
+ *this << errinfo_message(ErrorMessage(details.reason_));
+ if (details.hasNestedExceptions())
+ {
+ *this << errinfo_nested_exceptions(details.nested_);
+ }
}
const char *GromacsException::what() const throw()
{
- const std::string *msg = boost::get_error_info<errinfo_message>(*this);
- return msg != NULL ? msg->c_str() : "No reason provided";
+ const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*this);
+ while (msg != NULL && msg->isContext())
+ {
+ msg = &msg->child();
+ }
+ return msg != NULL ? msg->text().c_str() : "No reason provided";
+}
+
+void GromacsException::prependContext(const std::string &context)
+{
+ const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*this);
+ GMX_RELEASE_ASSERT(msg != NULL, "Message should always be set");
+ *this << errinfo_message(msg->prependContext(context));
}
/********************************************************************
* Global functions
*/
+namespace
+{
+
+void printExceptionMessage(FILE *fp, const std::exception &ex, int indent)
+{
+ const boost::exception *boostEx = dynamic_cast<const boost::exception *>(&ex);
+ if (boostEx != NULL)
+ {
+ // TODO: Remove duplicate context if present in multiple nested exceptions.
+ const ErrorMessage *msg = boost::get_error_info<errinfo_message>(*boostEx);
+ if (msg != NULL)
+ {
+ while (msg != NULL && msg->isContext())
+ {
+ internal::printFatalErrorMessageLine(fp, msg->text().c_str(), indent*2);
+ ++indent;
+ msg = &msg->child();
+ }
+ if (msg != NULL && !msg->text().empty())
+ {
+ internal::printFatalErrorMessageLine(fp, msg->text().c_str(), indent*2);
+ }
+ }
+ else
+ {
+ internal::printFatalErrorMessageLine(fp, ex.what(), indent);
+ }
+
+ const int *errorNumber
+ = boost::get_error_info<boost::errinfo_errno>(*boostEx);
+ if (errorNumber != NULL)
+ {
+ std::fprintf(fp, "%*sReason: %s\n", (indent+1)*2, "",
+ std::strerror(*errorNumber));
+ const char * const *funcName
+ = boost::get_error_info<boost::errinfo_api_function>(*boostEx);
+ if (funcName != NULL)
+ {
+ std::fprintf(fp, "%*s(call to %s() returned error code %d)\n",
+ (indent+1)*2, "", *funcName, *errorNumber);
+ }
+ }
+
+ // TODO: Treat also boost::nested_exception (not currently used, though)
+
+ const internal::NestedExceptionList *nested
+ = boost::get_error_info<errinfo_nested_exceptions>(*boostEx);
+ if (nested != NULL)
+ {
+ internal::NestedExceptionList::const_iterator ni;
+ for (ni = nested->begin(); ni != nested->end(); ++ni)
+ {
+ try
+ {
+ rethrow_exception(*ni);
+ }
+ catch (const std::exception &nestedEx)
+ {
+ printExceptionMessage(fp, nestedEx, indent + 1);
+ }
+ }
+ }
+ }
+ else
+ {
+ internal::printFatalErrorMessageLine(fp, ex.what(), indent);
+ }
+}
+
+} // namespace
+
void printFatalErrorMessage(FILE *fp, const std::exception &ex)
{
const char *title = "Unknown exception";
+ bool bPrintType = false;
const GromacsException *gmxEx = dynamic_cast<const GromacsException *>(&ex);
- // TODO: Also treat common standard exceptions
+ // TODO: Treat more of the standard exceptions
if (gmxEx != NULL)
{
title = getErrorCodeString(gmxEx->errorCode());
}
+ else if (dynamic_cast<const tMPI::system_error *>(&ex) != NULL)
+ {
+ title = "System error in thread synchronization";
+ }
else if (dynamic_cast<const std::bad_alloc *>(&ex) != NULL)
{
title = "Memory allocation failed";
}
+ else if (dynamic_cast<const std::logic_error *>(&ex) != NULL)
+ {
+ title = "Standard library logic error (bug)";
+ bPrintType = true;
+ }
+ else if (dynamic_cast<const std::runtime_error *>(&ex) != NULL)
+ {
+ title = "Standard library runtime error (possible bug)";
+ bPrintType = true;
+ }
+ else
+ {
+ bPrintType = true;
+ }
// We can't call get_error_info directly on ex since our internal boost
// needs to be compiled with BOOST_NO_RTTI. So we do the dynamic_cast
// here instead.
filePtr = boost::get_error_info<boost::throw_file>(*boostEx);
linePtr = boost::get_error_info<boost::throw_line>(*boostEx);
}
- // TODO: Treat errno information in boost exceptions
- internal::printFatalError(fp, title, ex.what(),
- funcPtr != NULL ? *funcPtr : NULL,
- filePtr != NULL ? *filePtr : NULL,
- linePtr != NULL ? *linePtr : 0);
+ internal::printFatalErrorHeader(fp, title,
+ funcPtr != NULL ? *funcPtr : NULL,
+ filePtr != NULL ? *filePtr : NULL,
+ linePtr != NULL ? *linePtr : 0);
+ if (bPrintType)
+ {
+ std::fprintf(fp, "(exception type: %s)\n", typeid(ex).name());
+ }
+ printExceptionMessage(fp, ex, 0);
+ internal::printFatalErrorFooter(fp);
}
} // namespace gmx
#include <exception>
#include <string>
+#include <vector>
+#include <boost/exception_ptr.hpp>
#include <boost/exception/errinfo_api_function.hpp>
#include <boost/exception/errinfo_errno.hpp>
#include <boost/exception/exception.hpp>
namespace gmx
{
-/*! \brief
- * Stores a user-friendly explanation for the reason of an exception.
- *
- * Typically, should not be used directly, but through the GromacsException
- * class: it is initialized by the constructor, and can be accessed with
- * GromacsException::what().
- *
- * \inlibraryapi
- */
-typedef boost::error_info<struct errinfo_message_, std::string> errinfo_message;
+namespace internal
+{
+//! Internal container type for storing a list of nested exceptions.
+typedef std::vector<boost::exception_ptr> NestedExceptionList;
+} // namespace internal
/*! \addtopublicapi
* \{
*/
+/*! \brief
+ * Provides information for Gromacs exception constructors.
+ *
+ * This class exists to implement common functionality for initializing all
+ * Gromacs exceptions without having extra code in each exception class.
+ * In simple cases, it can be implicitly constructed by passing a simple string
+ * to an exception constructor.
+ * If more complex initialization is necessary, it is possible to explicitly
+ * construct an object of this type and then call other methods to add
+ * information before actually creating the exception object.
+ *
+ * \todo
+ * With the exception of the reason string, information added with this class
+ * is not currently accessible through any public API, except for calling
+ * printFatalErrorMessage(). This is not implemented as there is no current
+ * need for it, and it is not clear what would be the best alternative for the
+ * access. It should be possible to refactor the internal implementation to
+ * suit the needs of such external access without requiring changes in code
+ * that throws these exceptions.
+ *
+ * \ingroup module_utility
+ */
+class ExceptionInitializer
+{
+ public:
+ /*! \brief
+ * Creates an initialized with the given string as the reason.
+ *
+ * \param[in] reason Detailed reason for the exception.
+ * \throw std::bad_alloc if out of memory.
+ *
+ * This constructor is not explicit to allow constructing exceptions
+ * with a plain string argument given to the constructor without adding
+ * extra code to each exception class.
+ */
+ ExceptionInitializer(const char *reason)
+ : reason_(reason)
+ {
+ }
+ //! \copydoc ExceptionInitializer(const char *)
+ ExceptionInitializer(const std::string &reason)
+ : reason_(reason)
+ {
+ }
+
+ /*! \brief
+ * Returns true if addCurrentExceptionAsNested() has been called.
+ *
+ * Provided for convenience for cases where exceptions will be added
+ * conditionally, and the caller wants to check whether any excetions
+ * were actually added.
+ */
+ bool hasNestedExceptions() const { return !nested_.empty(); }
+ /*! \brief
+ * Adds the currently caught exception as a nested exception.
+ *
+ * May be called multiple times; all provided exceptions will be added
+ * in a list of nested exceptions.
+ *
+ * Must not be called outside a catch block.
+ */
+ void addCurrentExceptionAsNested()
+ {
+ nested_.push_back(boost::current_exception());
+ }
+
+ private:
+ std::string reason_;
+ internal::NestedExceptionList nested_;
+
+ friend class GromacsException;
+};
+
/*! \brief
* Base class for all exception objects in Gromacs.
*
*/
virtual int errorCode() const = 0;
+ /*! \brief
+ * Adds context information to this exception.
+ *
+ * \param[in] context Context string to add.
+ * \throws std::bad_alloc if out of memory.
+ *
+ * Typical use is to add additional information higher up in the call
+ * stack using this function in a catch block and the rethrow the
+ * exception.
+ *
+ * \todo
+ * The added information is currently not accessible through what(),
+ * nor through any other means except for calling
+ * printFatalErrorMessage(). See ExceptionInitializer for more
+ * discussion.
+ */
+ void prependContext(const std::string &context);
+
protected:
/*! \brief
- * Creates an exception object with the provided detailed reason.
+ * Creates an exception object with the provided initializer/reason.
*
- * \param[in] reason Detailed reason for the exception.
+ * \param[in] details Initializer for the exception.
+ * \throws std::bad_alloc if out of memory.
*/
- explicit GromacsException(const std::string &reason);
+ explicit GromacsException(const ExceptionInitializer &details);
};
/*! \brief
{
public:
/*! \brief
- * Creates an exception object with the provided detailed reason.
+ * Creates an exception object with the provided initializer/reason.
+ *
+ * \param[in] details Initializer for the exception.
+ * \throws std::bad_alloc if out of memory.
*
- * \param[in] reason Detailed reason for the exception.
+ * It is possible to call this constructor either with an explicit
+ * ExceptionInitializer object (useful for more complex cases), or
+ * a simple string if only a reason string needs to be provided.
*/
- explicit FileIOError(const std::string &reason)
- : GromacsException(reason) {}
+ explicit FileIOError(const ExceptionInitializer &details)
+ : GromacsException(details) {}
virtual int errorCode() const;
};
{
protected:
//! \copydoc FileIOError::FileIOError()
- explicit UserInputError(const std::string &reason)
- : GromacsException(reason) {}
+ explicit UserInputError(const ExceptionInitializer &details)
+ : GromacsException(details) {}
};
/*! \brief
{
public:
//! \copydoc FileIOError::FileIOError()
- explicit InvalidInputError(const std::string &reason)
- : UserInputError(reason) {}
+ explicit InvalidInputError(const ExceptionInitializer &details)
+ : UserInputError(details) {}
virtual int errorCode() const;
};
{
public:
//! \copydoc FileIOError::FileIOError()
- explicit InconsistentInputError(const std::string &reason)
- : UserInputError(reason) {}
+ explicit InconsistentInputError(const ExceptionInitializer &details)
+ : UserInputError(details) {}
virtual int errorCode() const;
};
{
public:
//! \copydoc FileIOError::FileIOError()
- explicit SimulationInstabilityError(const std::string &reason)
- : GromacsException(reason) {}
+ explicit SimulationInstabilityError(const ExceptionInitializer &details)
+ : GromacsException(details) {}
virtual int errorCode() const;
};
{
public:
//! \copydoc FileIOError::FileIOError()
- explicit InternalError(const std::string &reason)
- : GromacsException(reason) {}
+ explicit InternalError(const ExceptionInitializer &details)
+ : GromacsException(details) {}
virtual int errorCode() const;
};
{
public:
//! \copydoc FileIOError::FileIOError()
- explicit APIError(const std::string &reason)
- : GromacsException(reason) {}
+ explicit APIError(const ExceptionInitializer &details)
+ : GromacsException(details) {}
virtual int errorCode() const;
};
{
public:
//! \copydoc FileIOError::FileIOError()
- explicit NotImplementedError(const std::string &reason)
- : APIError(reason) {}
+ explicit NotImplementedError(const ExceptionInitializer &details)
+ : APIError(details) {}
virtual int errorCode() const;
};
* \author Teemu Murtola <teemu.murtola@cbr.su.se>
* \ingroup module_utility
*/
-#include "gromacs/utility/gmxassert.h"
+#include "gmxassert.h"
#include <cstdio>
#include <cstdlib>
-#include <string>
-
-#include "gromacs/utility/stringutil.h"
-
#include "errorformat.h"
namespace gmx
void assertHandler(const char *condition, const char *msg,
const char *func, const char *file, int line)
{
- try
- {
- std::string title = formatString("Condition: %s\n%s", condition, msg);
- printFatalError(stderr, "Assertion failed", title.c_str(),
- func, file, line);
- }
- catch (const std::bad_alloc &)
- {
- printFatalError(stderr, "Assertion failed",
- "(memory allocation failed while formatting the error message)",
- func, file, line);
- }
+ printFatalErrorHeader(stderr, "Assertion failed", func, file, line);
+ std::fprintf(stderr, "Condition: %s\n", condition);
+ printFatalErrorMessageLine(stderr, msg, 0);
+ printFatalErrorFooter(stderr);
std::abort();
}
return replaceInternal(input, from, to, true);
}
+
/********************************************************************
- * TextLineWrapper::Impl
+ * TextLineWrapperSettings
*/
-/*! \internal \brief
- * Private implementation class for TextLineWrapper.
- *
- * \ingroup module_utility
- */
-class TextLineWrapper::Impl
+TextLineWrapperSettings::TextLineWrapperSettings()
+ : maxLength_(0), indent_(0), firstLineIndent_(-1),
+ bStripLeadingWhitespace_(true), continuationChar_('\0')
{
- public:
- //! Initialize default values for the wrapper.
- Impl() : maxLength_(0) {}
+}
- /*! \brief
- * Helper method to find the next wrapped line.
- *
- * \param[in] input Full input string.
- * \param[in,out] lineStartPtr
- * Index of first character for the line to wrap.
- * On exit, index of the first character of the next line.
- * \returns Next output line.
- */
- std::string wrapNextLine(const std::string &input,
- size_t *lineStartPtr) const;
- //! Maximum length of output lines, or <= 0 if no limit.
- int maxLength_;
-};
+/********************************************************************
+ * TextLineWrapper
+ */
-std::string
-TextLineWrapper::Impl::wrapNextLine(const std::string &input,
- size_t *lineStartPtr) const
+size_t
+TextLineWrapper::findNextLine(const char *input, size_t lineStart) const
{
- // Strip leading whitespace.
- size_t lineStart = input.find_first_not_of(' ', *lineStartPtr);
- if (lineStart == std::string::npos)
+ size_t inputLength = std::strlen(input);
+ bool bFirstLine = (lineStart == 0 || input[lineStart - 1] == '\n');
+ // Ignore leading whitespace if necessary.
+ if (!bFirstLine || settings_.bStripLeadingWhitespace_)
{
- *lineStartPtr = lineStart;
- return std::string();
+ lineStart += std::strspn(input + lineStart, " ");
+ if (lineStart >= inputLength)
+ {
+ return inputLength;
+ }
}
- size_t lineEnd = std::string::npos;
- size_t nextNewline
- = std::min(input.find('\n', lineStart), input.length());
- if (maxLength_ <= 0 || nextNewline <= lineStart + maxLength_)
- {
- lineEnd = nextNewline;
- }
- else
+ int indent = (bFirstLine ? settings_.firstLineIndent() : settings_.indent());
+ size_t lastAllowedBreakPoint
+ = (settings_.lineLength() > 0
+ ? std::min(lineStart + settings_.lineLength() - indent, inputLength)
+ : inputLength);
+ // Ignore trailing whitespace.
+ lastAllowedBreakPoint += std::strspn(input + lastAllowedBreakPoint, " ");
+ size_t lineEnd = lineStart;
+ do
{
- size_t bestSpace = input.rfind(' ', lineStart + maxLength_);
- if (bestSpace < lineStart || bestSpace == std::string::npos)
+ const char *nextBreakPtr = std::strpbrk(input + lineEnd, " \n");
+ size_t nextBreak
+ = (nextBreakPtr != NULL ? nextBreakPtr - input : inputLength);
+ if (nextBreak > lastAllowedBreakPoint && lineEnd > lineStart)
{
- bestSpace = input.find(' ', lineStart);
+ break;
}
- lineEnd = std::min(bestSpace, nextNewline);
+ lineEnd = nextBreak + 1;
}
+ while (lineEnd < lastAllowedBreakPoint && input[lineEnd - 1] != '\n');
+ return (lineEnd < inputLength ? lineEnd : inputLength);
+}
+
+size_t
+TextLineWrapper::findNextLine(const std::string &input, size_t lineStart) const
+{
+ return findNextLine(input.c_str(), lineStart);
+}
- if (lineEnd == std::string::npos)
+std::string
+TextLineWrapper::formatLine(const std::string &input,
+ size_t lineStart, size_t lineEnd) const
+{
+ size_t inputLength = input.length();
+ bool bFirstLine = (lineStart == 0 || input[lineStart - 1] == '\n');
+ // Strip leading whitespace if necessary.
+ if (!bFirstLine || settings_.bStripLeadingWhitespace_)
{
- lineEnd = input.length();
+ lineStart = input.find_first_not_of(' ', lineStart);
+ if (lineStart >= inputLength)
+ {
+ return std::string();
+ }
}
- *lineStartPtr = lineEnd + 1;
+ int indent = (bFirstLine ? settings_.firstLineIndent() : settings_.indent());
+ bool bContinuation = (lineEnd < inputLength && input[lineEnd - 1] != '\n');
// Strip trailing whitespace.
while (lineEnd > lineStart && std::isspace(input[lineEnd - 1]))
{
}
size_t lineLength = lineEnd - lineStart;
- return input.substr(lineStart, lineLength);
-}
-
-/********************************************************************
- * TextLineWrapper
- */
-
-TextLineWrapper::TextLineWrapper()
- : impl_(new Impl)
-{
-}
-
-TextLineWrapper::~TextLineWrapper()
-{
-}
-
-TextLineWrapper &TextLineWrapper::setLineLength(int length)
-{
- impl_->maxLength_ = length;
- return *this;
+ std::string result(indent, ' ');
+ result.append(input, lineStart, lineLength);
+ if (bContinuation && settings_.continuationChar_ != '\0')
+ {
+ result.append(1, ' ');
+ result.append(1, settings_.continuationChar_);
+ }
+ return result;
}
std::string
size_t length = input.length();
while (lineStart < length)
{
- result.append(impl_->wrapNextLine(input, &lineStart));
- if (lineStart < length
- || (lineStart == length && input[length - 1] == '\n'))
+ size_t nextLineStart = findNextLine(input, lineStart);
+ result.append(formatLine(input, lineStart, nextLineStart));
+ if (nextLineStart < length
+ || (nextLineStart == length && input[length - 1] == '\n'))
{
result.append("\n");
}
+ lineStart = nextLineStart;
}
return result;
}
{
std::vector<std::string> result;
size_t lineStart = 0;
- while (lineStart < input.length())
+ size_t length = input.length();
+ while (lineStart < length)
{
- result.push_back(impl_->wrapNextLine(input, &lineStart));
+ size_t nextLineStart = findNextLine(input, lineStart);
+ result.push_back(formatLine(input, lineStart, nextLineStart));
+ lineStart = nextLineStart;
}
return result;
}
#ifndef GMX_UTILITY_STRINGUTIL_H
#define GMX_UTILITY_STRINGUTIL_H
+#include <cstring>
+
#include <string>
#include <vector>
-#include "common.h"
-
namespace gmx
{
{
return str.compare(0, prefix.length(), prefix) == 0;
}
+//! \copydoc startsWith(const std::string &, const std::string &)
+bool inline startsWith(const char *str, const char *prefix)
+{
+ return std::strncmp(str, prefix, std::strlen(prefix)) == 0;
+}
/*! \brief
* Tests whether a string ends with another string.
std::string replaceAllWords(const std::string &input,
const char *from, const char *to);
+class TextLineWrapper;
+
+/*! \brief
+ * Stores settings for line wrapping.
+ *
+ * Methods in this class do not throw.
+ *
+ * \see TextLineWrapper
+ *
+ * \inpublicapi
+ * \ingroup module_utility
+ */
+class TextLineWrapperSettings
+{
+ public:
+ /*! \brief
+ * Initializes default wrapper settings.
+ *
+ * Default settings are:
+ * - No maximum line width (only explicit line breaks).
+ * - No indentation.
+ * - No continuation characters.
+ * - Ignore whitespace after an explicit newline.
+ */
+ TextLineWrapperSettings();
+
+ /*! \brief
+ * Sets the maximum length for output lines.
+ *
+ * \param[in] length Maximum length for the lines after wrapping.
+ *
+ * If this method is not called, or is called with zero \p length, the
+ * wrapper has no maximum length (only wraps at explicit line breaks).
+ */
+ void setLineLength(int length) { maxLength_ = length; }
+ /*! \brief
+ * Sets the indentation for output lines.
+ *
+ * \param[in] indent Number of spaces to add for indentation.
+ *
+ * If this method is not called, the wrapper does not add indentation.
+ */
+ void setIndent(int indent) { indent_ = indent; }
+ /*! \brief
+ * Sets the indentation for first output line after a line break.
+ *
+ * \param[in] indent Number of spaces to add for indentation.
+ *
+ * If this method is not called, or called with \p indent equal to -1,
+ * the value set with setIndent() is used.
+ */
+ void setFirstLineIndent(int indent) { firstLineIndent_ = indent; }
+ /*! \brief
+ * Sets whether to remove spaces after an explicit newline.
+ *
+ * \param[in] bStrip If true, spaces after newline are ignored.
+ *
+ * If not removed, the space is added to the indentation set with
+ * setIndent().
+ * The default is to strip such whitespace.
+ */
+ void setStripLeadingWhitespace(bool bStrip)
+ {
+ bStripLeadingWhitespace_ = bStrip;
+ }
+ /*! \brief
+ * Sets a continuation marker for wrapped lines.
+ *
+ * \param[in] continuationChar Character to use to mark continuation
+ * lines.
+ *
+ * If set to non-zero character code, this character is added at the
+ * end of each line where a line break is added by TextLineWrapper
+ * (but not after lines produced by explicit line breaks).
+ * The default (\c '\0') is to not add continuation markers.
+ *
+ * Note that currently, the continuation char may cause the output line
+ * length to exceed the value set with setLineLength() by at most two
+ * characters.
+ */
+ void setContinuationChar(char continuationChar)
+ {
+ continuationChar_ = continuationChar;
+ }
+
+ //! Returns the maximum length set with setLineLength().
+ int lineLength() const { return maxLength_; }
+ //! Returns the indentation set with setIndent().
+ int indent() const { return indent_; }
+ /*! \brief
+ * Returns the indentation set with setFirstLineIndent().
+ *
+ * If setFirstLineIndent() has not been called or has been called with
+ * -1, indent() is returned.
+ */
+ int firstLineIndent() const
+ {
+ return (firstLineIndent_ >= 0 ? firstLineIndent_ : indent_);
+ }
+
+ private:
+ //! Maximum length of output lines, or <= 0 if no limit.
+ int maxLength_;
+ //! Number of spaces to indent each output line with.
+ int indent_;
+ /*! \brief
+ * Number of spaces to indent the first line after a newline.
+ *
+ * If -1, \a indent_ is used.
+ */
+ int firstLineIndent_;
+ //! Whether to ignore or preserve space after a newline.
+ bool bStripLeadingWhitespace_;
+ //! If not \c '\0', mark each wrapping point with this character.
+ char continuationChar_;
+
+ //! Needed to access the members.
+ friend class TextLineWrapper;
+};
+
/*! \brief
* Wraps lines to a predefined length.
*
* longer than a predefined length. Explicit newlines ('\\n') are preserved.
* Only space is considered a word separator. If a single word exceeds the
* maximum line length, it is still printed on a single line.
- * Extra whitespace is stripped from the start and end of produced lines.
- * If maximum line length is not set using setLineLength(), only wraps at
- * explicit newlines.
- *
- * Two output formats are possible: wrapToString() produces a single string
- * with embedded newlines, and wrapToVector() produces a vector of strings,
- * where each element is one line.
+ * Extra whitespace is stripped from the end of produced lines.
+ * Other options on the wrapping, such as the line length or indentation,
+ * can be changed using a TextLineWrapperSettings object.
+ *
+ * Two interfaces to do the wrapping are provided:
+ * -# High-level interface using either wrapToString() (produces a single
+ * string with embedded newlines) or wrapToVector() (produces a vector of
+ * strings with each line as one element).
+ * These methods operate on std::string and wrap the entire input string.
+ * -# Low-level interface using findNextLine() and formatLine().
+ * findNextLine() operates either on a C string or an std::string, and does
+ * not do any memory allocation (so it does not throw). It finds the next
+ * line to be wrapped, considering the wrapping settings.
+ * formatLine() does whitespace operations on the line found by
+ * findNextLine() and returns an std::string.
+ * These methods allow custom wrapping implementation to either avoid
+ * exceptions or to wrap only a part of the input string.
*
* Typical usage:
* \code
gmx::TextLineWrapper wrapper;
-wrapper.setLineLength(78);
+wrapper.settings().setLineLength(78);
printf("%s\n", wrapper.wrapToString(textToWrap).c_str());
* \endcode
*
- * Methods in this class may throw std::bad_alloc if out of memory.
- * Other exceptions are not thrown.
- *
* \inpublicapi
* \ingroup module_utility
*/
class TextLineWrapper
{
public:
- //! Constructs a new line wrapper with no initial wrapping length.
- TextLineWrapper();
- ~TextLineWrapper();
+ /*! \brief
+ * Constructs a new line wrapper with default settings.
+ *
+ * Does not throw.
+ */
+ TextLineWrapper()
+ {
+ }
+ /*! \brief
+ * Constructs a new line wrapper with given settings.
+ *
+ * \param[in] settings Wrapping settings.
+ *
+ * Does not throw.
+ */
+ explicit TextLineWrapper(const TextLineWrapperSettings &settings)
+ : settings_(settings)
+ {
+ }
/*! \brief
- * Sets the maximum length for output lines.
+ * Provides access to settings of this wrapper.
*
- * \param[in] length Maximum length for the lines after wrapping.
- * \returns *this
+ * \returns The settings object for this wrapper.
+ *
+ * The returned object can be used to modify settings for the wrapper.
+ * All subsequent calls to wrapToString() and wrapToVector() use the
+ * modified settings.
+ *
+ * Does not throw.
+ */
+ TextLineWrapperSettings &settings() { return settings_; }
+
+ /*! \brief
+ * Finds the next line to be wrapped.
*
- * If this method is not called, the wrapper has no maximum length
- * (only wraps at explicit line breaks).
+ * \param[in] input String to wrap.
+ * \param[in] lineStart Index of first character of the line to find.
+ * \returns Index of first character of the next line.
+ *
+ * If this is the last line, returns the length of \p input.
+ * In determining the length of the returned line, this function
+ * considers the maximum line length, leaving space for indentation,
+ * and also whitespace stripping behavior.
+ * Thus, the line returned may be longer than the maximum line length
+ * if it has leading and/or trailing space.
+ * When wrapping a line on a space (not on an explicit line break),
+ * the returned index is always on a non-whitespace character after the
+ * space.
+ *
+ * To iterate over lines in a string, use the following code:
+ * \code
+gmx::TextLineWrapper wrapper;
+// <set desired wrapping settings>
+size_t lineStart = 0;
+size_t length = input.length();
+while (lineStart < length)
+{
+ size_t nextLineStart = wrapper.findNextLine(input, lineStart);
+ std::string line = wrapper.formatLine(input, lineStart, nextLineStart));
+ // <do something with the line>
+ lineStart = nextLineStart;
+}
+return result;
+ * \endcode
*
* Does not throw.
*/
- TextLineWrapper &setLineLength(int length);
+ size_t findNextLine(const char *input, size_t lineStart) const;
+ //! \copydoc findNextLine(const char *, size_t) const
+ size_t findNextLine(const std::string &input, size_t lineStart) const;
+ /*! \brief
+ * Formats a single line for output according to wrapping settings.
+ *
+ * \param[in] input Input string.
+ * \param[in] lineStart Index of first character of the line to format.
+ * \param[in] lineEnd Index of first character of the next line.
+ * \returns The line with leading and/or trailing whitespace removed
+ * and indentation applied.
+ * \throws std::bad_alloc if out of memory.
+ *
+ * Intended to be used on the lines found by findNextLine().
+ * When used with the lines returned from findNextLine(), the returned
+ * line conforms to the wrapper settings.
+ * Trailing whitespace is always stripped (including any newlines,
+ * i.e., the return value does not contain a newline).
+ */
+ std::string formatLine(const std::string &input,
+ size_t lineStart, size_t lineEnd) const;
/*! \brief
* Formats a string, producing a single string with all the lines.
* \param[in] input String to wrap.
* \returns \p input with added newlines such that maximum line
* length is not exceeded.
+ * \throws std::bad_alloc if out of memory.
*
* Newlines in the input are preserved, including terminal newlines.
* Note that if the input does not contain a terminal newline, the
* \param[in] input String to wrap.
* \returns \p input split into lines such that maximum line length
* is not exceeded.
+ * \throws std::bad_alloc if out of memory.
*
* The strings in the returned vector do not contain newlines at the
* end.
std::vector<std::string> wrapToVector(const std::string &input) const;
private:
- class Impl;
-
- PrivateImplPointer<Impl> impl_;
+ TextLineWrapperSettings settings_;
};
} // namespace gmx
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="WrappedAt14/12"><![CDATA[
+ A quick brown \
+ fox jumps
+ over the lazy \
+ dog]]></String>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="WrappedWithNoLimit"><![CDATA[
+ A quick brown fox jumps
+ over the lazy dog]]></String>
+ <String Name="WrappedAt14/12"><![CDATA[
+ A quick brown
+ fox jumps
+ over the lazy
+ dog]]></String>
+</ReferenceData>
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="WrappedWithNoLimit"><![CDATA[
+ A quick brown fox jumps
+ over the lazy dog]]></String>
+ <String Name="WrappedAt14"><![CDATA[
+ A quick brown
+ fox jumps
+ over the lazy
+ dog]]></String>
+</ReferenceData>
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
<ReferenceData>
- <String Name="WrappedAt14"><![CDATA[
+ <String Name="WrappedAt14StripLeading"><![CDATA[
A quick brown
fox jumps
over the lazy
+dog]]></String>
+ <String Name="WrappedAt14PreserveLeading"><![CDATA[
+ A quick brown
+fox jumps
+ over the lazy
dog]]></String>
</ReferenceData>
EXPECT_FALSE(gmx::startsWith("", "foobar"));
EXPECT_FALSE(gmx::startsWith("foo", "foobar"));
EXPECT_FALSE(gmx::startsWith("foobar", "oob"));
+ EXPECT_TRUE(gmx::startsWith(std::string("foobar"), "foo"));
+ EXPECT_TRUE(gmx::startsWith(std::string("foobar"), ""));
+ EXPECT_TRUE(gmx::startsWith(std::string(""), ""));
+ EXPECT_FALSE(gmx::startsWith(std::string(""), "foobar"));
+ EXPECT_FALSE(gmx::startsWith(std::string("foo"), "foobar"));
+ EXPECT_FALSE(gmx::startsWith(std::string("foobar"), "oob"));
}
TEST(StringUtilityTest, EndsWithWorks)
* Tests for TextLineWrapper
*/
+//! Simple test string for wrapping.
const char g_wrapText[] = "A quick brown fox jumps over the lazy dog";
+//! Test string for wrapping with embedded line breaks.
const char g_wrapText2[] = "A quick brown fox jumps\nover the lazy dog";
+//! Test string for wrapping with a long word.
const char g_wrapTextLongWord[]
= "A quick brown fox jumps awordthatoverflowsaline over the lazy dog";
+//! Test string for wrapping with extra whitespace.
const char g_wrapTextWhitespace[] = " A quick brown fox jumps \n over the lazy dog";
typedef gmx::test::StringTestBase TextLineWrapperTest;
{
gmx::TextLineWrapper wrapper;
- wrapper.setLineLength(10);
+ wrapper.settings().setLineLength(10);
checkText(wrapper.wrapToString(g_wrapText), "WrappedAt10");
std::vector<std::string> wrapped(wrapper.wrapToVector(g_wrapText));
checker().checkSequence(wrapped.begin(), wrapped.end(), "WrappedToVector");
- wrapper.setLineLength(13);
+ wrapper.settings().setLineLength(13);
checkText(wrapper.wrapToString(g_wrapText), "WrappedAt13");
- wrapper.setLineLength(14);
+ wrapper.settings().setLineLength(14);
checkText(wrapper.wrapToString(g_wrapText), "WrappedAt14");
checkText(wrapper.wrapToString(g_wrapTextLongWord), "WrappedWithLongWord");
}
gmx::TextLineWrapper wrapper;
checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
- wrapper.setLineLength(10);
+ wrapper.settings().setLineLength(10);
checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt10");
- wrapper.setLineLength(14);
+ wrapper.settings().setLineLength(14);
checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14");
}
+TEST_F(TextLineWrapperTest, HandlesIndent)
+{
+ gmx::TextLineWrapper wrapper;
+ wrapper.settings().setIndent(2);
+
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
+ wrapper.settings().setLineLength(16);
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14");
+}
+
+TEST_F(TextLineWrapperTest, HandlesHangingIndent)
+{
+ gmx::TextLineWrapper wrapper;
+ wrapper.settings().setFirstLineIndent(2);
+ wrapper.settings().setIndent(4);
+
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
+ wrapper.settings().setLineLength(16);
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14/12");
+}
+
+TEST_F(TextLineWrapperTest, HandlesContinuationCharacter)
+{
+ gmx::TextLineWrapper wrapper;
+ wrapper.settings().setFirstLineIndent(2);
+ wrapper.settings().setIndent(4);
+ wrapper.settings().setContinuationChar('\\');
+
+ wrapper.settings().setLineLength(16);
+ checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14/12");
+}
+
TEST_F(TextLineWrapperTest, WrapsCorrectlyWithExtraWhitespace)
{
gmx::TextLineWrapper wrapper;
- wrapper.setLineLength(14);
- checkText(wrapper.wrapToString(g_wrapTextWhitespace), "WrappedAt14");
+ wrapper.settings().setLineLength(14);
+ wrapper.settings().setStripLeadingWhitespace(true);
+ checkText(wrapper.wrapToString(g_wrapTextWhitespace),
+ "WrappedAt14StripLeading");
+ wrapper.settings().setStripLeadingWhitespace(false);
+ checkText(wrapper.wrapToString(g_wrapTextWhitespace),
+ "WrappedAt14PreserveLeading");
}
} // namespace
+++ /dev/null
-g_highway
-g_logo
-g_xrama
-ngmx
+++ /dev/null
-do_dssp
-editconf
-eneconv
-g_anadock
-g_anaeig
-g_analyze
-g_angle
-g_bar
-g_bond
-g_bundle
-g_chi
-g_cluster
-g_clustsize
-g_confrms
-g_covar
-g_current
-g_density
-g_densmap
-g_dielectric
-g_dih
-g_dipoles
-g_disre
-g_dist
-g_dyndom
-g_enemat
-g_energy
-g_filter
-g_gyrate
-g_h2order
-g_hbond
-g_helix
-g_helixorient
-g_kinetics
-g_lie
-g_mdmat
-g_membed
-g_mindist
-g_morph
-g_msd
-g_nmeig
-g_nmens
-g_nmtraj
-g_order
-g_polystat
-g_potential
-g_principal
-g_rama
-g_rdf
-g_rms
-g_rmsdist
-g_rmsf
-g_rotacf
-g_rotmat
-g_saltbr
-g_sas
-g_sdf
-g_select
-g_sgangle
-g_sham
-g_sigeps
-g_sorient
-g_spatial
-g_spol
-g_tcaf
-g_traj
-g_tune_pme
-g_vanhove
-g_velacc
-g_wham
-g_wheel
-genbox
-genconf
-genion
-genrestr
-make_edi
-make_ndx
-mk_angndx
-trjcat
-trjconv
-trjorder
-xpm2ps