*
* 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 §ion)
+ {
+ 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 §ion)
+ {
+ 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