* 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 "gromacs/commandline/cmdlinehelpcontext.h"
#include "gromacs/commandline/pargs.h"
#include "gromacs/fileio/filenm.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/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++)
- {
- 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
+ public:
+ const std::string &optionList() const { return optionList_; }
+
+ virtual void visitSubSection(const Options §ion)
{
- fprintf(fp, "%s", ftp2ext(ftp));
+ OptionsIterator iterator(section);
+ iterator.acceptSubSections(this);
+ iterator.acceptOptions(this);
}
- fprintf(fp, "*(");
- for (int j = 0; j < NZEXT; j++)
+ virtual void visitOption(const OptionInfo &option)
{
- if (j > 0)
+ if (option.isHidden())
+ {
+ return;
+ }
+ 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 '/' -X '.*' -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 §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);
+
+ File &out_;
+};
+
+void OptionCompletionWriter::visitOption(const OptionInfo &option)
{
- fprintf(fp, "if (( $COMP_CWORD <= 1 )) || [[ $c == -* ]]; then COMPREPLY=( $(compgen -W '");
- for (int i = 0; i < nfile; i++)
+ if (option.isHidden())
{
- fprintf(fp, " -%s", fnm[i].opt+1);
+ 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, " -no%s", pa[i].option+1);
+ writeOptionCompletion(option, "compgen -S ' ' -d $c");
+ return;
}
- else
+ const FileNameOptionInfo::ExtensionList &extensionList = fileOption->extensions();
+ if (extensionList.empty())
{
- fprintf(fp, " -%s", pa[i].option+1);
+ return;
}
- }
- fprintf(fp, "' -- $c)); return 0; fi\n");
-}
-
-static void pr_enums(FILE *fp, int npargs, t_pargs pa[])
-{
- for (int i = 0; i < npargs; i++)
- {
- if (pa[i].type == etENUM)
+ std::string completion("compgen -S ' ' -X '!*");
+ std::string extensions(joinStrings(extensionList, "|"));
+ if (extensionList.size() > 1)
{
- fprintf(fp, "%s) COMPREPLY=( $(compgen -W '", pa[i].option);
- for (int j = 1; pa[i].u.c[j]; j++)
- {
- fprintf(fp, " %s", pa[i].u.c[j]);
- }
- fprintf(fp, " ' -- $c ));;\n");
+ 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 write_bashcompl(FILE *out,
- const char *funcName,
- int nfile, t_filenm *fnm,
- int npargs, t_pargs *pa)
+void OptionCompletionWriter::writeOptionCompletion(
+ const OptionInfo &option, const std::string &completion)
{
- /* 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 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);
- fprintf(out, "case \"$p\" in\n");
-
- pr_enums(out, npargs, pa);
- pr_fopts(out, nfile, fnm);
- fprintf(out, "esac }\n");
+ std::string result(formatString("-%s) ", option.name().c_str()));
+ if (option.maxValueCount() >= 0)
+ {
+ result.append(formatString("(( $n <= %d )) && ", option.maxValueCount()));
+ }
+ result.append("COMPREPLY=( $(");
+ result.append(completion);
+ result.append("));;");
+ out_.writeLine(result);
}
-namespace gmx
-{
+} // namespace
class ShellCompletionWriter::Impl
{
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");
+ 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("if [[ \"${COMP_WORDS[i]}\" != -* ]]; then break ; fi");
+ 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]}");
- // TODO: Get rid of these hard-coded options.
- std::string completions("-h -quiet -version -nocopyright");
+ OptionsListWriter lister;
+ lister.visitSubSection(options);
+ std::string completions(lister.optionList());
for (ModuleNameList::const_iterator i = modules.begin();
i != modules.end(); ++i)
{
- completions.append(" ");
+ completions.append("\\n");
completions.append(*i);
}
- impl_->file_->writeLine("COMPREPLY=( $(compgen -W '" + completions + "' -- $c) )");
+ 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]}");