From: Teemu Murtola Date: Tue, 6 May 2014 03:32:47 +0000 (+0300) Subject: Replace all command line parsing with Options X-Git-Url: http://biod.pnpi.spb.ru/gitweb/?a=commitdiff_plain;h=eb9a45e5088458dadf5dd4dc47c7a38213788d11;p=alexxy%2Fgromacs.git Replace all command line parsing with Options - Add a more elaborate conversion mechanism in pargs.cpp, that also converts the values back for option types where the C and C++ types do not match. - Arguments added in parse_common_args() no longer need to go through the conversion, but instead use Options mechanisms directly. - Time unit option is now handled through TimeUnitManager. It requires extra refactoring to make oenv to use this also internally, but that would remove more duplication. - A few more tweaks to FileNameOption behavior. The implementation is not perhaps the cleanest currently, but refactoring will follow. There can be some glitches in uncommon cases or on error paths in the new approach, but in general, error handling should be much more user-friendly. Part of future FileNameOption refactoring aims to improve this part futher. The only way to find issues is for people to actually use the new code. Change-Id: Iea51bd379a3cd04ad62ac76822fcb9f4251ee9d4 --- diff --git a/src/gromacs/commandline/pargs.cpp b/src/gromacs/commandline/pargs.cpp index 6b240668b8..151c3b9e50 100644 --- a/src/gromacs/commandline/pargs.cpp +++ b/src/gromacs/commandline/pargs.cpp @@ -46,23 +46,27 @@ #include #include +#include +#include + #ifdef HAVE_UNISTD_H #include #endif #include "thread_mpi/threads.h" -#include "gromacs/legacyheaders/macros.h" - #include "gromacs/commandline/cmdlinehelpcontext.h" #include "gromacs/commandline/cmdlinehelpwriter.h" -#include "gromacs/commandline/shellcompletions.h" +#include "gromacs/commandline/cmdlineparser.h" #include "gromacs/fileio/timecontrol.h" #include "gromacs/options/basicoptions.h" #include "gromacs/options/filenameoption.h" +#include "gromacs/options/filenameoptionmanager.h" #include "gromacs/options/options.h" +#include "gromacs/options/timeunitmanager.h" #include "gromacs/utility/arrayref.h" #include "gromacs/utility/basenetwork.h" +#include "gromacs/utility/common.h" #include "gromacs/utility/cstringutil.h" #include "gromacs/utility/exceptions.h" #include "gromacs/utility/fatalerror.h" @@ -74,98 +78,6 @@ /* The source code in this file should be thread-safe. Please keep it that way. */ -static void usage(const char *type, const char *arg) -{ - GMX_ASSERT(arg != NULL, "NULL command-line argument should not occur"); - gmx_fatal(FARGS, "Expected %s argument for option %s\n", type, arg); -} - -/* Scan an int for argument argv[*i] from argument at argv[*i + 1]. - * eg: -p 32. argv[*i] is only used for error reporting. - * If there is no value, or the conversion is not successful, the - * routine exits with an error, otherwise it returns the value found. - * *i is incremented once. - */ -static int iscan(int argc, char *argv[], int *i) -{ - const char *const arg = argv[*i]; - if (argc <= (*i)+1) - { - usage("an integer", arg); - } - const char *const value = argv[++(*i)]; - char *endptr; - int var = std::strtol(value, &endptr, 10); - if (*value == '\0' || *endptr != '\0') - { - usage("an integer", arg); - } - return var; -} - -/* Same as above, but for large integer values */ -static gmx_int64_t istepscan(int argc, char *argv[], int *i) -{ - const char *const arg = argv[*i]; - if (argc <= (*i)+1) - { - usage("an integer", arg); - } - const char *const value = argv[++(*i)]; - char *endptr; - gmx_int64_t var = str_to_int64_t(value, &endptr); - if (*value == '\0' || *endptr != '\0') - { - usage("an integer", arg); - } - return var; -} - -/* Routine similar to the above, but working on doubles. */ -static double dscan(int argc, char *argv[], int *i) -{ - const char *const arg = argv[*i]; - if (argc <= (*i)+1) - { - usage("a real", arg); - } - const char *const value = argv[++(*i)]; - char *endptr; - double var = std::strtod(value, &endptr); - if (*value == '\0' || *endptr != '\0') - { - usage("a real", arg); - } - return var; -} - -/* Routine similar to the above, but working on strings. The pointer - * returned is a pointer to the argv field. - */ -static char *sscan(int argc, char *argv[], int *i) -{ - if (argc > (*i)+1) - { - if ( (argv[(*i)+1][0] == '-') && (argc > (*i)+2) && - (argv[(*i)+2][0] != '-') ) - { - fprintf(stderr, "Possible missing string argument for option %s\n\n", - argv[*i]); - } - } - else - { - usage("a string", argv[*i]); - } - - return argv[++(*i)]; -} - -static gmx_bool is_hidden(t_pargs *pa) -{ - return (strstr(pa->desc, "HIDDEN") != NULL); -} - int nenum(const char *const enumc[]) { int i; @@ -180,140 +92,6 @@ int nenum(const char *const enumc[]) return i; } -/* Read a number of arguments from the command line. - * For etINT, etREAL and etCHAR an extra argument is read (when present) - * for etBOOL the gmx_boolean option is changed to the negate value - */ -static void get_pargs(int *argc, char *argv[], int nparg, t_pargs pa[]) -{ - int i, j, k, match; - gmx_bool *bKeep; - char buf[32]; - char *ptr; - - snew(bKeep, *argc+1); - bKeep[0] = TRUE; - bKeep[*argc] = TRUE; - - for (i = 1; (i < *argc); i++) - { - bKeep[i] = TRUE; - for (j = 0; (j < nparg); j++) - { - if (pa[j].type == etBOOL) - { - sprintf(buf, "-no%s", pa[j].option+1); - if (strcmp(pa[j].option, argv[i]) == 0) - { - *pa[j].u.b = TRUE; - pa[j].bSet = TRUE; - bKeep[i] = FALSE; - } - else if (strcmp(buf, argv[i]) == 0) - { - *pa[j].u.b = FALSE; - pa[j].bSet = TRUE; - bKeep[i] = FALSE; - } - } - else if (strcmp(pa[j].option, argv[i]) == 0) - { - if (pa[j].bSet) - { - fprintf(stderr, "Setting option %s more than once!\n", - pa[j].option); - } - pa[j].bSet = TRUE; - bKeep[i] = FALSE; - switch (pa[j].type) - { - case etINT: - *pa[j].u.i = iscan(*argc, argv, &i); - break; - case etINT64: - *pa[j].u.is = istepscan(*argc, argv, &i); - break; - case etTIME: - case etREAL: - *pa[j].u.r = dscan(*argc, argv, &i); - break; - case etSTR: - *(pa[j].u.c) = sscan(*argc, argv, &i); - break; - case etENUM: - match = -1; - ptr = sscan(*argc, argv, &i); - for (k = 1; (pa[j].u.c[k] != NULL); k++) - { - /* only check ptr against beginning of - pa[j].u.c[k] */ - if (gmx_strncasecmp(ptr, pa[j].u.c[k], strlen(ptr)) == 0) - { - if ( ( match == -1 ) || - ( strlen(pa[j].u.c[k]) < - strlen(pa[j].u.c[match]) ) ) - { - match = k; - } - } - } - if (match != -1) - { - pa[j].u.c[0] = pa[j].u.c[match]; - } - else - { - gmx_fatal(FARGS, "Invalid argument %s for option %s", - ptr, pa[j].option); - } - break; - case etRVEC: - (*pa[j].u.rv)[0] = dscan(*argc, argv, &i); - if ( (i+1 == *argc) || - ( (argv[i+1][0] == '-') && - !isdigit(argv[i+1][1]) ) ) - { - (*pa[j].u.rv)[1] = - (*pa[j].u.rv)[2] = - (*pa[j].u.rv)[0]; - } - else - { - bKeep[i] = FALSE; - (*pa[j].u.rv)[1] = dscan(*argc, argv, &i); - if ( (i+1 == *argc) || - ( (argv[i+1][0] == '-') && - !isdigit(argv[i+1][1]) ) ) - { - gmx_fatal(FARGS, - "%s: vector must have 1 or 3 real parameters", - pa[j].option); - } - bKeep[i] = FALSE; - (*pa[j].u.rv)[2] = dscan(*argc, argv, &i); - } - break; - default: - gmx_fatal(FARGS, "Invalid type %d in pargs", pa[j].type); - } - /* i may be incremented, so set it to not keep */ - bKeep[i] = FALSE; - } - } - } - - /* Remove used entries */ - for (i = j = 0; (i <= *argc); i++) - { - if (bKeep[i]) - { - argv[j++] = argv[i]; - } - } - (*argc) = j-1; - sfree(bKeep); -} - int opt2parg_int(const char *option, int nparg, t_pargs pa[]) { int i; @@ -420,89 +198,141 @@ const char *opt2parg_enum(const char *option, int nparg, t_pargs pa[]) * parse_common_args() */ -static void set_default_time_unit(const char *time_list[], gmx_bool bCanTime) +namespace gmx { - int i = 0; - const char *select = NULL; - - if (bCanTime) - { - select = getenv("GMXTIMEUNIT"); - if (select != NULL) - { - i = 1; - while (time_list[i] && strcmp(time_list[i], select) != 0) - { - i++; - } - } - } - if (!bCanTime || select == NULL || - time_list[i] == NULL || strcmp(time_list[i], select) != 0) - { - /* Set it to the default: ps */ - i = 1; - while (time_list[i] && strcmp(time_list[i], "ps") != 0) - { - i++; - } - } - time_list[0] = time_list[i]; -} - -static void set_default_xvg_format(const char *xvg_list[]) +namespace { - int i; - const char *select; - select = getenv("GMX_VIEW_XVG"); - if (select == NULL) - { - /* The default is the first option */ - xvg_list[0] = xvg_list[1]; - } - else +/*! \brief + * Returns the index of the default xvg format. + * + * \ingroup module_commandline + */ +int getDefaultXvgFormat(gmx::ConstArrayRef xvgFormats) +{ + const char *const select = getenv("GMX_VIEW_XVG"); + if (select != NULL) { - i = 1; - while (xvg_list[i] && strcmp(xvg_list[i], select) != 0) + ConstArrayRef::const_iterator i = + std::find(xvgFormats.begin(), xvgFormats.end(), std::string(select)); + if (i != xvgFormats.end()) { - i++; - } - if (xvg_list[i] != NULL) - { - xvg_list[0] = xvg_list[i]; + return i - xvgFormats.begin(); } else { - xvg_list[0] = xvg_list[exvgNONE]; + return exvgNONE - 1; } } + /* The default is the first option */ + return 0; } -static int add_parg(int npargs, t_pargs *pa, t_pargs *pa_add) -{ - memcpy(&(pa[npargs]), pa_add, sizeof(*pa_add)); - - return npargs+1; -} - -namespace gmx -{ - -namespace -{ - /*! \brief - * Converts a t_filenm option into an Options option. + * Conversion helper between t_pargs/t_filenm and Options. * - * \param options Options object to add the new option to. - * \param[in] fnm t_filenm option to convert. + * This class holds the necessary mapping between the old C structures and + * the new C++ options to allow copying values back after parsing for cases + * where the C++ options do not directly provide the type of value required for + * the C structures. * * \ingroup module_commandline */ -void filenmToOptions(Options *options, const t_filenm *fnm) +class OptionsAdapter { + public: + /*! \brief + * Initializes the adapter to convert from a specified command line. + * + * The command line is required, because t_pargs wants to return + * strings by reference to the original command line. + * OptionsAdapter creates a copy of the `argv` array (but not the + * strings) to make this possible, even if the parser removes + * options it has recognized. + */ + OptionsAdapter(int argc, const char *const argv[]) + : argv_(argv, argv + argc) + { + } + + /*! \brief + * Converts a t_filenm option into an Options option. + * + * \param options Options object to add the new option to. + * \param fnm t_filenm option to convert. + */ + void filenmToOptions(Options *options, t_filenm *fnm); + /*! \brief + * Converts a t_pargs option into an Options option. + * + * \param options Options object to add the new option to. + * \param pa t_pargs option to convert. + */ + void pargsToOptions(Options *options, t_pargs *pa); + + /*! \brief + * Copies values back from options to t_pargs/t_filenm. + */ + void copyValues(bool bReadNode); + + private: + struct FileNameData + { + //! Creates a conversion helper for a given `t_filenm` struct. + explicit FileNameData(t_filenm *fnm) : fnm(fnm), optionInfo(NULL) + { + } + + //! t_filenm structure to receive the final values. + t_filenm *fnm; + //! Option info object for the created FileNameOption. + FileNameOptionInfo *optionInfo; + //! Value storage for the created FileNameOption. + std::vector values; + }; + struct ProgramArgData + { + //! Creates a conversion helper for a given `t_pargs` struct. + explicit ProgramArgData(t_pargs *pa) + : pa(pa), optionInfo(NULL), enumIndex(0), boolValue(false) + { + } + + //! t_pargs structure to receive the final values. + t_pargs *pa; + //! Option info object for the created option. + OptionInfo *optionInfo; + //! Value storage for a non-enum StringOption (unused for other types). + std::string stringValue; + //! Value storage for an enum option (unused for other types). + int enumIndex; + //! Value storage for a BooleanOption (unused for other types). + bool boolValue; + }; + + std::vector argv_; + // These are lists instead of vectors to avoid relocating existing + // objects in case the container is reallocated (the Options object + // contains pointes to members of the objects, which would get + // invalidated). + std::list fileNameOptions_; + std::list programArgs_; + + GMX_DISALLOW_COPY_AND_ASSIGN(OptionsAdapter); +}; + +void OptionsAdapter::filenmToOptions(Options *options, t_filenm *fnm) +{ + if (fnm->opt == NULL) + { + // Existing code may use opt2fn() instead of ftp2fn() for + // options that use the default option name, so we need to + // keep the old behavior instead of fixing opt2fn(). + // TODO: Check that this is not the case, remove this, and make + // opt2*() work even if fnm->opt is NULL for some options. + fnm->opt = ftp2defopt(fnm->ftp); + } const bool bRead = ((fnm->flag & ffREAD) != 0); const bool bWrite = ((fnm->flag & ffWRITE) != 0); const bool bOptional = ((fnm->flag & ffOPT) != 0); @@ -514,81 +344,136 @@ void filenmToOptions(Options *options, const t_filenm *fnm) { defName = ftp2defnm(fnm->ftp); } - // Since we are not (yet) using this for actual parsing, we don't need to - // set any storage. - options->addOption( - FileNameOption(name).defaultBasename(defName).legacyType(fnm->ftp) - .readWriteFlags(bRead, bWrite).required(!bOptional) - .libraryFile(bLibrary).multiValue(bMultiple) - .description(ftp2desc(fnm->ftp))); + fileNameOptions_.push_back(FileNameData(fnm)); + FileNameData &data = fileNameOptions_.back(); + data.optionInfo = options->addOption( + FileNameOption(name).storeVector(&data.values) + .defaultBasename(defName).legacyType(fnm->ftp) + .legacyOptionalBehavior() + .readWriteFlags(bRead, bWrite).required(!bOptional) + .libraryFile(bLibrary).multiValue(bMultiple) + .description(ftp2desc(fnm->ftp))); } -/*! \brief - * Converts a t_pargs option into an Options option. - * - * \param options Options object to add the new option to. - * \param[in] pa t_pargs option to convert. - * - * \ingroup module_commandline - */ -void pargsToOptions(Options *options, t_pargs *pa) +void OptionsAdapter::pargsToOptions(Options *options, t_pargs *pa) { - const bool bHidden = is_hidden(pa); + const bool bHidden = startsWith(pa->desc, "HIDDEN"); const char *const name = &pa->option[1]; const char *const desc = (bHidden ? &pa->desc[6] : pa->desc); - // Since we are not (yet) using this for actual parsing, we can take some - // shortcuts and not set any storage where there is no direct - // correspondence in the types. + programArgs_.push_back(ProgramArgData(pa)); + ProgramArgData &data = programArgs_.back(); switch (pa->type) { case etINT: - options->addOption( - IntegerOption(name).store(pa->u.i) - .description(desc).hidden(bHidden)); + data.optionInfo = options->addOption( + IntegerOption(name).store(pa->u.i) + .description(desc).hidden(bHidden)); return; case etINT64: - options->addOption( - Int64Option(name).store(pa->u.is) - .description(desc).hidden(bHidden)); + data.optionInfo = options->addOption( + Int64Option(name).store(pa->u.is) + .description(desc).hidden(bHidden)); return; case etREAL: - options->addOption( - RealOption(name).store(pa->u.r) - .description(desc).hidden(bHidden)); + data.optionInfo = options->addOption( + RealOption(name).store(pa->u.r) + .description(desc).hidden(bHidden)); return; case etTIME: - options->addOption( - RealOption(name).store(pa->u.r).timeValue() - .description(desc).hidden(bHidden)); + data.optionInfo = options->addOption( + RealOption(name).store(pa->u.r).timeValue() + .description(desc).hidden(bHidden)); return; case etSTR: { const char *const defValue = (*pa->u.c != NULL ? *pa->u.c : ""); - options->addOption( - StringOption(name).defaultValue(defValue) - .description(desc).hidden(bHidden)); + data.optionInfo = options->addOption( + StringOption(name).store(&data.stringValue) + .defaultValue(defValue) + .description(desc).hidden(bHidden)); return; } case etBOOL: - options->addOption( - BooleanOption(name).defaultValue(*pa->u.b) - .description(desc).hidden(bHidden)); + data.optionInfo = options->addOption( + BooleanOption(name).store(&data.boolValue) + .defaultValue(*pa->u.b) + .description(desc).hidden(bHidden)); return; case etRVEC: - options->addOption( - RealOption(name).store(*pa->u.rv).vector() - .description(desc).hidden(bHidden)); + data.optionInfo = options->addOption( + RealOption(name).store(*pa->u.rv).vector() + .description(desc).hidden(bHidden)); return; case etENUM: - options->addOption( - StringOption(name).defaultEnumIndex(nenum(pa->u.c) - 1) - .enumValueFromNullTerminatedArray(pa->u.c + 1) - .description(desc).hidden(bHidden)); + { + const int defaultIndex = (pa->u.c[0] != NULL ? nenum(pa->u.c) - 1 : 0); + data.optionInfo = options->addOption( + StringOption(name).storeEnumIndex(&data.enumIndex) + .defaultEnumIndex(defaultIndex) + .enumValueFromNullTerminatedArray(pa->u.c + 1) + .description(desc).hidden(bHidden)); return; + } } GMX_THROW(NotImplementedError("Argument type not implemented")); } +void OptionsAdapter::copyValues(bool bReadNode) +{ + std::list::const_iterator file; + for (file = fileNameOptions_.begin(); file != fileNameOptions_.end(); ++file) + { + // FIXME: FF_NOT_READ_NODE should also skip all fexist() calls in + // FileNameOption. However, it is not currently used, and other + // commented out code for using it is also outdated, so left this for + // later. + if (!bReadNode && (file->fnm->flag & ffREAD)) + { + continue; + } + if (file->optionInfo->isSet()) + { + file->fnm->flag |= ffSET; + } + file->fnm->nfiles = file->values.size(); + snew(file->fnm->fns, file->fnm->nfiles); + for (int i = 0; i < file->fnm->nfiles; ++i) + { + // TODO: Check for out-of-memory. + file->fnm->fns[i] = strdup(file->values[i].c_str()); + } + } + std::list::const_iterator arg; + for (arg = programArgs_.begin(); arg != programArgs_.end(); ++arg) + { + arg->pa->bSet = arg->optionInfo->isSet(); + switch (arg->pa->type) + { + case etSTR: + { + if (arg->pa->bSet) + { + std::vector::const_iterator pos = + std::find(argv_.begin(), argv_.end(), arg->stringValue); + GMX_RELEASE_ASSERT(pos != argv_.end(), + "String argument got a value not in argv"); + *arg->pa->u.c = *pos; + } + break; + } + case etBOOL: + *arg->pa->u.b = arg->boolValue; + break; + case etENUM: + *arg->pa->u.c = arg->pa->u.c[arg->enumIndex + 1]; + break; + default: + // For other types, there is nothing type-specific to do. + break; + } + } +} + } // namespace } // namespace gmx @@ -600,279 +485,183 @@ gmx_bool parse_common_args(int *argc, char *argv[], unsigned long Flags, output_env_t *oenv) { /* This array should match the order of the enum in oenv.h */ - const char *xvg_format[] = { NULL, "xmgrace", "xmgr", "none", NULL }; - /* This array should match the order of the enum in oenv.h */ - const char *time_units[] = { - NULL, "fs", "ps", "ns", "us", "ms", "s", - NULL - }; - int nicelevel = 0, debug_level = 0; - char *deffnm = NULL; - real tbegin = 0, tend = 0, tdelta = 0; - gmx_bool bView = FALSE; - - t_pargs *all_pa = NULL; - - t_pargs nice_pa = { - "-nice", FALSE, etINT, {&nicelevel}, - "Set the nicelevel" - }; - t_pargs deffnm_pa = { - "-deffnm", FALSE, etSTR, {&deffnm}, - "Set the default filename for all file options" - }; - t_pargs begin_pa = { - "-b", FALSE, etTIME, {&tbegin}, - "First frame (%t) to read from trajectory" - }; - t_pargs end_pa = { - "-e", FALSE, etTIME, {&tend}, - "Last frame (%t) to read from trajectory" - }; - t_pargs dt_pa = { - "-dt", FALSE, etTIME, {&tdelta}, - "Only use frame when t MOD dt = first time (%t)" - }; - t_pargs view_pa = { - "-w", FALSE, etBOOL, {&bView}, - "View output [TT].xvg[tt], [TT].xpm[tt], [TT].eps[tt] and [TT].pdb[tt] files" - }; - t_pargs xvg_pa = { - "-xvg", FALSE, etENUM, {xvg_format}, - "xvg plot formatting" - }; - t_pargs time_pa = { - "-tu", FALSE, etENUM, {time_units}, - "Time unit" - }; - /* Maximum number of extra arguments */ -#define EXTRA_PA 16 - - t_pargs pca_pa[] = { - { "-debug", FALSE, etINT, {&debug_level}, - "HIDDENWrite file with debug information, 1: short, 2: also x and f" }, - }; -#define NPCA_PA asize(pca_pa) - gmx_bool bXvgr; - int i, j, k, npall, max_pa; + const char *const xvg_formats[] = { "xmgrace", "xmgr", "none" }; // Handle the flags argument, which is a bit field // The FF macro returns whether or not the bit is set #define FF(arg) ((Flags & arg) == arg) - /* Check for double arguments */ - for (i = 1; (i < *argc); i++) + try { - if (argv[i] && (strlen(argv[i]) > 1) && (!std::isdigit(argv[i][1]))) + int nicelevel = 0, debug_level = 0; + double tbegin = 0.0, tend = 0.0, tdelta = 0.0; + bool bView = false; + int xvgFormat = 0; + gmx::TimeUnitManager timeUnitManager; + gmx::OptionsAdapter adapter(*argc, argv); + gmx::Options options(NULL, NULL); + gmx::FileNameOptionManager fileOptManager; + + options.setDescription(gmx::ConstArrayRef(desc, ndesc)); + options.addOption( + gmx::IntegerOption("debug").store(&debug_level).hidden() + .description("Write file with debug information, " + "1: short, 2: also x and f")); + + options.addOption( + gmx::IntegerOption("nice").store(&nicelevel) + .defaultValue(FF(PCA_BE_NICE) ? 19 : 0) + .description("Set the nicelevel")); + + if (FF(PCA_CAN_SET_DEFFNM)) { - for (j = i+1; (j < *argc); j++) - { - if ( (argv[i][0] == '-') && (argv[j][0] == '-') && - (strcmp(argv[i], argv[j]) == 0) ) - { - if (FF(PCA_NOEXIT_ON_ARGS)) - { - fprintf(stderr, "Double command line argument %s\n", - argv[i]); - } - else - { - gmx_fatal(FARGS, "Double command line argument %s\n", - argv[i]); - } - } - } + fileOptManager.addDefaultFileNameOption(&options, "deffnm"); } - } - - /* Check ALL the flags ... */ - max_pa = NPCA_PA + EXTRA_PA + npargs+1; - snew(all_pa, max_pa); - - for (i = npall = 0; (i < static_cast(NPCA_PA)); i++) - { - npall = add_parg(npall, all_pa, &(pca_pa[i])); - } - - if (FF(PCA_BE_NICE)) - { - nicelevel = 19; - } - npall = add_parg(npall, all_pa, &nice_pa); - - if (FF(PCA_CAN_SET_DEFFNM)) - { - npall = add_parg(npall, all_pa, &deffnm_pa); - } - if (FF(PCA_CAN_BEGIN)) - { - npall = add_parg(npall, all_pa, &begin_pa); - } - if (FF(PCA_CAN_END)) - { - npall = add_parg(npall, all_pa, &end_pa); - } - if (FF(PCA_CAN_DT)) - { - npall = add_parg(npall, all_pa, &dt_pa); - } - if (FF(PCA_TIME_UNIT)) - { - npall = add_parg(npall, all_pa, &time_pa); - } - if (FF(PCA_CAN_VIEW)) - { - npall = add_parg(npall, all_pa, &view_pa); - } - - bXvgr = FALSE; - for (i = 0; (i < nfile); i++) - { - bXvgr = bXvgr || (fnm[i].ftp == efXVG); - } - if (bXvgr) - { - npall = add_parg(npall, all_pa, &xvg_pa); - } - - /* Now append the program specific arguments */ - for (i = 0; (i < npargs); i++) - { - npall = add_parg(npall, all_pa, &(pa[i])); - } - - /* set etENUM options to default */ - for (i = 0; (i < npall); i++) - { - if (all_pa[i].type == etENUM) + if (FF(PCA_CAN_BEGIN)) { - all_pa[i].u.c[0] = all_pa[i].u.c[1]; + options.addOption( + gmx::DoubleOption("b").store(&tbegin).timeValue() + .description("First frame (%t) to read from trajectory")); + } + if (FF(PCA_CAN_END)) + { + options.addOption( + gmx::DoubleOption("e").store(&tend).timeValue() + .description("Last frame (%t) to read from trajectory")); + } + if (FF(PCA_CAN_DT)) + { + options.addOption( + gmx::DoubleOption("dt").store(&tdelta).timeValue() + .description("Only use frame when t MOD dt = first time (%t)")); + } + if (FF(PCA_TIME_UNIT)) + { + timeUnitManager.setTimeUnitFromEnvironment(); + timeUnitManager.addTimeUnitOption(&options, "tu"); + } + if (FF(PCA_CAN_VIEW)) + { + options.addOption( + gmx::BooleanOption("w").store(&bView) + .description("View output [TT].xvg[tt], [TT].xpm[tt], " + "[TT].eps[tt] and [TT].pdb[tt] files")); } - } - set_default_time_unit(time_units, FF(PCA_TIME_UNIT)); - set_default_xvg_format(xvg_format); - - /* Now parse all the command-line options */ - get_pargs(argc, argv, npall, all_pa); - - /* set program name, command line, and default values for output options */ - output_env_init(oenv, gmx::getProgramContext(), (time_unit_t)nenum(time_units), bView, - (xvg_format_t)nenum(xvg_format), 0, debug_level); - - /* Parse the file args */ - parse_file_args(argc, argv, nfile, fnm, deffnm, !FF(PCA_NOT_READ_NODE)); - - /* Open the debug file */ - if (debug_level > 0) - { - char buf[256]; - if (gmx_mpi_initialized()) + bool bXvgr = false; + for (int i = 0; i < nfile; i++) { - sprintf(buf, "%s%d.debug", output_env_get_short_program_name(*oenv), - gmx_node_rank()); + bXvgr = bXvgr || (fnm[i].ftp == efXVG); } - else + xvgFormat = gmx::getDefaultXvgFormat(xvg_formats); + if (bXvgr) { - sprintf(buf, "%s.debug", output_env_get_short_program_name(*oenv)); + options.addOption( + gmx::StringOption("xvg").enumValue(xvg_formats) + .storeEnumIndex(&xvgFormat) + .description("xvg plot formatting")); } - init_debug(debug_level, buf); - fprintf(stderr, "Opening debug file %s (src code file %s, line %d)\n", - buf, __FILE__, __LINE__); - } + /* Now append the program specific arguments */ + for (int i = 0; i < nfile; i++) + { + adapter.filenmToOptions(&options, &fnm[i]); + } + for (int i = 0; i < npargs; i++) + { + adapter.pargsToOptions(&options, &pa[i]); + } - /* Now copy the results back... */ - for (i = 0, k = npall-npargs; (i < npargs); i++, k++) - { - memcpy(&(pa[i]), &(all_pa[k]), (size_t)sizeof(pa[i])); - } + setManagerForFileNameOptions(&options, &fileOptManager); - bool bExit = false; - try - { const gmx::CommandLineHelpContext *context = gmx::GlobalCommandLineHelpContext::get(); - bExit = (context != NULL); - if (context != NULL && !(FF(PCA_QUIET))) + if (context != NULL) { - gmx::Options options(NULL, NULL); - options.setDescription(gmx::ConstArrayRef(desc, ndesc)); - for (i = 0; i < nfile; i++) + if (!(FF(PCA_QUIET))) { - gmx::filenmToOptions(&options, &fnm[i]); + gmx::CommandLineHelpWriter(options) + .setShowDescriptions(true) + .setTimeUnitString(timeUnitManager.timeUnitAsString()) + .setKnownIssues(gmx::ConstArrayRef(bugs, nbugs)) + .writeHelp(*context); } - for (i = 0; i < npall; i++) + return FALSE; + } + + /* Now parse all the command-line options */ + gmx::CommandLineParser(&options).skipUnknown(FF(PCA_NOEXIT_ON_ARGS)) + .parse(argc, argv); + options.finish(); + + /* set program name, command line, and default values for output options */ + output_env_init(oenv, gmx::getProgramContext(), + (time_unit_t)(timeUnitManager.timeUnit() + 1), bView, + (xvg_format_t)(xvgFormat + 1), 0, debug_level); + + /* Open the debug file */ + if (debug_level > 0) + { + char buf[256]; + + if (gmx_mpi_initialized()) + { + sprintf(buf, "%s%d.debug", output_env_get_short_program_name(*oenv), + gmx_node_rank()); + } + else { - gmx::pargsToOptions(&options, &all_pa[i]); + sprintf(buf, "%s.debug", output_env_get_short_program_name(*oenv)); } - gmx::CommandLineHelpWriter(options) - .setShowDescriptions(true) - .setTimeUnitString(output_env_get_time_unit(*oenv)) - .setKnownIssues(gmx::ConstArrayRef(bugs, nbugs)) - .writeHelp(*context); + + init_debug(debug_level, buf); + fprintf(stderr, "Opening debug file %s (src code file %s, line %d)\n", + buf, __FILE__, __LINE__); } - } - GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR; - /* Set the nice level */ + /* Set the nice level */ #ifdef HAVE_UNISTD_H #ifndef GMX_NO_NICE - /* The some system, e.g. the catamount kernel on cray xt3 do not have nice(2). */ - if (nicelevel != 0 && !bExit) - { - static gmx_bool nice_set = FALSE; /* only set it once */ - static tMPI_Thread_mutex_t init_mutex = TMPI_THREAD_MUTEX_INITIALIZER; - tMPI_Thread_mutex_lock(&init_mutex); - if (!nice_set) + /* The some system, e.g. the catamount kernel on cray xt3 do not have nice(2). */ + if (nicelevel != 0) { - if (nice(nicelevel) == -1) + static gmx_bool nice_set = FALSE; /* only set it once */ + static tMPI_Thread_mutex_t init_mutex = TMPI_THREAD_MUTEX_INITIALIZER; + tMPI_Thread_mutex_lock(&init_mutex); + if (!nice_set) { - /* Do nothing, but use the return value to avoid warnings. */ + if (nice(nicelevel) == -1) + { + /* Do nothing, but use the return value to avoid warnings. */ + } + nice_set = TRUE; } - nice_set = TRUE; + tMPI_Thread_mutex_unlock(&init_mutex); } - tMPI_Thread_mutex_unlock(&init_mutex); - } #endif #endif - /* convert time options, must be done after printing! */ + timeUnitManager.scaleTimeOptions(&options); - for (i = 0; i < npall; i++) - { - if (all_pa[i].type == etTIME && all_pa[i].bSet) + /* Extract Time info from arguments */ + // TODO: Use OptionInfo objects instead of string constants + if (FF(PCA_CAN_BEGIN) && options.isSet("b")) { - *all_pa[i].u.r *= output_env_get_time_invfactor(*oenv); + setTimeValue(TBEGIN, tbegin); } - } - - /* Extract Time info from arguments */ - if (FF(PCA_CAN_BEGIN) && opt2parg_bSet("-b", npall, all_pa)) - { - setTimeValue(TBEGIN, opt2parg_real("-b", npall, all_pa)); - } - - if (FF(PCA_CAN_END) && opt2parg_bSet("-e", npall, all_pa)) - { - setTimeValue(TEND, opt2parg_real("-e", npall, all_pa)); - } - - if (FF(PCA_CAN_DT) && opt2parg_bSet("-dt", npall, all_pa)) - { - setTimeValue(TDELTA, opt2parg_real("-dt", npall, all_pa)); - } - - /* clear memory */ - sfree(all_pa); - - if (!FF(PCA_NOEXIT_ON_ARGS)) - { - if (*argc > 1) + if (FF(PCA_CAN_END) && options.isSet("-e")) + { + setTimeValue(TEND, tend); + } + if (FF(PCA_CAN_DT) && options.isSet("-dt")) { - gmx_cmd(argv[1]); + setTimeValue(TDELTA, tdelta); } + + adapter.copyValues(!FF(PCA_NOT_READ_NODE)); + + return TRUE; } - return !bExit; + GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR; #undef FF } diff --git a/src/gromacs/commandline/tests/pargs.cpp b/src/gromacs/commandline/tests/pargs.cpp index ca95dd7db7..a27f3b98f4 100644 --- a/src/gromacs/commandline/tests/pargs.cpp +++ b/src/gromacs/commandline/tests/pargs.cpp @@ -323,8 +323,12 @@ TEST_F(ParseCommonArgsTest, ParsesFileArgs) EXPECT_STREQ("test.xvg", opt2fn_null("-o2", nfile(), fnm)); char **files; EXPECT_EQ(2, opt2fns(&files, "-om", nfile(), fnm)); - EXPECT_STREQ("test1.xvg", files[0]); - EXPECT_STREQ("test2.xvg", files[1]); + EXPECT_TRUE(files != NULL); + if (files != NULL) + { + EXPECT_STREQ("test1.xvg", files[0]); + EXPECT_STREQ("test2.xvg", files[1]); + } EXPECT_STREQ("outm2.xvg", opt2fn("-om2", nfile(), fnm)); done_filenms(nfile(), fnm); } diff --git a/src/gromacs/fileio/filenm.c b/src/gromacs/fileio/filenm.c index de8d84de1a..0b31d704f5 100644 --- a/src/gromacs/fileio/filenm.c +++ b/src/gromacs/fileio/filenm.c @@ -41,16 +41,14 @@ #endif #include -#include #include -#include "macros.h" -#include "types/commrec.h" +#include "gromacs/legacyheaders/macros.h" +#include "gromacs/legacyheaders/types/commrec.h" #include "gromacs/utility/basedefinitions.h" #include "gromacs/utility/cstringutil.h" #include "gromacs/utility/fatalerror.h" -#include "gromacs/utility/futil.h" #include "gromacs/utility/smalloc.h" /* XDR should be available on all platforms now, @@ -61,9 +59,6 @@ /* Use bitflag ... */ #define IS_SET(fn) ((fn.flag & ffSET) != 0) #define IS_OPT(fn) ((fn.flag & ffOPT) != 0) -#define IS_MULT(fn) ((fn.flag & ffMULT) != 0) -#define UN_SET(fn) (fn.flag = (fn.flag & ~ffSET)) -#define DO_SET(fn) (fn.flag = (fn.flag | ffSET)) enum { @@ -336,26 +331,15 @@ const char *ftp2defnm(int ftp) } } -static void check_opts(int nf, t_filenm fnm[]) +const char *ftp2defopt(int ftp) { - int i; - const t_deffile *df; - - for (i = 0; (i < nf); i++) + if ((0 <= ftp) && (ftp < efNR)) { - df = &(deffile[fnm[i].ftp]); - if (fnm[i].opt == NULL) - { - if (df->defopt == NULL) - { - gmx_fatal(FARGS, "No default cmd-line option for %s (type %d)\n", - deffile[fnm[i].ftp].ext, fnm[i].ftp); - } - else - { - fnm[i].opt = df->defopt; - } - } + return deffile[ftp].defopt; + } + else + { + return NULL; } } @@ -394,246 +378,6 @@ int fn2ftp(const char *fn) return i; } -static void set_extension(char *buf, int ftp) -{ - int len, extlen; - const t_deffile *df; - - /* check if extension is already at end of filename */ - df = &(deffile[ftp]); - len = strlen(buf); - extlen = strlen(df->ext); - if ((len <= extlen) || (gmx_strcasecmp(&(buf[len - extlen]), df->ext) != 0)) - { - strcat(buf, df->ext); - } -} - -static void add_filenm(t_filenm *fnm, const char *filenm) -{ - srenew(fnm->fns, fnm->nfiles+1); - fnm->fns[fnm->nfiles] = strdup(filenm); - fnm->nfiles++; -} - -static void set_grpfnm(t_filenm *fnm, const char *name, const char *deffnm) -{ - char buf[256], buf2[256]; - int i, type; - gmx_bool bValidExt; - int nopts; - const int *ftps; - - nopts = deffile[fnm->ftp].ntps; - ftps = deffile[fnm->ftp].tps; - if ((nopts == 0) || (ftps == NULL)) - { - gmx_fatal(FARGS, "nopts == 0 || ftps == NULL"); - } - - bValidExt = FALSE; - if (name && deffnm == NULL) - { - strcpy(buf, name); - /* First check whether we have a valid filename already */ - type = fn2ftp(name); - if ((fnm->flag & ffREAD) && (fnm->ftp == efTRX)) - { - /*if file exist don't add an extension for trajectory reading*/ - bValidExt = gmx_fexist(name); - } - for (i = 0; (i < nopts) && !bValidExt; i++) - { - if (type == ftps[i]) - { - bValidExt = TRUE; - } - } - } - else if (deffnm != NULL) - { - strcpy(buf, deffnm); - } - else - { - /* No name given, set the default name */ - strcpy(buf, ftp2defnm(fnm->ftp)); - } - - if (!bValidExt && (fnm->flag & ffREAD)) - { - /* for input-files only: search for filenames in the directory */ - for (i = 0; (i < nopts) && !bValidExt; i++) - { - type = ftps[i]; - strcpy(buf2, buf); - set_extension(buf2, type); - if (gmx_fexist(buf2)) - { - bValidExt = TRUE; - strcpy(buf, buf2); - } - } - } - - if (!bValidExt) - { - /* Use the first extension type */ - set_extension(buf, ftps[0]); - } - - add_filenm(fnm, buf); -} - -static void set_filenm(t_filenm *fnm, const char *name, const char *deffnm, - gmx_bool bReadNode) -{ - /* Set the default filename, extension and option for those fields that - * are not already set. An extension is added if not present, if fn = NULL - * or empty, the default filename is given. - */ - char buf[256]; - int i, len, extlen; - - if ((fnm->flag & ffREAD) && !bReadNode) - { - return; - } - - if ((fnm->ftp < 0) || (fnm->ftp >= efNR)) - { - gmx_fatal(FARGS, "file type out of range (%d)", fnm->ftp); - } - - if (name) - { - strcpy(buf, name); - } - if ((fnm->flag & ffREAD) && name && gmx_fexist(name)) - { - /* check if filename ends in .gz or .Z, if so remove that: */ - len = strlen(name); - for (i = 0; i < NZEXT; i++) - { - extlen = strlen(z_ext[i]); - if (len > extlen) - { - if (gmx_strcasecmp(name+len-extlen, z_ext[i]) == 0) - { - buf[len-extlen] = '\0'; - break; - } - } - } - } - - if (deffile[fnm->ftp].ntps) - { - set_grpfnm(fnm, name ? buf : NULL, deffnm); - } - else - { - if (name == NULL || deffnm != NULL) - { - if (deffnm != NULL) - { - strcpy(buf, deffnm); - } - else - { - strcpy(buf, ftp2defnm(fnm->ftp)); - } - } - set_extension(buf, fnm->ftp); - - add_filenm(fnm, buf); - } -} - -static void set_filenms(int nf, t_filenm fnm[], const char *deffnm, gmx_bool bReadNode) -{ - int i; - - for (i = 0; (i < nf); i++) - { - if (!IS_SET(fnm[i])) - { - set_filenm(&(fnm[i]), fnm[i].fn, deffnm, bReadNode); - } - } -} - -void parse_file_args(int *argc, char *argv[], int nf, t_filenm fnm[], - const char *deffnm, gmx_bool bReadNode) -{ - int i, j; - gmx_bool *bRemove; - - check_opts(nf, fnm); - - for (i = 0; (i < nf); i++) - { - UN_SET(fnm[i]); - } - - if (*argc > 1) - { - snew(bRemove, (*argc)+1); - i = 1; - do - { - for (j = 0; (j < nf); j++) - { - if (strcmp(argv[i], fnm[j].opt) == 0) - { - DO_SET(fnm[j]); - bRemove[i] = TRUE; - i++; - /* check if we are out of arguments for this option */ - if ((i >= *argc) || (argv[i][0] == '-')) - { - set_filenm(&fnm[j], fnm[j].fn, deffnm, bReadNode); - } - /* sweep up all file arguments for this option */ - while ((i < *argc) && (argv[i][0] != '-')) - { - set_filenm(&fnm[j], argv[i], NULL, bReadNode); - bRemove[i] = TRUE; - i++; - /* only repeat for 'multiple' file options: */ - if (!IS_MULT(fnm[j])) - { - break; - } - } - - break; /* jump out of 'j' loop */ - } - } - /* No file found corresponding to option argv[i] */ - if (j == nf) - { - i++; - } - } - while (i < *argc); - - /* Remove used entries */ - for (i = j = 0; (i <= *argc); i++) - { - if (!bRemove[i]) - { - argv[j++] = argv[i]; - } - } - (*argc) = j - 1; - sfree(bRemove); - } - - set_filenms(nf, fnm, deffnm, bReadNode); - -} - const char *opt2fn(const char *opt, int nfile, const t_filenm fnm[]) { int i; @@ -818,7 +562,7 @@ int add_suffix_to_output_names(t_filenm *fnm, int nfile, const char *suffix) extpos = strrchr(buf, '.'); *extpos = '\0'; sprintf(newname, "%s%s.%s", buf, suffix, extpos + 1); - free(fnm[i].fns[j]); + sfree(fnm[i].fns[j]); fnm[i].fns[j] = strdup(newname); } } diff --git a/src/gromacs/fileio/filenm.h b/src/gromacs/fileio/filenm.h index 0463c54a34..d5c1bd6816 100644 --- a/src/gromacs/fileio/filenm.h +++ b/src/gromacs/fileio/filenm.h @@ -115,14 +115,12 @@ const char *ftp2desc(int ftp); const char *ftp2defnm(int ftp); /* Return default file name for file type */ +const char *ftp2defopt(int ftp); +/* Return default option name for file type */ + const char *ftp2ftype(int ftp); /* Return Binary or ASCII depending on file type */ -void parse_file_args(int *argc, char *argv[], int nf, t_filenm fnm[], - const char *deffnm, gmx_bool bReadNode); -/* Parse command line for file names. When bKeep is set args are - * not removed from argv. */ - const char *opt2fn(const char *opt, int nfile, const t_filenm fnm[]); /* Return the filename belonging to cmd-line option opt, or NULL when * no such option. */ diff --git a/src/gromacs/options/filenameoption.cpp b/src/gromacs/options/filenameoption.cpp index 37d3a6a0cc..9922e16fa9 100644 --- a/src/gromacs/options/filenameoption.cpp +++ b/src/gromacs/options/filenameoption.cpp @@ -42,6 +42,8 @@ #include "filenameoption.h" #include "filenameoptionstorage.h" +#include + #include #include @@ -63,6 +65,10 @@ class FileTypeRegistry; //! \addtogroup module_options //! \{ +//! Extensions that are recognized as compressed files. +const char *const c_compressedExtensions[] = +{ ".gz", ".Z" }; + //! Shorthand for a list of file extensions. typedef std::vector ExtensionList; @@ -252,6 +258,15 @@ std::string completeFileName(const std::string &value, OptionFileType filetype, // TODO: This may not work as expected if the value is passed to a // function that uses fn2ftp() to determine the file type and the input // file has an unrecognized extension. + ConstArrayRef compressedExtensions(c_compressedExtensions); + ConstArrayRef::const_iterator ext; + for (ext = compressedExtensions.begin(); ext != compressedExtensions.end(); ++ext) + { + if (endsWith(value, *ext)) + { + return value.substr(0, value.length() - std::strlen(*ext)); + } + } return value; } const FileTypeRegistry ®istry = FileTypeRegistry::instance(); @@ -293,7 +308,7 @@ FileNameOptionStorage::FileNameOptionStorage(const FileNameOption &settings) completeFileName(settings.defaultBasename_, filetype_, legacyType_, false); setDefaultValueIfSet(defaultValue); - if (isRequired()) + if (isRequired() || settings.bLegacyOptionalBehavior_) { setDefaultValue(defaultValue); } diff --git a/src/gromacs/options/filenameoption.h b/src/gromacs/options/filenameoption.h index c00e71254d..45fd6fa1a9 100644 --- a/src/gromacs/options/filenameoption.h +++ b/src/gromacs/options/filenameoption.h @@ -73,7 +73,7 @@ class FileNameOption : public OptionTemplate //! Initializes an option with the given name. explicit FileNameOption(const char *name) : MyBase(name), filetype_(eftUnknown), legacyType_(-1), - defaultBasename_(NULL), + defaultBasename_(NULL), bLegacyOptionalBehavior_(false), bRead_(false), bWrite_(false), bLibrary_(false) { } @@ -93,6 +93,16 @@ class FileNameOption : public OptionTemplate */ MyClass &legacyType(int type) { legacyType_ = type; return me(); } + /*! \brief + * Changes the behavior of optional options to match old t_filenm. + * + * If this is not set, optional options return an empty string if not + * set. If this is set, a non-empty value is always returned. + * In the latter case, whether the option is set only affects the + * return value of OptionInfo::isSet() and Options::isSet(). + */ + MyClass &legacyOptionalBehavior() + { bLegacyOptionalBehavior_ = true; return me(); } //! Tells that the file provided by this option is used for input only. MyClass &inputFile() { bRead_ = true; bWrite_ = false; return me(); } @@ -160,6 +170,7 @@ class FileNameOption : public OptionTemplate OptionFileType filetype_; int legacyType_; const char *defaultBasename_; + bool bLegacyOptionalBehavior_; bool bRead_; bool bWrite_; bool bLibrary_; diff --git a/src/gromacs/options/timeunitmanager.cpp b/src/gromacs/options/timeunitmanager.cpp index ec2882d3cd..721a61c01d 100644 --- a/src/gromacs/options/timeunitmanager.cpp +++ b/src/gromacs/options/timeunitmanager.cpp @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2012,2013, by the GROMACS development team, led by + * Copyright (c) 2012,2013,2014, 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. @@ -41,10 +41,17 @@ */ #include "gromacs/options/timeunitmanager.h" +#include + +#include + #include "gromacs/options/basicoptions.h" #include "gromacs/options/options.h" #include "gromacs/options/optionsvisitor.h" +#include "gromacs/utility/arrayref.h" +#include "gromacs/utility/exceptions.h" #include "gromacs/utility/gmxassert.h" +#include "gromacs/utility/stringutil.h" namespace { @@ -108,6 +115,27 @@ double TimeUnitManager::inverseTimeScaleFactor() const return 1.0 / timeScaleFactor(); } +void TimeUnitManager::setTimeUnitFromEnvironment() +{ + const char *const value = std::getenv("GMXTIMEUNIT"); + if (value != NULL) + { + ConstArrayRef timeUnits(g_timeUnits); + ConstArrayRef::const_iterator i = + std::find(timeUnits.begin(), timeUnits.end(), std::string(value)); + if (i == timeUnits.end()) + { + std::string message = formatString( + "Time unit provided with environment variable GMXTIMEUNIT=%s " + "is not recognized as a valid time unit.\n" + "Possible values are: %s", + value, joinStrings(timeUnits, ", ").c_str()); + GMX_THROW(InvalidInputError(message)); + } + timeUnit_ = i - timeUnits.begin(); + } +} + void TimeUnitManager::addTimeUnitOption(Options *options, const char *name) { options->addOption(StringOption(name).enumValue(g_timeUnits) diff --git a/src/gromacs/options/timeunitmanager.h b/src/gromacs/options/timeunitmanager.h index 34f11d97fa..7ad7fe3243 100644 --- a/src/gromacs/options/timeunitmanager.h +++ b/src/gromacs/options/timeunitmanager.h @@ -1,7 +1,7 @@ /* * This file is part of the GROMACS molecular simulation package. * - * Copyright (c) 2012, by the GROMACS development team, led by + * Copyright (c) 2012,2014, 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. @@ -117,6 +117,10 @@ class TimeUnitManager //! Returns the scaling factor to convert times from ps. double inverseTimeScaleFactor() const; + /*! \brief + * Sets the time unit in this manager from an environment variable. + */ + void setTimeUnitFromEnvironment(); /*! \brief * Adds a common option for selecting the time unit. *