Shell completion export from the wrapper binary
[alexxy/gromacs.git] / src / gromacs / commandline / shellcompletions.cpp
index 002ff60d590c20d73d49d4734ae6fe58feda18ff..cf3cd9fb1d0bb453de0e9e1a589bec45c4ded398 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.
 #include "gromacs/commandline/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/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
-};
-
 // TODO: Don't duplicate this from filenm.c/futil.c.
 #define NZEXT 2
 static const char *z_ext[NZEXT] = { ".gz", ".Z" };
 
-static void pr_fopts(FILE *fp, int nf, const t_filenm tfn[], int shell)
+static void pr_fopts(FILE *fp, int nf, const t_filenm tfn[])
 {
-    switch (shell)
+    for (int i = 0; i < nf; i++)
     {
-        case eshellCSH:
-            for (int i = 0; i < nf; i++)
-            {
-                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, "}/\"");
-            }
-            break;
-        case eshellBASH:
-            for (int i = 0; i < nf; i++)
+        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++)
             {
-                fprintf(fp, "%s) COMPREPLY=( $(compgen -X '!*.", tfn[i].opt);
-                const int ftp          = tfn[i].ftp;
-                const int genericCount = ftp2generic_count(ftp);
-                if (genericCount > 0)
+                if (j > 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, ")");
+                    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");
+                fprintf(fp, "%s", ftp2ext(genericTypes[j]));
             }
-            break;
-        case eshellZSH:
-            for (int i = 0; i < nf; i++)
+            fprintf(fp, ")");
+        }
+        else
+        {
+            fprintf(fp, "%s", ftp2ext(ftp));
+        }
+        fprintf(fp, "*(");
+        for (int j = 0; j < NZEXT; j++)
+        {
+            if (j > 0)
             {
-                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, ") *(/)' ");
+                fprintf(fp, "|");
             }
-            break;
+            fprintf(fp, "%s", z_ext[j]);
+        }
+        fprintf(fp, ")' -f $c ; compgen -S '/' -X '.*' -d $c ));;\n");
     }
 }
 
 static void pr_opts(FILE *fp,
                     int nfile,  t_filenm *fnm,
-                    int npargs, t_pargs pa[], int shell)
+                    int npargs, t_pargs pa[])
 {
-    switch (shell)
+    fprintf(fp, "if (( $COMP_CWORD <= 1 )) || [[ $c == -* ]]; then COMPREPLY=( $(compgen  -W '");
+    for (int i = 0; i < nfile; i++)
     {
-        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;
+        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");
 }
 
-static void pr_enums(FILE *fp, int npargs, t_pargs pa[], int shell)
+static void pr_enums(FILE *fp, int npargs, t_pargs pa[])
 {
-    int i, j;
-
-    switch (shell)
+    for (int i = 0; i < npargs; i++)
     {
-        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, "%s) COMPREPLY=( $(compgen -W '", pa[i].option);
+            for (int j = 1; pa[i].u.c[j]; j++)
             {
-                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, "\" ");
-                }
+                fprintf(fp, " %s", pa[i].u.c[j]);
             }
-            break;
+            fprintf(fp, " ' -- $c ));;\n");
+        }
     }
 }
 
 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.
-     * 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, "%s() {\n", funcName);
+    fprintf(out, "local p c\n");
     fprintf(out, "COMPREPLY=() c=${COMP_WORDS[COMP_CWORD]} p=${COMP_WORDS[COMP_CWORD-1]}\n");
-    pr_opts(out, nfile, fnm, npargs, pa, eshellBASH);
+    pr_opts(out, nfile, fnm, npargs, pa);
     fprintf(out, "case \"$p\" in\n");
 
-    pr_enums(out, npargs, pa, eshellBASH);
-    pr_fopts(out, nfile, fnm, eshellBASH);
-    fprintf(out, "esac }\ncomplete -F _%s_compl %s\n", ShortProgram(), ShortProgram());
+    pr_enums(out, npargs, pa);
+    pr_fopts(out, nfile, fnm);
+    fprintf(out, "esac }\n");
 }
 
-static void write_cshcompl(FILE *out,
-                           int nfile,  t_filenm *fnm,
-                           int npargs, t_pargs *pa)
+namespace gmx
+{
+
+class ShellCompletionWriter::Impl
+{
+    public:
+        Impl(const std::string &binaryName, ShellCompletionFormat /*format*/)
+            : binaryName_(binaryName)
+        {
+        }
+
+        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))
 {
-    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)
+ShellCompletionWriter::~ShellCompletionWriter()
 {
-    fprintf(out, "compctl ");
+}
 
-    /* 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());
+File *ShellCompletionWriter::outputFile()
+{
+    return impl_->file_.get();
 }
 
-void write_completions(const char *type, const char *program,
-                       int nfile,  t_filenm *fnm,
-                       int npargs, t_pargs *pa)
+void ShellCompletionWriter::startCompletions()
 {
-    char     buf[256];
-    sprintf(buf, "%s.%s", program, type);
-    FILE    *out = gmx_fio_fopen(buf, "w");
+    impl_->file_.reset(new File(impl_->binaryName_ + "-completion.bash", "w"));
+    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;
 
@@ -344,20 +214,64 @@ void write_completions(const char *type, const char *program,
         }
     }
 
-    if (strcmp(type, "completion-zsh") == 0)
-    {
-        write_zshcompl(out, nfile, fnm, npar, par);
-    }
-    if (strcmp(type, "completion-bash") == 0)
+    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*/)
+{
+    // TODO: Implement.
+    impl_->file_->writeLine(
+            impl_->completionFunctionName(moduleName) + "() {\nCOMPREPLY=()\n}\n");
+}
+
+void ShellCompletionWriter::writeWrapperCompletions(
+        const ModuleNameList &modules)
+{
+    impl_->file_->writeLine("_" + impl_->binaryName_ + "_compl() {");
+    impl_->file_->writeLine("local i c m");
+    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("if [[ \"${COMP_WORDS[i]}\" != -* ]]; then break ; fi");
+    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]}");
+    // TODO: Get rid of these hard-coded options.
+    std::string completions("-h -quiet -version -nocopyright");
+    for (ModuleNameList::const_iterator i = modules.begin();
+         i != modules.end(); ++i)
     {
-        write_bashcompl(out, nfile, fnm, npar, par);
+        completions.append(" ");
+        completions.append(*i);
     }
-    if (strcmp(type, "completion-csh") == 0)
+    impl_->file_->writeLine("COMPREPLY=( $(compgen -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