/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2012,2013,2014,2015,2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2012,2013,2014,2015,2016,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
Options options;
options.addOption(StringOption("export").store(&exportFormat)
.enumValue(exportFormats));
- CommandLineParser(&options).parse(&argc, argv);
+ CommandLineParser(&options).allowPositionalArguments(true).parse(&argc, argv);
if (!exportFormat.empty())
{
ModificationCheckingFileOutputRedirector redirector(impl_->outputRedirector_);
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2012,2013,2014,2015,2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2012,2013,2014,2015,2016,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
{
// If not in single-module mode, process options to the wrapper binary.
// TODO: Ideally, this could be done by CommandLineParser.
- int argcForWrapper = 1;
- while (argcForWrapper < *argc && (*argv)[argcForWrapper][0] == '-')
+
+ // Find the module name (if any) in the arg list
+ int indexOfModuleName = 1;
+ while (indexOfModuleName < *argc && (*argv)[indexOfModuleName][0] == '-')
{
- ++argcForWrapper;
+ ++indexOfModuleName;
}
- if (argcForWrapper > 1)
+ if (indexOfModuleName > 1)
{
+ // Process options that are provided to the wrapper
+ // binary. These precede the module name, if one exists.
+ int argcForWrapper = indexOfModuleName;
CommandLineParser(optionsHolder->options())
.parse(&argcForWrapper, *argv);
}
// If no action requested and there is a module specified, process it.
- if (argcForWrapper < *argc && !optionsHolder->shouldIgnoreActualModule())
+ if (indexOfModuleName < *argc && !optionsHolder->shouldIgnoreActualModule())
{
- const char *moduleName = (*argv)[argcForWrapper];
+ const char *moduleName = (*argv)[indexOfModuleName];
CommandLineModuleMap::const_iterator moduleIter
= findModuleByName(moduleName);
if (moduleIter == modules_.end())
GMX_THROW(InvalidInputError(message));
}
module = moduleIter->second.get();
- *argc -= argcForWrapper;
- *argv += argcForWrapper;
+ *argc -= indexOfModuleName;
+ *argv += indexOfModuleName;
// After this point, argc and argv are the same independent of
// which path is taken: (*argv)[0] is the module name.
}
// TODO: It could be nicer to only recognize -h/-hidden if module is not
// null.
CommandLineParser(optionsHolder->options())
+ .allowPositionalArguments(true)
.skipUnknown(true).parse(argc, *argv);
}
if (!optionsHolder->finishOptions())
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2010,2011,2012,2013,2014,2015,2017, by the GROMACS development team, led by
+ * Copyright (c) 2010,2011,2012,2013,2014,2015,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
OptionsAssigner assigner_;
//! Whether to allow and skip unknown options.
bool bSkipUnknown_;
+ /*! \brief Whether to allow positional arguments
+ *
+ * These are not options (no leading hyphen), and come before
+ * all options. */
+ bool bAllowPositionalArguments_;
};
CommandLineParser::Impl::Impl(Options *options)
- : assigner_(options), bSkipUnknown_(false)
+ : assigner_(options), bSkipUnknown_(false), bAllowPositionalArguments_(false)
{
assigner_.setAcceptBooleanNoPrefix(true);
}
return *this;
}
+CommandLineParser &CommandLineParser::allowPositionalArguments(bool bEnabled)
+{
+ impl_->bAllowPositionalArguments_ = bEnabled;
+ return *this;
+}
+
void CommandLineParser::parse(int *argc, char *argv[])
{
ExceptionInitializer errors("Invalid command-line options");
std::string currentContext;
bool bInOption = false;
+ // Note that this function gets called multiple times in typical
+ // cases of calling gmx. Command lines like "gmx -hidden mdrun -h"
+ // work because the first call has argv[0] == "gmx" and skips
+ // unknown things, and the second has argv[0] == "mdrun".
+ int i = 1, newi = 1;
+
+ // First, process any permitted leading positional arguments.
+ for (; i < *argc; ++i)
+ {
+ const char *const arg = argv[i];
+ if (impl_->toOptionName(arg) != nullptr)
+ {
+ // If we find an option, no more positional arguments
+ // can be handled.
+ break;
+ }
+
+ if (!impl_->bAllowPositionalArguments_)
+ {
+ GMX_THROW(InvalidInputError
+ ("Positional argument '" + std::string(arg) + "' cannot be accepted. "
+ "Perhaps you forgot to put a hyphen before an option name."));
+ }
+ // argv[i] is not an option, so preserve it in the argument list
+ // by incrementing newi. There's no need to copy argv contents
+ // because they cannot have changed yet.
+ ++newi;
+ }
+
+ // Now handle the option arguments.
impl_->assigner_.start();
- int newi = 1;
- for (int i = 1; i < *argc; ++i)
+ for (; i < *argc; ++i)
{
const char *const arg = argv[i];
const char *const optionName = impl_->toOptionName(arg);
errors.addCurrentExceptionAsNested();
}
}
- // Remove recognized options if applicable.
+ // Retain unrecognized options if applicable.
if (!bInOption && impl_->bSkipUnknown_)
{
argv[newi] = argv[i];
++newi;
}
}
- // Update the argc count if argv was modified.
+ // Update the args if argv was modified.
if (impl_->bSkipUnknown_)
{
*argc = newi;
argv[newi] = nullptr;
}
+
// Finish the last option.
if (bInOption)
{
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2010,2011,2012,2013,2014,2016, by the GROMACS development team, led by
+ * Copyright (c) 2010,2011,2012,2013,2014,2016,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
*/
CommandLineParser &skipUnknown(bool bEnabled);
+ /*! \brief
+ * Makes the parser accept positional arguments
+ *
+ * \param[in] bEnabled Whether to skip and keep positional arguments.
+ * \returns *this
+ *
+ * Arguments that are not options (ie. no leading hyphen), and
+ * which come before all options are acceptable if this has
+ * been enabled. If so, these arguments are left in \c argc
+ * and \c argv in parse().
+ *
+ * The default is false: unknown leading arguments result in
+ * exceptions and \c argc and \c argv are not modified.
+ *
+ * Does not throw.
+ */
+ CommandLineParser &allowPositionalArguments(bool bEnabled);
+
/*! \brief
* Parses the command line.
*
* \throws std::bad_alloc if out of memory.
* \throws InvalidInputError if any errors were detected in the input.
*
- * All command-line arguments are parsed, and an aggregate exception
- * with all the detected errors is thrown in the end.
+ * All command-line arguments are parsed, and an aggregate
+ * exception with all the detected errors (including unknown
+ * options, where applicable) is thrown in the end.
+ *
+ * If skipUnknown() was not called, or last called with a
+ * false value, the input arguments are not modified. If
+ * skipUnknown() was last called with a true value, only
+ * unknown options will be retained in \c argc and \c argv.
+ *
+ * All positional arguments are retained in the argument list,
+ * but such arguments must precede all options.
+ *
+ * \c argv[0] is never modified.
*
- * If skipUnknown() is false, the input parameters are not modified.
- * If skipUnknown() is true, recognized options and their values are
- * removed from the argument list. \c argv[0] is never modified.
*/
void parse(int *argc, char *argv[]);
*
* Copyright (c) 1991-2000, University of Groningen, The Netherlands.
* Copyright (c) 2001-2004, The GROMACS development team.
- * Copyright (c) 2013,2014,2015,2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2013,2014,2015,2016,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
}
/* Now parse all the command-line options */
- gmx::CommandLineParser(&options).skipUnknown(isFlagSet(PCA_NOEXIT_ON_ARGS))
+ gmx::CommandLineParser(&options)
+ .skipUnknown(isFlagSet(PCA_NOEXIT_ON_ARGS))
+ .allowPositionalArguments(isFlagSet(PCA_NOEXIT_ON_ARGS))
.parse(argc, argv);
behaviors.optionsFinishing();
options.finish();
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2012,2013,2014,2015,2017, by the GROMACS development team, led by
+ * Copyright (c) 2012,2013,2014,2015,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
ASSERT_EQ(0, rc);
}
+TEST_F(CommandLineModuleManagerTest, RunsModuleHelpAfterQuiet)
+{
+ const char *const cmdline[] = {
+ "test", "-quiet", "help", "module"
+ };
+ CommandLine args(cmdline);
+ initManager(args, "test");
+ MockModule &mod1 = addModule("module", "First module");
+ addModule("other", "Second module");
+ using ::testing::_;
+ EXPECT_CALL(mod1, writeHelp(_));
+ mod1.setExpectedDisplayName("test module");
+ int rc = 0;
+ ASSERT_NO_THROW_GMX(rc = manager().run(args.argc(), args.argv()));
+ ASSERT_EQ(0, rc);
+}
+
TEST_F(CommandLineModuleManagerTest, RunsModuleHelpWithDashH)
{
const char *const cmdline[] = {
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2010,2011,2012,2013,2014,2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2010,2011,2012,2013,2014,2016,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
std::vector<double> dvalues_;
int ivalue1p_;
int ivalue12_;
+ std::string stringValue_;
};
CommandLineParserTest::CommandLineParserTest()
using gmx::BooleanOption;
using gmx::IntegerOption;
using gmx::DoubleOption;
+ using gmx::StringOption;
options_.addOption(BooleanOption("flag").store(&flag_));
options_.addOption(IntegerOption("mvi").storeVector(&ivalues_).multiValue());
options_.addOption(DoubleOption("mvd").storeVector(&dvalues_).allowMultiple());
options_.addOption(IntegerOption("1p").store(&ivalue1p_));
options_.addOption(IntegerOption("12").store(&ivalue12_));
+ options_.addOption(StringOption("str").store(&stringValue_));
}
TEST_F(CommandLineParserTest, HandlesSingleValues)
CommandLine args(cmdline);
ASSERT_NO_THROW_GMX(parser_.parse(&args.argc(), args.argv()));
ASSERT_NO_THROW_GMX(options_.finish());
+ EXPECT_EQ(7, args.argc());
+ EXPECT_STREQ("test", args.arg(0));
EXPECT_TRUE(flag_);
ASSERT_EQ(1U, ivalues_.size());
EXPECT_DOUBLE_EQ(2.7, dvalues_[0]);
}
+TEST_F(CommandLineParserTest, HandlesBooleanWithoutArgument)
+{
+ const char *const cmdline[] = {
+ "test", "-flag"
+ };
+ CommandLine args(cmdline);
+ ASSERT_NO_THROW_GMX(parser_.parse(&args.argc(), args.argv()));
+ ASSERT_NO_THROW_GMX(options_.finish());
+ EXPECT_EQ(2, args.argc());
+ EXPECT_STREQ("test", args.arg(0));
+
+ EXPECT_TRUE(flag_);
+}
+
+TEST_F(CommandLineParserTest, HandlesBooleanAsNoWithoutArgument)
+{
+ const char *const cmdline[] = {
+ "test", "-noflag"
+ };
+ CommandLine args(cmdline);
+ ASSERT_NO_THROW_GMX(parser_.parse(&args.argc(), args.argv()));
+ ASSERT_NO_THROW_GMX(options_.finish());
+ EXPECT_EQ(2, args.argc());
+ EXPECT_STREQ("test", args.arg(0));
+
+ EXPECT_FALSE(flag_);
+}
+
+TEST_F(CommandLineParserTest, ThrowsWithBooleanAsNoWithArgument)
+{
+ const char *const cmdline[] = {
+ "test", "-noflag", "no"
+ };
+ CommandLine args(cmdline);
+ EXPECT_THROW_GMX(parser_.parse(&args.argc(), args.argv()), gmx::InvalidInputError);
+}
+
TEST_F(CommandLineParserTest, HandlesNegativeNumbers)
{
const char *const cmdline[] = {
CommandLine args(cmdline);
ASSERT_NO_THROW_GMX(parser_.parse(&args.argc(), args.argv()));
ASSERT_NO_THROW_GMX(options_.finish());
+ EXPECT_EQ(6, args.argc());
+ EXPECT_STREQ("test", args.arg(0));
ASSERT_EQ(2U, ivalues_.size());
EXPECT_EQ(1, ivalues_[0]);
EXPECT_DOUBLE_EQ(-2.7, dvalues_[0]);
}
+TEST_F(CommandLineParserTest, HandlesString)
+{
+ const char *const cmdline[] = {
+ "test", "-str", "text"
+ };
+ CommandLine args(cmdline);
+ ASSERT_NO_THROW_GMX(parser_.parse(&args.argc(), args.argv()));
+ ASSERT_NO_THROW_GMX(options_.finish());
+ EXPECT_EQ(3, args.argc());
+ EXPECT_STREQ("test", args.arg(0));
+
+ EXPECT_EQ("text", stringValue_);
+}
+
+TEST_F(CommandLineParserTest, RejectsStringWithMultipleValues)
+{
+ const char *const cmdline[] = {
+ "test", "-str", "text", "excess text"
+ };
+ CommandLine args(cmdline);
+ EXPECT_THROW_GMX(parser_.parse(&args.argc(), args.argv()), gmx::InvalidInputError);
+}
+
TEST_F(CommandLineParserTest, HandlesDoubleDashOptionPrefix)
{
const char *const cmdline[] = {
CommandLine args(cmdline);
ASSERT_NO_THROW_GMX(parser_.parse(&args.argc(), args.argv()));
ASSERT_NO_THROW_GMX(options_.finish());
+ EXPECT_EQ(6, args.argc());
+ EXPECT_STREQ("test", args.arg(0));
ASSERT_EQ(2U, ivalues_.size());
EXPECT_EQ(1, ivalues_[0]);
CommandLine args(cmdline);
ASSERT_NO_THROW_GMX(parser_.parse(&args.argc(), args.argv()));
ASSERT_NO_THROW_GMX(options_.finish());
+ EXPECT_EQ(5, args.argc());
+ EXPECT_STREQ("test", args.arg(0));
EXPECT_EQ(1, ivalue12_);
EXPECT_EQ(-12, ivalue1p_);
TEST_F(CommandLineParserTest, HandlesSkipUnknown)
{
const char *const cmdline[] = {
- "test", "-opt1", "-flag", "-opt2", "value", "-mvi", "2", "-mvd", "2.7", "-opt3"
+ "test", "-unknown1", "-flag", "-unknown2", "value", "-mvi", "2", "-mvd", "2.7", "-unknown3"
};
CommandLine args(cmdline);
parser_.skipUnknown(true);
ASSERT_EQ(5, args.argc());
EXPECT_STREQ("test", args.arg(0));
- EXPECT_STREQ("-opt1", args.arg(1));
- EXPECT_STREQ("-opt2", args.arg(2));
+ EXPECT_STREQ("-unknown1", args.arg(1));
+ EXPECT_STREQ("-unknown2", args.arg(2));
EXPECT_STREQ("value", args.arg(3));
- EXPECT_STREQ("-opt3", args.arg(4));
+ EXPECT_STREQ("-unknown3", args.arg(4));
EXPECT_TRUE(args.arg(5) == nullptr);
EXPECT_TRUE(flag_);
EXPECT_DOUBLE_EQ(2.7, dvalues_[0]);
}
+TEST_F(CommandLineParserTest, RejectsPositionalArgumentsByDefault)
+{
+ // Ensures that "gmx trjconv f" gets rejected.
+ const char *const cmdline[] = {
+ "test", "module", "positional"
+ };
+ CommandLine args(cmdline);
+ EXPECT_THROW_GMX(parser_.parse(&args.argc(), args.argv()), gmx::InvalidInputError);
+}
+
+TEST_F(CommandLineParserTest, CanAllowPositionalArguments)
+{
+ // Ensures that "gmx help trjconv" works
+ const char *const cmdline[] = {
+ "test", "module", "positional", "-flag"
+ };
+ CommandLine args(cmdline);
+ parser_.allowPositionalArguments(true);
+ ASSERT_NO_THROW_GMX(parser_.parse(&args.argc(), args.argv()));
+ ASSERT_NO_THROW_GMX(options_.finish());
+ ASSERT_EQ(4, args.argc());
+ EXPECT_STREQ("test", args.arg(0));
+ EXPECT_STREQ("module", args.arg(1));
+ EXPECT_STREQ("positional", args.arg(2));
+}
+
+TEST_F(CommandLineParserTest, CannotHavePositionalArgumentsAfterOptions)
+{
+ // Even for the options that can't have arbitrary numbers of
+ // values, there's no way to check whether there's been enough
+ // values provided, so we can't have positional arguments after
+ // any options.
+ const char *const cmdline[] = {
+ "test", "module", "-1p", "2", "positional"
+ };
+ CommandLine args(cmdline);
+ parser_.allowPositionalArguments(true);
+ EXPECT_THROW_GMX(parser_.parse(&args.argc(), args.argv()), gmx::InvalidInputError);
+}
+
} // namespace
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2013,2014,2015,2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2013,2014,2015,2016,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
done_filenms(nfile(), fnm);
}
+// This is needed e.g. for tune_pme, which passes unknown arguments on
+// to child mdrun processes that it spawns.
TEST_F(ParseCommonArgsTest, CanKeepUnknownArgs)
{
int ivalue = 0;
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2017, by the GROMACS development team, led by
+ * Copyright (c) 2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
void runTest()
{
auto &cmdline = commandLine();
- cmdline.append("energy");
setInputFile("-s", "dhdl.tpr");
setInputFile("-f", "dhdl.edr");
void runTest(const char *stringForStdin)
{
auto &cmdline = commandLine();
- cmdline.append("energy");
setInputFile("-s", "orires.tpr");
setInputFile("-f", "orires.edr");
void runTest(const char *stringForStdin)
{
auto &cmdline = commandLine();
- cmdline.append("energy");
setInputFile("-f", "ener.edr");
setOutputFile("-o", "energy.xvg", XvgMatch());
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2013,2014,2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2013,2014,2016,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
void runTest(const char *fileName)
{
auto &cmdline = commandLine();
- cmdline.append("traj");
setInputFile("-s", "spc2.gro");
setInputFile("-f", fileName);
setOutputFile("-ox", "spc2.xvg", gmx::test::NoTextMatch());
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2013,2014,2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2013,2014,2016,2017,2018, by the GROMACS development team, led by
* Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
* and including many others, as listed in the AUTHORS file in the
* top-level source directory and at http://www.gromacs.org.
void runTest(const char *fileName)
{
auto &cmdline = commandLine();
- cmdline.append("trjconv");
setInputFile("-s", "spc2.gro");
setInputFile("-f", fileName);