Shell completions through Options
authorTeemu Murtola <teemu.murtola@gmail.com>
Fri, 7 Feb 2014 18:52:53 +0000 (20:52 +0200)
committerTeemu Murtola <teemu.murtola@gmail.com>
Sun, 9 Feb 2014 17:32:23 +0000 (19:32 +0200)
- Implement shell completion generation for command line options
  specified through an Options object.
- Use this support to generate the list of options for the wrapper
  binary instead of hardcoding them.
- Use this support and the conversion from t_pargs/t_filenm to Options
  to generate the existing completions.  The only differences in the
  generated completions are in the order of the options and in changing
  "$n == 1" to "$n <= 1" and ".@(xtc|trr|...)" to "@(.xtc|.trr|...)".
- Extend some of the options to expose information necessary for this.

Related to #969 and #1410.

Change-Id: Ib77543367c38803ef186f6024a1af14feb806d80

src/gromacs/commandline/cmdlinehelpmodule.cpp
src/gromacs/commandline/pargs.cpp
src/gromacs/commandline/shellcompletions.cpp
src/gromacs/commandline/shellcompletions.h
src/gromacs/options/basicoptions.cpp
src/gromacs/options/basicoptions.h
src/gromacs/options/basicoptionstorage.h
src/gromacs/options/filenameoption.cpp
src/gromacs/options/filenameoption.h
src/gromacs/options/filenameoptionstorage.h
src/gromacs/utility/stringutil.h

index 0e3f43cec764e80e618ec51f69af8a93969f6bba..d76bbb4ef03cfb6cdc97861889a89e176b2ba26a 100644 (file)
@@ -704,7 +704,9 @@ void HelpExportCompletion::exportModuleHelp(
 
 void HelpExportCompletion::finishModuleExport()
 {
-    bashWriter_.writeWrapperCompletions(modules_);
+    CommandLineCommonOptionsHolder optionsHolder;
+    optionsHolder.initOptions();
+    bashWriter_.writeWrapperCompletions(modules_, *optionsHolder.options());
     bashWriter_.finishCompletions();
 }
 
index 9ffbf88939f7cfe08850a91eb887a67251c19943..f1fa5ae42f1553af184e0f3053154a6e853ba31f 100644 (file)
@@ -829,29 +829,21 @@ gmx_bool parse_common_args(int *argc, char *argv[], unsigned long Flags,
         bExit = (context != NULL);
         if (context != NULL && !(FF(PCA_QUIET)))
         {
-            if (context->isCompletionExport())
+            gmx::Options options(NULL, NULL);
+            options.setDescription(gmx::ConstArrayRef<const char *>(desc, ndesc));
+            for (i = 0; i < nfile; i++)
             {
-                context->shellCompletionWriter().writeLegacyModuleCompletions(
-                        context->moduleDisplayName(), nfile, fnm, npall, all_pa);
+                gmx::filenmToOptions(&options, &fnm[i]);
             }
-            else
+            for (i = 0; i < npall; i++)
             {
-                gmx::Options options(NULL, NULL);
-                options.setDescription(gmx::ConstArrayRef<const char *>(desc, ndesc));
-                for (i = 0; i < nfile; i++)
-                {
-                    gmx::filenmToOptions(&options, &fnm[i]);
-                }
-                for (i = 0; i < npall; i++)
-                {
-                    gmx::pargsToOptions(&options, &all_pa[i]);
-                }
-                gmx::CommandLineHelpWriter(options)
-                    .setShowDescriptions(true)
-                    .setTimeUnitString(output_env_get_time_unit(*oenv))
-                    .setKnownIssues(gmx::ConstArrayRef<const char *>(bugs, nbugs))
-                    .writeHelp(*context);
+                gmx::pargsToOptions(&options, &all_pa[i]);
             }
+            gmx::CommandLineHelpWriter(options)
+                .setShowDescriptions(true)
+                .setTimeUnitString(output_env_get_time_unit(*oenv))
+                .setKnownIssues(gmx::ConstArrayRef<const char *>(bugs, nbugs))
+                .writeHelp(*context);
         }
     }
     GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
index 9697a5cbfe364d75a97815100d90c791cb373a19..150f03426c224c607848ccb725d8d6f00cc7a94a 100644 (file)
 
 #include "gromacs/commandline/cmdlinehelpcontext.h"
 #include "gromacs/commandline/pargs.h"
+#include "gromacs/options/basicoptions.h"
+#include "gromacs/options/filenameoption.h"
+#include "gromacs/options/optionsvisitor.h"
 #include "gromacs/fileio/filenm.h"
+#include "gromacs/utility/arrayref.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/file.h"
 #include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/stringutil.h"
 
-#include "gromacs/legacyheaders/smalloc.h"
+namespace gmx
+{
 
-// TODO: Don't duplicate this from filenm.c/futil.c.
-#define NZEXT 2
-static const char *z_ext[NZEXT] = { ".gz", ".Z" };
+namespace
+{
 
-static void pr_fopts(FILE *fp, int nf, const t_filenm tfn[])
+class OptionsListWriter : public OptionsVisitor
 {
-    for (int i = 0; i < nf; i++)
-    {
-        const int   ftp          = tfn[i].ftp;
-        const char *multiplicity = "(( $n == 1 )) && ";
-        if (tfn[i].flag & ffMULT)
-        {
-            multiplicity = "";
-        }
-        if (ftp == efRND)
+    public:
+        const std::string &optionList() const { return optionList_; }
+
+        virtual void visitSubSection(const Options &section)
         {
-            fprintf(fp, "%s) %sCOMPREPLY=( $(compgen -S ' ' -d $c) );;\n",
-                    tfn[i].opt, multiplicity);
-            continue;
+            OptionsIterator iterator(section);
+            iterator.acceptSubSections(this);
+            iterator.acceptOptions(this);
         }
-        fprintf(fp, "%s) %sCOMPREPLY=( $(compgen -S ' ' -X '!*.",
-                tfn[i].opt, multiplicity);
-        const int genericCount = ftp2generic_count(ftp);
-        if (genericCount > 0)
+        virtual void visitOption(const OptionInfo &option)
         {
-            fprintf(fp, "@(");
-            const int *const genericTypes = ftp2generic_list(ftp);
-            for (int j = 0; j < genericCount; j++)
+            if (option.isHidden())
             {
-                if (j > 0)
-                {
-                    fprintf(fp, "|");
-                }
-                fprintf(fp, "%s", ftp2ext(genericTypes[j]));
+                return;
             }
-            fprintf(fp, ")");
-        }
-        else
-        {
-            fprintf(fp, "%s", ftp2ext(ftp));
-        }
-        fprintf(fp, "?(");
-        for (int j = 0; j < NZEXT; j++)
-        {
-            if (j > 0)
+            if (!optionList_.empty())
+            {
+                optionList_.append("\\n");
+            }
+            optionList_.append("-");
+            const BooleanOptionInfo *booleanOption
+                = option.toType<BooleanOptionInfo>();
+            if (booleanOption != NULL && booleanOption->defaultValue())
             {
-                fprintf(fp, "|");
+                optionList_.append("no");
             }
-            fprintf(fp, "%s", z_ext[j]);
+            optionList_.append(option.name());
         }
-        fprintf(fp, ")' -f $c ; compgen -S '/' -d $c ));;\n");
-    }
-}
 
-static void pr_opts(FILE *fp,
-                    int nfile,  t_filenm *fnm,
-                    int npargs, t_pargs pa[])
+    private:
+        std::string optionList_;
+};
+
+class OptionCompletionWriter : public OptionsVisitor
+{
+    public:
+        explicit OptionCompletionWriter(File *out) : out_(*out) {}
+
+        virtual void visitSubSection(const Options &section)
+        {
+            OptionsIterator iterator(section);
+            iterator.acceptSubSections(this);
+            iterator.acceptOptions(this);
+        }
+        virtual void visitOption(const OptionInfo &option);
+
+    private:
+        void writeOptionCompletion(const OptionInfo  &option,
+                                   const std::string &completion);
+
+        File &out_;
+};
+
+void OptionCompletionWriter::visitOption(const OptionInfo &option)
 {
-    fprintf(fp, "if (( $COMP_CWORD <= 1 )) || [[ $c == -* ]]; then COMPREPLY=( $(compgen -S ' '  -W $'");
-    const char *sep = "";
-    for (int i = 0; i < nfile; i++)
+    if (option.isHidden())
     {
-        fprintf(fp, "%s-%s", sep, fnm[i].opt+1);
-        sep = "\\n";
+        return;
     }
-    for (int i = 0; i < npargs; i++)
+    const FileNameOptionInfo *fileOption = option.toType<FileNameOptionInfo>();
+    if (fileOption != NULL)
     {
-        if (pa[i].type == etBOOL && *(pa[i].u.b))
+        if (fileOption->isDirectoryOption())
         {
-            fprintf(fp, "%s-no%s", sep, pa[i].option + 1);
+            writeOptionCompletion(option, "compgen -S ' ' -d $c");
+            return;
         }
-        else
+        const FileNameOptionInfo::ExtensionList &extensionList = fileOption->extensions();
+        if (extensionList.empty())
         {
-            fprintf(fp, "%s-%s", sep, pa[i].option + 1);
+            return;
         }
-        sep = "\\n";
+        std::string completion("compgen -S ' ' -X '!*");
+        std::string extensions(joinStrings(extensionList, "|"));
+        if (extensionList.size() > 1)
+        {
+            extensions = "@(" + extensions + ")";
+        }
+        completion.append(extensions);
+        // TODO: Don't duplicate this from filenm.c/futil.c.
+        completion.append("?(.gz|.Z)' -f -- $c ; compgen -S '/' -d $c");
+        writeOptionCompletion(option, completion);
+        return;
+    }
+    const StringOptionInfo *stringOption = option.toType<StringOptionInfo>();
+    if (stringOption != NULL && stringOption->isEnumerated())
+    {
+        std::string completion("compgen -S ' ' -W $'");
+        completion.append(joinStrings(stringOption->allowedValues(), "\\n"));
+        completion.append("' -- $c");
+        writeOptionCompletion(option, completion);
+        return;
     }
-    fprintf(fp, "' -- $c)); return 0; fi\n");
 }
 
-static void pr_enums(FILE *fp, int npargs, t_pargs pa[])
+void OptionCompletionWriter::writeOptionCompletion(
+        const OptionInfo &option, const std::string &completion)
 {
-    for (int i = 0; i < npargs; i++)
+    std::string result(formatString("-%s) ", option.name().c_str()));
+    if (option.maxValueCount() >= 0)
     {
-        if (pa[i].type == etENUM)
-        {
-            fprintf(fp, "%s) (( $n == 1 )) && COMPREPLY=( $(compgen -S ' ' -W $'", pa[i].option);
-            for (int j = 1; pa[i].u.c[j]; j++)
-            {
-                fprintf(fp, "%s%s", (j == 1 ? "" : "\\n"), pa[i].u.c[j]);
-            }
-            fprintf(fp, "' -- $c ));;\n");
-        }
+        result.append(formatString("(( $n <= %d )) && ", option.maxValueCount()));
     }
+    result.append("COMPREPLY=( $(");
+    result.append(completion);
+    result.append("));;");
+    out_.writeLine(result);
 }
 
-static void write_bashcompl(FILE *out,
-                            const char *funcName,
-                            int nfile,  t_filenm *fnm,
-                            int npargs, t_pargs *pa)
-{
-    /* Advanced bash completions are handled by shell functions.
-     * p and c hold the previous and current word on the command line.
-     */
-    fprintf(out, "%s() {\n", funcName);
-    fprintf(out, "local IFS=$'\\n'\n");
-    fprintf(out, "local c=${COMP_WORDS[COMP_CWORD]}\n");
-    fprintf(out, "local n\n");
-    fprintf(out, "for ((n=1;n<COMP_CWORD;++n)) ; do [[ \"${COMP_WORDS[COMP_CWORD-n]}\" == -* ]] && break ; done\n");
-    fprintf(out, "local p=${COMP_WORDS[COMP_CWORD-n]}\n");
-    fprintf(out, "COMPREPLY=()\n");
-
-    pr_opts(out, nfile, fnm, npargs, pa);
-    fprintf(out, "case \"$p\" in\n");
-
-    pr_enums(out, npargs, pa);
-    pr_fopts(out, nfile, fnm);
-    fprintf(out, "esac }\n");
-}
-
-namespace gmx
-{
+}   // namespace
 
 class ShellCompletionWriter::Impl
 {
@@ -214,44 +212,31 @@ void ShellCompletionWriter::startCompletions()
     impl_->file_->writeLine("shopt -s extglob");
 }
 
-void ShellCompletionWriter::writeLegacyModuleCompletions(
-        const char *moduleName,
-        int nfile,  t_filenm *fnm,
-        int npargs, t_pargs *pa)
-{
-    int      npar;
-    t_pargs *par;
-
-    // Remove hidden arguments.
-    snew(par, npargs);
-    npar = 0;
-    for (int i = 0; i < npargs; i++)
-    {
-        if (!is_hidden(&pa[i]))
-        {
-            par[npar] = pa[i];
-            npar++;
-        }
-    }
-
-    write_bashcompl(impl_->file_->handle(),
-                    impl_->completionFunctionName(moduleName).c_str(),
-                    nfile, fnm, npar, par);
-
-    sfree(par);
-}
-
 void ShellCompletionWriter::writeModuleCompletions(
         const char    *moduleName,
-        const Options  & /*options*/)
+        const Options &options)
 {
-    // TODO: Implement.
-    impl_->file_->writeLine(
-            impl_->completionFunctionName(moduleName) + "() {\nCOMPREPLY=()\n}\n");
+    File &out = *impl_->file_;
+    out.writeLine(formatString("%s() {", impl_->completionFunctionName(moduleName).c_str()));
+    out.writeLine("local IFS=$'\\n'");
+    out.writeLine("local c=${COMP_WORDS[COMP_CWORD]}");
+    out.writeLine("local n");
+    out.writeLine("for ((n=1;n<COMP_CWORD;++n)) ; do [[ \"${COMP_WORDS[COMP_CWORD-n]}\" == -* ]] && break ; done");
+    out.writeLine("local p=${COMP_WORDS[COMP_CWORD-n]}");
+    out.writeLine("COMPREPLY=()");
+
+    OptionsListWriter listWriter;
+    listWriter.visitSubSection(options);
+    out.writeLine(formatString("if (( $COMP_CWORD <= 1 )) || [[ $c == -* ]]; then COMPREPLY=( $(compgen -S ' '  -W $'%s' -- $c)); return 0; fi", listWriter.optionList().c_str()));
+
+    out.writeLine("case \"$p\" in");
+    OptionCompletionWriter optionWriter(&out);
+    optionWriter.visitSubSection(options);
+    out.writeLine("esac }");
 }
 
 void ShellCompletionWriter::writeWrapperCompletions(
-        const ModuleNameList &modules)
+        const ModuleNameList &modules, const Options &options)
 {
     impl_->file_->writeLine("_" + impl_->binaryName_ + "_compl() {");
     impl_->file_->writeLine("local i c m");
@@ -264,8 +249,9 @@ void ShellCompletionWriter::writeWrapperCompletions(
     impl_->file_->writeLine("done");
     impl_->file_->writeLine("if (( i == COMP_CWORD )); then");
     impl_->file_->writeLine("c=${COMP_WORDS[COMP_CWORD]}");
-    // TODO: Get rid of these hard-coded options.
-    std::string completions("-h\\n-quiet\\n-version\\n-nocopyright");
+    OptionsListWriter lister;
+    lister.visitSubSection(options);
+    std::string       completions(lister.optionList());
     for (ModuleNameList::const_iterator i = modules.begin();
          i != modules.end(); ++i)
     {
index a1088d2a58a0852ad56f6e9948af9cf1a5c327cb..c8855305cc95b6f0a0212854f6ac8992edd97218 100644 (file)
@@ -46,8 +46,6 @@
 #include <string>
 #include <vector>
 
-#include "gromacs/commandline/pargs.h"
-#include "gromacs/fileio/filenm.h"
 #include "gromacs/utility/common.h"
 
 namespace gmx
@@ -82,12 +80,10 @@ class ShellCompletionWriter
         File *outputFile();
 
         void startCompletions();
-        void writeLegacyModuleCompletions(const char *moduleName,
-                                          int nfile,  t_filenm *fnm,
-                                          int npargs, t_pargs *pa);
         void writeModuleCompletions(const char    *moduleName,
                                     const Options &options);
-        void writeWrapperCompletions(const ModuleNameList &modules);
+        void writeWrapperCompletions(const ModuleNameList &modules,
+                                     const Options        &options);
         void finishCompletions();
 
     private:
index a7611e740ba5271cabcbf60debff28c49e48ccf2..692a3e62630ae5eab50ef562a49d701c9a3eadee 100644 (file)
@@ -125,6 +125,16 @@ BooleanOptionInfo::BooleanOptionInfo(BooleanOptionStorage *option)
 {
 }
 
+const BooleanOptionStorage &BooleanOptionInfo::option() const
+{
+    return static_cast<const BooleanOptionStorage &>(OptionInfo::option());
+}
+
+bool BooleanOptionInfo::defaultValue() const
+{
+    return option().defaultValue();
+}
+
 /********************************************************************
  * BooleanOption
  */
index bef6e44ccba5bc03992c6f52dff3a1d822c70cfc..e733ad876019ac37cf8d3fc9d3ac9d0b52a78883 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2010,2011,2012,2013, by the GROMACS development team, led by
+ * Copyright (c) 2010,2011,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.
@@ -396,6 +396,12 @@ class BooleanOptionInfo : public OptionInfo
     public:
         //! Creates an option info object for the given option.
         explicit BooleanOptionInfo(BooleanOptionStorage *option);
+
+        //! Returns the default value for this option.
+        bool defaultValue() const;
+
+    private:
+        const BooleanOptionStorage &option() const;
 };
 
 /*! \brief
index 2d674335f3b7db9f792c87ba1327eb959e2a947c..02529cd7a224c8cd665d300beca7034cc8134da7 100644 (file)
@@ -75,6 +75,9 @@ class BooleanOptionStorage : public OptionStorageTemplate<bool>
         virtual std::string typeString() const { return "bool"; }
         virtual std::string formatSingleValue(const bool &value) const;
 
+        //! \copydoc BooleanOptionInfo::defaultValue()
+        bool defaultValue() const { return valueCount() > 0 && values()[0]; }
+
     private:
         virtual void convertValue(const std::string &value);
 
index 6ba1e370fc44d0d80bc2ac24962851b873f9cac1..4bc2810ba960958a41638e2cd376e55bc84bf62b 100644 (file)
@@ -47,6 +47,7 @@
 
 #include "gromacs/fileio/filenm.h"
 
+#include "gromacs/utility/arrayref.h"
 #include "gromacs/utility/file.h"
 #include "gromacs/utility/stringutil.h"
 
@@ -360,6 +361,19 @@ void FileNameOptionStorage::convertValue(const std::string &value)
     addValue(completeFileName(value, filetype_, legacyType_, bInput));
 }
 
+bool FileNameOptionStorage::isDirectoryOption() const
+{
+    return legacyType_ == efRND;
+}
+
+ConstArrayRef<const char *> FileNameOptionStorage::extensions() const
+{
+    const FileTypeRegistry &registry    = FileTypeRegistry::instance();
+    const FileTypeHandler  &typeHandler = registry.handlerForType(filetype_, legacyType_);
+    const ExtensionList    &extensions  = typeHandler.extensions();
+    return ConstArrayRef<const char *>(extensions.begin(), extensions.end());
+}
+
 /********************************************************************
  * FileNameOptionInfo
  */
@@ -394,6 +408,16 @@ bool FileNameOptionInfo::isLibraryFile() const
     return option().isLibraryFile();
 }
 
+bool FileNameOptionInfo::isDirectoryOption() const
+{
+    return option().isDirectoryOption();
+}
+
+FileNameOptionInfo::ExtensionList FileNameOptionInfo::extensions() const
+{
+    return option().extensions();
+}
+
 /********************************************************************
  * FileNameOption
  */
index 3896f3a0b7028b3bb5c995bb6b871723089bdadc..21e48aa7a54fb2b60206fb94a0005a71acc34de9 100644 (file)
@@ -51,6 +51,7 @@
 namespace gmx
 {
 
+template <typename T> class ConstArrayRef;
 class FileNameOptionInfo;
 class FileNameOptionStorage;
 
@@ -165,6 +166,9 @@ class FileNameOption : public OptionTemplate<std::string, FileNameOption>
 class FileNameOptionInfo : public OptionInfo
 {
     public:
+        //! Shorthand for a list of extensions.
+        typedef ConstArrayRef<const char *> ExtensionList;
+
         //! Creates an option info object for the given option.
         explicit FileNameOptionInfo(FileNameOptionStorage *option);
 
@@ -181,6 +185,11 @@ class FileNameOptionInfo : public OptionInfo
          */
         bool isLibraryFile() const;
 
+        //! Whether the option specifies directories.
+        bool isDirectoryOption() const;
+        //! Returns the list of extensions this option accepts.
+        ExtensionList extensions() const;
+
     private:
         const FileNameOptionStorage &option() const;
 };
index 0d439f66263c12de20053d0a9c867efa10cb65c9..f5f94c60a19e6a3203a36af70b0dc3ce118ad019 100644 (file)
@@ -76,6 +76,11 @@ class FileNameOptionStorage : public OptionStorageTemplate<std::string>
         //! \copydoc FileNameOptionInfo::isLibraryFile()
         bool isLibraryFile() const { return bLibrary_; }
 
+        //! \copydoc FileNameOptionInfo::isDirectoryOption()
+        bool isDirectoryOption() const;
+        //! \copydoc FileNameOptionInfo::extensions()
+        ConstArrayRef<const char *> extensions() const;
+
     private:
         virtual void convertValue(const std::string &value);
 
index 56757e1402c13039ac60099b81047be718ea343e..511b71e99b46cc7a694db26788537b470f982c8e 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2011,2012,2013, by the GROMACS development team, led by
+ * Copyright (c) 2011,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.
@@ -110,6 +110,45 @@ std::string stripSuffixIfPresent(const std::string &str, const char *suffix);
  */
 std::string formatString(const char *fmt, ...);
 
+/*! \brief
+ * Joins strings from a range with a separator in between.
+ *
+ * \param[in] begin      Iterator the beginning of the range to join.
+ * \param[in] end        Iterator the end of the range to join.
+ * \param[in] separator  String to put in between the joined strings.
+ * \returns   All strings from (`begin`, `end`) concatenated with `separator`
+ *     between each pair.
+ * \throws    std::bad_alloc if out of memory.
+ */
+template <typename InputIterator>
+std::string joinStrings(InputIterator begin, InputIterator end,
+                        const char *separator)
+{
+    std::string result;
+    const char *currentSeparator = "";
+    for (InputIterator i = begin; i != end; ++i)
+    {
+        result.append(currentSeparator);
+        result.append(*i);
+        currentSeparator = separator;
+    }
+    return result;
+}
+/*! \brief
+ * Joins strings from a container with a separator in between.
+ *
+ * \param[in] container  Strings to join.
+ * \param[in] separator  String to put in between the joined strings.
+ * \returns   All strings from `container` concatenated with `separator`
+ *     between each pair.
+ * \throws    std::bad_alloc if out of memory.
+ */
+template <typename ContainerType>
+std::string joinStrings(const ContainerType &container, const char *separator)
+{
+    return joinStrings(container.begin(), container.end(), separator);
+}
+
 /*! \brief
  * Joins strings in an array to a single string.
  *