Sort all includes in src/gromacs
[alexxy/gromacs.git] / src / gromacs / commandline / shellcompletions.cpp
index 002ff60d590c20d73d49d4734ae6fe58feda18ff..1eefeda1cf6fd282965b63511aab391030368ca4 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
  * Copyright (c) 2001-2004, The GROMACS development team.
- * Copyright (c) 2013, by the GROMACS development team, led by
+ * Copyright (c) 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.
  * To help us fund GROMACS development, we humbly ask that you cite
  * the research papers on the package. Check out http://www.gromacs.org.
  */
-#include "gromacs/commandline/shellcompletions.h"
+/*! \internal \file
+ * \brief
+ * Implements gmx::ShellCompletionWriter.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \ingroup module_commandline
+ */
+#include "gmxpre.h"
+
+#include "shellcompletions.h"
 
 #include <cstdio>
-#include <cstring>
 
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+
+#include "gromacs/commandline/cmdlinehelpcontext.h"
 #include "gromacs/commandline/pargs.h"
 #include "gromacs/fileio/filenm.h"
-#include "gromacs/fileio/gmxfio.h"
+#include "gromacs/options/basicoptions.h"
+#include "gromacs/options/filenameoption.h"
+#include "gromacs/options/optionsvisitor.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/copyrite.h"
-#include "gromacs/legacyheaders/smalloc.h"
-
-// Shell types for completion.
-enum {
-    eshellCSH, eshellBASH, eshellZSH
-};
+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[], int shell)
+class OptionsListWriter : public OptionsVisitor
 {
-    switch (shell)
-    {
-        case eshellCSH:
-            for (int i = 0; i < nf; i++)
+    public:
+        const std::string &optionList() const { return optionList_; }
+
+        virtual void visitSubSection(const Options &section)
+        {
+            OptionsIterator iterator(section);
+            iterator.acceptSubSections(this);
+            iterator.acceptOptions(this);
+        }
+        virtual void visitOption(const OptionInfo &option)
+        {
+            if (option.isHidden())
             {
-                fprintf(fp, " \"n/%s/f:*.", tfn[i].opt);
-                const int ftp          = tfn[i].ftp;
-                const int genericCount = ftp2generic_count(ftp);
-                if (genericCount > 0)
-                {
-                    fprintf(fp, "{");
-                    const int *const genericTypes = ftp2generic_list(ftp);
-                    for (int j = 0; j < genericCount; j++)
-                    {
-                        if (j > 0)
-                        {
-                            fprintf(fp, ",");
-                        }
-                        fprintf(fp, "%s", ftp2ext(genericTypes[j]));
-                    }
-                    fprintf(fp, "}");
-                }
-                else
-                {
-                    fprintf(fp, "%s", ftp2ext(ftp));
-                }
-                fprintf(fp, "{");
-                for (int j = 0; j < NZEXT; j++)
-                {
-                    fprintf(fp, ",%s", z_ext[j]);
-                }
-                fprintf(fp, "}/\"");
+                return;
             }
-            break;
-        case eshellBASH:
-            for (int i = 0; i < nf; i++)
+            if (!optionList_.empty())
             {
-                fprintf(fp, "%s) COMPREPLY=( $(compgen -X '!*.", tfn[i].opt);
-                const int ftp          = tfn[i].ftp;
-                const int genericCount = ftp2generic_count(ftp);
-                if (genericCount > 0)
-                {
-                    fprintf(fp, "+(");
-                    const int *const genericTypes = ftp2generic_list(ftp);
-                    for (int j = 0; j < genericCount; j++)
-                    {
-                        if (j > 0)
-                        {
-                            fprintf(fp, "|");
-                        }
-                        fprintf(fp, "%s", ftp2ext(genericTypes[j]));
-                    }
-                    fprintf(fp, ")");
-                }
-                else
-                {
-                    fprintf(fp, "%s", ftp2ext(ftp));
-                }
-                fprintf(fp, "*(");
-                for (int j = 0; j < NZEXT; j++)
-                {
-                    if (j > 0)
-                    {
-                        fprintf(fp, "|");
-                    }
-                    fprintf(fp, "%s", z_ext[j]);
-                }
-                fprintf(fp, ")' -f $c ; compgen -S '/' -X '.*' -d $c ));;\n");
+                optionList_.append("\\n");
             }
-            break;
-        case eshellZSH:
-            for (int i = 0; i < nf; i++)
+            optionList_.append("-");
+            const BooleanOptionInfo *booleanOption
+                = option.toType<BooleanOptionInfo>();
+            if (booleanOption != NULL && booleanOption->defaultValue())
             {
-                fprintf(fp, "- 'c[-1,%s]' -g '*.", tfn[i].opt);
-                const int ftp          = tfn[i].ftp;
-                const int genericCount = ftp2generic_count(ftp);
-                if (genericCount > 0)
-                {
-                    fprintf(fp, "(");
-                    const int *const genericTypes = ftp2generic_list(ftp);
-                    for (int j = 0; j < genericCount; j++)
-                    {
-                        if (j > 0)
-                        {
-                            fprintf(fp, "|");
-                        }
-                        fprintf(fp, "%s", ftp2ext(genericTypes[j]));
-                    }
-                    fprintf(fp, ")");
-                }
-                else
-                {
-                    fprintf(fp, "%s", ftp2ext(ftp));
-                }
-                fprintf(fp, "(");
-                for (int j = 0; j < NZEXT; j++)
-                {
-                    fprintf(fp, "|%s", z_ext[j]);
-                }
-                fprintf(fp, ") *(/)' ");
+                optionList_.append("no");
             }
-            break;
-    }
-}
+            optionList_.append(option.name());
+        }
+
+    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);
 
-static void pr_opts(FILE *fp,
-                    int nfile,  t_filenm *fnm,
-                    int npargs, t_pargs pa[], int shell)
+        File &out_;
+};
+
+void OptionCompletionWriter::visitOption(const OptionInfo &option)
 {
-    switch (shell)
+    if (option.isHidden())
     {
-        case eshellCSH:
-            fprintf(fp, " \"c/-/(");
-            for (int i = 0; i < nfile; i++)
-            {
-                fprintf(fp, " %s", fnm[i].opt+1);
-            }
-            for (int i = 0; i < npargs; i++)
-            {
-                if (pa[i].type == etBOOL && *(pa[i].u.b))
-                {
-                    fprintf(fp, " no%s", pa[i].option+1);
-                }
-                else
-                {
-                    fprintf(fp, " %s", pa[i].option+1);
-                }
-            }
-            fprintf(fp, ")/\"");
-            break;
-        case eshellBASH:
-            fprintf(fp, "if (( $COMP_CWORD <= 1 )) || [[ $c == -* ]]; then COMPREPLY=( $(compgen  -W '");
-            for (int i = 0; i < nfile; i++)
-            {
-                fprintf(fp, " -%s", fnm[i].opt+1);
-            }
-            for (int i = 0; i < npargs; i++)
-            {
-                if (pa[i].type == etBOOL && *(pa[i].u.b))
-                {
-                    fprintf(fp, " -no%s", pa[i].option+1);
-                }
-                else
-                {
-                    fprintf(fp, " -%s", pa[i].option+1);
-                }
-            }
-            fprintf(fp, "' -- $c)); return 0; fi\n");
-            break;
-        case eshellZSH:
-            fprintf(fp, " -x 's[-]' -s \"");
-            for (int i = 0; i < nfile; i++)
-            {
-                fprintf(fp, " %s", fnm[i].opt+1);
-            }
-            for (int i = 0; i < npargs; i++)
-            {
-                if (pa[i].type == etBOOL && *(pa[i].u.b))
-                {
-                    fprintf(fp, " no%s", pa[i].option+1);
-                }
-                else
-                {
-                    fprintf(fp, " %s", pa[i].option+1);
-                }
-            }
-            fprintf(fp, "\" ");
-            break;
+        return;
+    }
+    const FileNameOptionInfo *fileOption = option.toType<FileNameOptionInfo>();
+    if (fileOption != NULL)
+    {
+        if (fileOption->isDirectoryOption())
+        {
+            writeOptionCompletion(option, "compgen -S ' ' -d $c");
+            return;
+        }
+        const FileNameOptionInfo::ExtensionList &extensionList = fileOption->extensions();
+        if (extensionList.empty())
+        {
+            return;
+        }
+        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;
     }
 }
 
-static void pr_enums(FILE *fp, int npargs, t_pargs pa[], int shell)
+void OptionCompletionWriter::writeOptionCompletion(
+        const OptionInfo &option, const std::string &completion)
 {
-    int i, j;
-
-    switch (shell)
+    std::string result(formatString("-%s) ", option.name().c_str()));
+    if (option.maxValueCount() >= 0)
     {
-        case eshellCSH:
-            for (i = 0; i < npargs; i++)
-            {
-                if (pa[i].type == etENUM)
-                {
-                    fprintf(fp, " \"n/%s/(", pa[i].option);
-                    for (j = 1; pa[i].u.c[j]; j++)
-                    {
-                        fprintf(fp, " %s", pa[i].u.c[j]);
-                    }
-                    fprintf(fp, ")/\"");
-                }
-            }
-            break;
-        case eshellBASH:
-            for (i = 0; i < npargs; i++)
-            {
-                if (pa[i].type == etENUM)
-                {
-                    fprintf(fp, "%s) COMPREPLY=( $(compgen -W '", pa[i].option);
-                    for (j = 1; pa[i].u.c[j]; j++)
-                    {
-                        fprintf(fp, " %s", pa[i].u.c[j]);
-                    }
-                    fprintf(fp, " ' -- $c ));;\n");
-                }
-            }
-            break;
-        case eshellZSH:
-            for (i = 0; i < npargs; i++)
-            {
-                if (pa[i].type == etENUM)
-                {
-                    fprintf(fp, "- 'c[-1,%s]' -s \"", pa[i].option);
-                    for (j = 1; pa[i].u.c[j]; j++)
-                    {
-                        fprintf(fp, " %s", pa[i].u.c[j]);
-                    }
-                    fprintf(fp, "\" ");
-                }
-            }
-            break;
+        result.append(formatString("(( $n <= %d )) && ", option.maxValueCount()));
     }
+    result.append("COMPREPLY=( $(");
+    result.append(completion);
+    result.append("));;");
+    out_.writeLine(result);
 }
 
-static void write_bashcompl(FILE *out,
-                            int nfile,  t_filenm *fnm,
-                            int npargs, t_pargs *pa)
+}   // namespace
+
+class ShellCompletionWriter::Impl
 {
-    /* Advanced bash completions are handled by shell functions.
-     * p and c hold the previous and current word on the command line.
-     * We need to use extended globbing, so write it in each completion file */
-    fprintf(out, "shopt -s extglob\n");
-    fprintf(out, "_%s_compl() {\nlocal p c\n", ShortProgram());
-    fprintf(out, "COMPREPLY=() c=${COMP_WORDS[COMP_CWORD]} p=${COMP_WORDS[COMP_CWORD-1]}\n");
-    pr_opts(out, nfile, fnm, npargs, pa, eshellBASH);
-    fprintf(out, "case \"$p\" in\n");
+    public:
+        Impl(const std::string &binaryName, ShellCompletionFormat /*format*/)
+            : binaryName_(binaryName)
+        {
+        }
 
-    pr_enums(out, npargs, pa, eshellBASH);
-    pr_fopts(out, nfile, fnm, eshellBASH);
-    fprintf(out, "esac }\ncomplete -F _%s_compl %s\n", ShortProgram(), ShortProgram());
+        std::string completionFunctionName(const char *moduleName) const
+        {
+            // TODO: Consider if some characters need to be escaped.
+            return formatString("_%s_%s_compl", binaryName_.c_str(), moduleName);
+        }
+
+        std::string             binaryName_;
+        boost::scoped_ptr<File> file_;
+};
+
+ShellCompletionWriter::ShellCompletionWriter(const std::string     &binaryName,
+                                             ShellCompletionFormat  format)
+    : impl_(new Impl(binaryName, format))
+{
 }
 
-static void write_cshcompl(FILE *out,
-                           int nfile,  t_filenm *fnm,
-                           int npargs, t_pargs *pa)
+ShellCompletionWriter::~ShellCompletionWriter()
 {
-    fprintf(out, "complete %s", ShortProgram());
-    pr_enums(out, npargs, pa, eshellCSH);
-    pr_fopts(out, nfile, fnm, eshellCSH);
-    pr_opts(out, nfile, fnm, npargs, pa, eshellCSH);
-    fprintf(out, "\n");
 }
 
-static void write_zshcompl(FILE *out,
-                           int nfile,  t_filenm *fnm,
-                           int npargs, t_pargs *pa)
+File *ShellCompletionWriter::outputFile()
 {
-    fprintf(out, "compctl ");
+    return impl_->file_.get();
+}
 
-    /* start with options, since they are always present */
-    pr_opts(out, nfile, fnm, npargs, pa, eshellZSH);
-    pr_enums(out, npargs, pa, eshellZSH);
-    pr_fopts(out, nfile, fnm, eshellZSH);
-    fprintf(out, "-- %s\n", ShortProgram());
+void ShellCompletionWriter::startCompletions()
+{
+    impl_->file_.reset(new File(impl_->binaryName_ + "-completion.bash", "w"));
+    impl_->file_->writeLine("shopt -s extglob");
 }
 
-void write_completions(const char *type, const char *program,
-                       int nfile,  t_filenm *fnm,
-                       int npargs, t_pargs *pa)
+void ShellCompletionWriter::writeModuleCompletions(
+        const char    *moduleName,
+        const Options &options)
 {
-    char     buf[256];
-    sprintf(buf, "%s.%s", program, type);
-    FILE    *out = gmx_fio_fopen(buf, "w");
+    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=()");
 
-    int      npar;
-    t_pargs *par;
+    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()));
 
-    // 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++;
-        }
-    }
+    out.writeLine("case \"$p\" in");
+    OptionCompletionWriter optionWriter(&out);
+    optionWriter.visitSubSection(options);
+    out.writeLine("esac }");
+}
 
-    if (strcmp(type, "completion-zsh") == 0)
-    {
-        write_zshcompl(out, nfile, fnm, npar, par);
-    }
-    if (strcmp(type, "completion-bash") == 0)
+void ShellCompletionWriter::writeWrapperCompletions(
+        const ModuleNameList &modules, const Options &options)
+{
+    impl_->file_->writeLine("_" + impl_->binaryName_ + "_compl() {");
+    impl_->file_->writeLine("local i c m");
+    impl_->file_->writeLine("local IFS=$'\\n'\n");
+    impl_->file_->writeLine("COMPREPLY=()");
+    impl_->file_->writeLine("unset COMP_WORDS[0]");
+    impl_->file_->writeLine("for ((i=1;i<COMP_CWORD;++i)) ; do");
+    impl_->file_->writeLine("[[ \"${COMP_WORDS[i]}\" != -* ]] && break");
+    impl_->file_->writeLine("unset COMP_WORDS[i]");
+    impl_->file_->writeLine("done");
+    impl_->file_->writeLine("if (( i == COMP_CWORD )); then");
+    impl_->file_->writeLine("c=${COMP_WORDS[COMP_CWORD]}");
+    OptionsListWriter lister;
+    lister.visitSubSection(options);
+    std::string       completions(lister.optionList());
+    for (ModuleNameList::const_iterator i = modules.begin();
+         i != modules.end(); ++i)
     {
-        write_bashcompl(out, nfile, fnm, npar, par);
+        completions.append("\\n");
+        completions.append(*i);
     }
-    if (strcmp(type, "completion-csh") == 0)
+    impl_->file_->writeLine("COMPREPLY=( $(compgen -S ' ' -W $'" + completions + "' -- $c) )");
+    impl_->file_->writeLine("return 0");
+    impl_->file_->writeLine("fi");
+    impl_->file_->writeLine("m=${COMP_WORDS[i]}");
+    impl_->file_->writeLine("COMP_WORDS=( \"${COMP_WORDS[@]}\" )");
+    impl_->file_->writeLine("COMP_CWORD=$((COMP_CWORD-i))");
+    impl_->file_->writeLine("case \"$m\" in");
+    for (ModuleNameList::const_iterator i = modules.begin();
+         i != modules.end(); ++i)
     {
-        write_cshcompl(out, nfile, fnm, npar, par);
+        const char *const name = i->c_str();
+        impl_->file_->writeLine(formatString("%s) %s ;;", name,
+                                             impl_->completionFunctionName(name).c_str()));
     }
+    impl_->file_->writeLine("esac }");
+}
 
-    sfree(par);
-
-    gmx_fio_fclose(out);
+void ShellCompletionWriter::finishCompletions()
+{
+    impl_->file_->close();
+    impl_->file_.reset();
 }
+
+} // namespace gmx