Merge "Allow 'atomname' and 'atomtype' in selections."
authorChristoph Junghans <junghans@votca.org>
Mon, 10 Sep 2012 19:01:15 +0000 (21:01 +0200)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Mon, 10 Sep 2012 19:01:15 +0000 (21:01 +0200)
34 files changed:
.gitignore
share/template/.gitignore [deleted file]
src/gromacs/analysisdata/tests/.gitignore [deleted file]
src/gromacs/commandline/cmdlinehelpwriter.cpp
src/gromacs/commandline/cmdlineparser.cpp
src/gromacs/onlinehelp/helpformat.cpp
src/gromacs/onlinehelp/helptopic.h
src/gromacs/onlinehelp/helpwritercontext.cpp
src/gromacs/options/options.cpp
src/gromacs/options/tests/.gitignore [deleted file]
src/gromacs/selection/parsetree.cpp
src/gromacs/selection/parsetree.h
src/gromacs/selection/selectioncollection.cpp
src/gromacs/selection/selectionoption.h
src/gromacs/selection/selectionoptionmanager.cpp
src/gromacs/selection/tests/.gitignore [deleted file]
src/gromacs/utility/errorcodes.cpp
src/gromacs/utility/errorformat.cpp
src/gromacs/utility/errorformat.h
src/gromacs/utility/exceptions.cpp
src/gromacs/utility/exceptions.h
src/gromacs/utility/gmxassert.cpp
src/gromacs/utility/stringutil.cpp
src/gromacs/utility/stringutil.h
src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesContinuationCharacter.xml [new file with mode: 0644]
src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesHangingIndent.xml [new file with mode: 0644]
src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesIndent.xml [new file with mode: 0644]
src/gromacs/utility/tests/refdata/TextLineWrapperTest_WrapsCorrectlyWithExtraWhitespace.xml
src/gromacs/utility/tests/stringutil.cpp
src/ngmx/.gitignore [deleted file]
src/programs/g_ana/.gitignore [deleted file]
src/programs/mdrun/.gitignore [deleted file]
src/testutils/tests/.gitignore
src/tools/.gitignore [deleted file]

index 3c8df1e29a09d33d4da51efa34d32779ec4df2be..e61639eb9905fb607ab14aef4969961947946631 100644 (file)
@@ -1,3 +1,5 @@
+bin
+lib
 *~
 #*
 *.o
diff --git a/share/template/.gitignore b/share/template/.gitignore
deleted file mode 100644 (file)
index 2d63f39..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-template
-Makefile.*-*
diff --git a/src/gromacs/analysisdata/tests/.gitignore b/src/gromacs/analysisdata/tests/.gitignore
deleted file mode 100644 (file)
index 0e75d4c..0000000
+++ /dev/null
@@ -1 +0,0 @@
-analysisdata-tests
index 16c99caf45d51afff1f6f8e177c9a67e60af5a57..89e6d29a9455deb42ed460a2a4506f0c7b805216 100644 (file)
@@ -59,100 +59,250 @@ namespace
 {
 
 /********************************************************************
- * 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 &section) = 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 &section);
-        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 &section)
+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 &section)
+{
+    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 &section);
-        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 &section);
+        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 &section)
+void OptionsConsoleFormatter::formatDescription(
+        const HelpWriterContext &context, const Options &section)
 {
-    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;
@@ -165,7 +315,7 @@ void FileParameterWriter::visitOptionType(const FileNameOptionInfo &option)
         {
             value = option.formatValue(i);
         }
-        formatter_.addColumnLine(1, value);
+        fileOptionFormatter_.addColumnLine(1, value);
         if (value.length() > 12U && i == firstShortValue)
         {
             firstShortValue = i + 1;
@@ -201,13 +351,13 @@ void FileParameterWriter::visitOptionType(const FileNameOptionInfo &option)
         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)
         {
@@ -220,85 +370,30 @@ void FileParameterWriter::visitOptionType(const FileNameOptionInfo &option)
     {
         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 &section);
-        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 &section)
-{
-    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;
@@ -310,86 +405,44 @@ void ParameterWriter::visitOption(const OptionInfo &option)
         }
         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 &section);
-        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 &section)
-{
-    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));
     }
 }
 
@@ -459,46 +512,37 @@ CommandLineHelpWriter &CommandLineHelpWriter::setTimeUnitString(const char *time
 
 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
index d9544799dd4c51abe3bc3a73579587dbb637a1bd..f3fe596a34229d9913f542c0ff45500255037933 100644 (file)
 
 #include <cctype>
 
+#include <string>
+#include <vector>
+
 #include "gromacs/options/optionsassigner.h"
 #include "gromacs/utility/exceptions.h"
-#include "gromacs/utility/messagestringcollector.h"
 
 namespace gmx
 {
@@ -97,7 +99,8 @@ void CommandLineParser::parse(int *argc, char *argv[])
 
 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;
 
@@ -114,24 +117,26 @@ void CommandLineParser::parse(std::vector<std::string> *commandLine)
                 {
                     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)
@@ -140,9 +145,10 @@ void CommandLineParser::parse(std::vector<std::string> *commandLine)
             {
                 impl_->assigner_.appendValue(*arg);
             }
-            catch (const UserInputError &ex)
+            catch (UserInputError &ex)
             {
-                errors.append(ex.what());
+                ex.prependContext(currentContext);
+                errors.addCurrentExceptionAsNested();
             }
         }
     }
@@ -152,17 +158,17 @@ void CommandLineParser::parse(std::vector<std::string> *commandLine)
         {
             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));
     }
 }
 
index 61d8bb9447f5ec42adcb430103e675a190c91960..688f7fa7f38fb4a098d716510cdf23e2c1a24d9e 100644 (file)
@@ -211,7 +211,7 @@ void TextTableFormatter::addColumnLine(int index, const std::string &text)
     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());
index 5f1a5098b3786927cde43055556adbcd172fce0b..bd3f1d5dc25d9e6e5a176de612136330de38b6d2 100644 (file)
@@ -40,6 +40,7 @@
 #ifndef GMX_ONLINEHELP_HELPTOPIC_H
 #define GMX_ONLINEHELP_HELPTOPIC_H
 
+#include "../utility/common.h"
 #include "../utility/stringutil.h"
 #include "../utility/uniqueptr.h"
 
index 977ddfcc771b829f20519f69e1d96f709d12b78f..eca27cbc84dc6467355098340b8b7d070ee3dd36 100644 (file)
@@ -144,7 +144,7 @@ void HelpWriterContext::writeTitle(const std::string &title) const
 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)));
index 73ff1a20357fbb5d2073198308922327f251aa38..c84664d8b7b25944f100157d688ce6509aa93197 100644 (file)
@@ -169,7 +169,8 @@ bool Options::isSet(const char *name) const
 
 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)
     {
@@ -178,10 +179,10 @@ void Options::finish()
         {
             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;
@@ -192,15 +193,15 @@ void Options::finish()
         {
             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));
     }
 }
 
diff --git a/src/gromacs/options/tests/.gitignore b/src/gromacs/options/tests/.gitignore
deleted file mode 100644 (file)
index 0672786..0000000
+++ /dev/null
@@ -1 +0,0 @@
-options-test
index 68aebbc204ede19546dc99cef7414c5d71021edd..5d6858904aebab0c8d0336f15595877b2ddc3c4f 100644 (file)
@@ -310,10 +310,6 @@ SelectionParserParameter::SelectionParserParameter(
 {
 }
 
-SelectionParserParameter::~SelectionParserParameter()
-{
-}
-
 } // namespace gmx
 
 /*!
index fa78205dab66cdffe0802f7b47efa20b54a2e6f0..ccfd8b3fbd261257af07a1a4dd7154312b066df2 100644 (file)
@@ -355,7 +355,6 @@ class SelectionParserParameter
          */
         SelectionParserParameter(const char *name,
                                  SelectionParserValueListPointer values);
-        ~SelectionParserParameter();
 
         //! Returns the name of the parameter (may be empty).
         const std::string &name() const { return name_; }
index 6e8d228c78d12b18ac182c908f884917b39af48c..19b5727599103d59f79bde2a5c030b9451dc2156 100644 (file)
@@ -499,15 +499,25 @@ SelectionCollection::parseFromStdin(int nr, bool bInteractive)
 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;
+    }
 }
 
 
index e970f8916aeb4e299577b2df1388122ffe6487a0..18c1b3e137902f79b6a2ffbbf97125070e050b8f 100644 (file)
@@ -55,6 +55,14 @@ class SelectionOptionStorage;
  *
  * 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
  */
index 6463dfe5b1a4ada99c308574bddc3a44eb44a479..df78579a946a3e020d617847b6a5767d3f5add9e 100644 (file)
@@ -177,26 +177,40 @@ void SelectionOptionManager::Impl::placeSelectionsInRequests(
     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();
         }
@@ -206,7 +220,14 @@ void SelectionOptionManager::Impl::placeSelectionsInRequests(
     }
     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)));
     }
 }
 
@@ -298,7 +319,17 @@ void
 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
diff --git a/src/gromacs/selection/tests/.gitignore b/src/gromacs/selection/tests/.gitignore
deleted file mode 100644 (file)
index 8a29fe3..0000000
+++ /dev/null
@@ -1 +0,0 @@
-selection-test
index 4d2a207ba9d174c3ba64fa53ce7ce2aacce2c280..da02091926eac63832968cd1beae02957c73f0f6 100644 (file)
@@ -82,7 +82,9 @@ void standardErrorHandler(int retcode, const char *msg,
                           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);
 }
 
index ad9643b08ac533a51b43983cee49f8e168d5c437..546145fc1bdeb94de8052e66f35da0d1311efb01 100644 (file)
  * \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"
 
@@ -51,8 +57,8 @@ namespace gmx
 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".
@@ -66,20 +72,51 @@ void printFatalError(FILE *fp, const char *title, const char *details,
     }
 
     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");
index 30da26002163157b07d5c320aeccbb13b95660a3..e4b2bab47a3ca5dc78701787d7f698fd8a960f3f 100644 (file)
@@ -48,14 +48,30 @@ namespace internal
 {
 
 /*! \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
index 3244e7d33bed420f44062312abe5e3321f800613..fc4ea3340099761c75cdff1e7b15019e487d1d51 100644 (file)
  * \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));
 }
 
 /********************************************************************
@@ -105,19 +214,109 @@ int NotImplementedError::errorCode() const
  * 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.
@@ -131,11 +330,16 @@ void printFatalErrorMessage(FILE *fp, const std::exception &ex)
         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
index 7f246325ec5ba247ddbdfd409561f5cac0ed231f..7448835a61531337e4855df9b96f0b89add42e92 100644 (file)
@@ -44,7 +44,9 @@
 
 #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.
  *
@@ -98,13 +168,32 @@ class GromacsException : public std::exception, public boost::exception
          */
         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
@@ -116,12 +205,17 @@ class FileIOError : public GromacsException
 {
     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;
 };
@@ -138,8 +232,8 @@ class UserInputError : public GromacsException
 {
     protected:
         //! \copydoc FileIOError::FileIOError()
-        explicit UserInputError(const std::string &reason)
-            : GromacsException(reason) {}
+        explicit UserInputError(const ExceptionInitializer &details)
+            : GromacsException(details) {}
 };
 
 /*! \brief
@@ -151,8 +245,8 @@ class InvalidInputError : public UserInputError
 {
     public:
         //! \copydoc FileIOError::FileIOError()
-        explicit InvalidInputError(const std::string &reason)
-            : UserInputError(reason) {}
+        explicit InvalidInputError(const ExceptionInitializer &details)
+            : UserInputError(details) {}
 
         virtual int errorCode() const;
 };
@@ -166,8 +260,8 @@ class InconsistentInputError : public UserInputError
 {
     public:
         //! \copydoc FileIOError::FileIOError()
-        explicit InconsistentInputError(const std::string &reason)
-            : UserInputError(reason) {}
+        explicit InconsistentInputError(const ExceptionInitializer &details)
+            : UserInputError(details) {}
 
         virtual int errorCode() const;
 };
@@ -181,8 +275,8 @@ class SimulationInstabilityError : public GromacsException
 {
     public:
         //! \copydoc FileIOError::FileIOError()
-        explicit SimulationInstabilityError(const std::string &reason)
-            : GromacsException(reason) {}
+        explicit SimulationInstabilityError(const ExceptionInitializer &details)
+            : GromacsException(details) {}
 
         virtual int errorCode() const;
 };
@@ -196,8 +290,8 @@ class InternalError : public GromacsException
 {
     public:
         //! \copydoc FileIOError::FileIOError()
-        explicit InternalError(const std::string &reason)
-            : GromacsException(reason) {}
+        explicit InternalError(const ExceptionInitializer &details)
+            : GromacsException(details) {}
 
         virtual int errorCode() const;
 };
@@ -211,8 +305,8 @@ class APIError : public GromacsException
 {
     public:
         //! \copydoc FileIOError::FileIOError()
-        explicit APIError(const std::string &reason)
-            : GromacsException(reason) {}
+        explicit APIError(const ExceptionInitializer &details)
+            : GromacsException(details) {}
 
         virtual int errorCode() const;
 };
@@ -226,8 +320,8 @@ class NotImplementedError : public APIError
 {
     public:
         //! \copydoc FileIOError::FileIOError()
-        explicit NotImplementedError(const std::string &reason)
-            : APIError(reason) {}
+        explicit NotImplementedError(const ExceptionInitializer &details)
+            : APIError(details) {}
 
         virtual int errorCode() const;
 };
index 2f4f2ed6602ef246c64441a6f6aa843c80b41058..018dcd110b6eb91dda3588ab6651c33086ab0cb2 100644 (file)
  * \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
@@ -56,18 +52,10 @@ namespace internal
 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();
 }
 
index a0cf5c13ab56743bf226de63d5a6bdc19e24e8c3..61ba3a0c37194d704f4d2c7f5b5e5c4b5dc59d2f 100644 (file)
@@ -177,71 +177,83 @@ replaceAllWords(const std::string &input, const char *from, const char *to)
     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]))
     {
@@ -249,26 +261,14 @@ TextLineWrapper::Impl::wrapNextLine(const std::string &input,
     }
 
     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
@@ -279,12 +279,14 @@ TextLineWrapper::wrapToString(const std::string &input) const
     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;
 }
@@ -294,9 +296,12 @@ TextLineWrapper::wrapToVector(const std::string &input) const
 {
     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;
 }
index f6bb5c917409a4cd4b8973b01872079f9c88402b..0dd731771be673aa23b736acc37919f5f60c1cda 100644 (file)
 #ifndef GMX_UTILITY_STRINGUTIL_H
 #define GMX_UTILITY_STRINGUTIL_H
 
+#include <cstring>
+
 #include <string>
 #include <vector>
 
-#include "common.h"
-
 namespace gmx
 {
 
@@ -63,6 +63,11 @@ bool inline startsWith(const std::string &str, const std::string &prefix)
 {
     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.
@@ -181,6 +186,126 @@ std::string replaceAll(const std::string &input,
 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.
  *
@@ -188,46 +313,126 @@ std::string replaceAllWords(const std::string &input,
  * 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.
@@ -235,6 +440,7 @@ class TextLineWrapper
          * \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
@@ -247,6 +453,7 @@ class TextLineWrapper
          * \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.
@@ -257,9 +464,7 @@ class TextLineWrapper
         std::vector<std::string> wrapToVector(const std::string &input) const;
 
     private:
-        class Impl;
-
-        PrivateImplPointer<Impl> impl_;
+        TextLineWrapperSettings settings_;
 };
 
 } // namespace gmx
diff --git a/src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesContinuationCharacter.xml b/src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesContinuationCharacter.xml
new file mode 100644 (file)
index 0000000..c726f2f
--- /dev/null
@@ -0,0 +1,9 @@
+<?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>
diff --git a/src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesHangingIndent.xml b/src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesHangingIndent.xml
new file mode 100644 (file)
index 0000000..793270b
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesIndent.xml b/src/gromacs/utility/tests/refdata/TextLineWrapperTest_HandlesIndent.xml
new file mode 100644 (file)
index 0000000..3a117d1
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
index cfeeb01b4ca52a621dcd9433126404b216d83542..f277fc930b97c4ba9836c38f9b0d203ea8c22603 100644 (file)
@@ -1,9 +1,14 @@
 <?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>
index 335f8f0ab66d02369586361805aacde787337542..b1c317076440385f2c26eeea479193309cfc19d7 100644 (file)
@@ -64,6 +64,12 @@ TEST(StringUtilityTest, StartsWithWorks)
     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)
@@ -174,10 +180,14 @@ TEST(ReplaceAllTest, HandlesPossibleRecursiveMatches)
  * 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;
@@ -234,13 +244,13 @@ TEST_F(TextLineWrapperTest, WrapsCorrectly)
 {
     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");
 }
@@ -250,18 +260,55 @@ TEST_F(TextLineWrapperTest, WrapsCorrectlyWithExistingBreaks)
     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
diff --git a/src/ngmx/.gitignore b/src/ngmx/.gitignore
deleted file mode 100644 (file)
index 43a470b..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-g_highway
-g_logo
-g_xrama
-ngmx
diff --git a/src/programs/g_ana/.gitignore b/src/programs/g_ana/.gitignore
deleted file mode 100644 (file)
index e25cc71..0000000
+++ /dev/null
@@ -1 +0,0 @@
-g_ana
diff --git a/src/programs/mdrun/.gitignore b/src/programs/mdrun/.gitignore
deleted file mode 100644 (file)
index d4822df..0000000
+++ /dev/null
@@ -1 +0,0 @@
-mdrun
index 8d1061e1f86aa63c85fb5dcdb2da41895eb34f84..4236747b4e55d9f7b755c5791c1f6c3e5e25ebb4 100644 (file)
@@ -1,2 +1 @@
-testutils-test
 refdata
diff --git a/src/tools/.gitignore b/src/tools/.gitignore
deleted file mode 100644 (file)
index 87fe1df..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-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