Replace all command line parsing with Options
authorTeemu Murtola <teemu.murtola@gmail.com>
Tue, 6 May 2014 03:32:47 +0000 (06:32 +0300)
committerDavid van der Spoel <davidvanderspoel@gmail.com>
Sat, 7 Jun 2014 12:49:11 +0000 (14:49 +0200)
- 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

src/gromacs/commandline/pargs.cpp
src/gromacs/commandline/tests/pargs.cpp
src/gromacs/fileio/filenm.c
src/gromacs/fileio/filenm.h
src/gromacs/options/filenameoption.cpp
src/gromacs/options/filenameoption.h
src/gromacs/options/timeunitmanager.cpp
src/gromacs/options/timeunitmanager.h

index 6b240668b872dc400e2f4c509059399ba231c7ef..151c3b9e504e6b8f5d3a818753844eb32ba76e1a 100644 (file)
 #include <cstdlib>
 #include <cstring>
 
+#include <algorithm>
+#include <list>
+
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #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"
 /* 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<const char *> 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 char *>::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<std::string>  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<const char *>    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<FileNameData>      fileNameOptions_;
+        std::list<ProgramArgData>    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<FileNameData>::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<ProgramArgData>::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 char *>::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<const char *>(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<int>(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<const char *>(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<const char *>(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<const char *>(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
 }
index ca95dd7db7e4e1e0f3147b7905e2a89a33911df5..a27f3b98f4e90a107356e23778416b63895e1df6 100644 (file)
@@ -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);
 }
index de8d84de1ad1a49203b5d938727a41806710e686..0b31d704f5cc639ba6fea5fde6d9d9a0b7973c41 100644 (file)
 #endif
 
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 
-#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);
             }
         }
index 0463c54a34fac52afec7c04d906f2d36f2f09397..d5c1bd68164191970a6dedd2dc4c7c425b3df3e7 100644 (file)
@@ -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. */
index 37d3a6a0ccc0036a1bfc95e00c8b5d5035dad0c8..9922e16fa977b6a2b3fc27bb7961745ac3324ee9 100644 (file)
@@ -42,6 +42,8 @@
 #include "filenameoption.h"
 #include "filenameoptionstorage.h"
 
+#include <cstring>
+
 #include <string>
 #include <vector>
 
@@ -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<const char *> 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<const char *>                 compressedExtensions(c_compressedExtensions);
+        ConstArrayRef<const char *>::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 &registry    = 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);
         }
index c00e71254d4120e357d140acdd7f55e06c20ecfa..45fd6fa1a9abab5fbf6b22d0686f11f41a16c0be 100644 (file)
@@ -73,7 +73,7 @@ class FileNameOption : public OptionTemplate<std::string, FileNameOption>
         //! 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<std::string, FileNameOption>
          */
         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<std::string, FileNameOption>
         OptionFileType          filetype_;
         int                     legacyType_;
         const char             *defaultBasename_;
+        bool                    bLegacyOptionalBehavior_;
         bool                    bRead_;
         bool                    bWrite_;
         bool                    bLibrary_;
index ec2882d3cd2e7b2c67e3caf9728dc978541ddf74..721a61c01d84b1d1dcc6cb6ab6f5bd57d21c7d23 100644 (file)
@@ -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.
  */
 #include "gromacs/options/timeunitmanager.h"
 
+#include <cstdlib>
+
+#include <algorithm>
+
 #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<const char *>                 timeUnits(g_timeUnits);
+        ConstArrayRef<const char *>::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)
index 34f11d97fa1bb40819054f48264c0a951ced1a50..7ad7fe3243a56dcf88d11eee5d4699d0b0befd68 100644 (file)
@@ -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.
          *