Fix shell completions of ffMULT options
[alexxy/gromacs.git] / src / gromacs / commandline / shellcompletions.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2013,2014, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37 #include "gromacs/commandline/shellcompletions.h"
38
39 #include <cstdio>
40
41 #include <string>
42
43 #include <boost/scoped_ptr.hpp>
44
45 #include "gromacs/commandline/cmdlinehelpcontext.h"
46 #include "gromacs/commandline/pargs.h"
47 #include "gromacs/fileio/filenm.h"
48 #include "gromacs/utility/exceptions.h"
49 #include "gromacs/utility/file.h"
50 #include "gromacs/utility/gmxassert.h"
51 #include "gromacs/utility/stringutil.h"
52
53 #include "gromacs/legacyheaders/smalloc.h"
54
55 // TODO: Don't duplicate this from filenm.c/futil.c.
56 #define NZEXT 2
57 static const char *z_ext[NZEXT] = { ".gz", ".Z" };
58
59 static void pr_fopts(FILE *fp, int nf, const t_filenm tfn[])
60 {
61     for (int i = 0; i < nf; i++)
62     {
63         const int   ftp          = tfn[i].ftp;
64         const char *multiplicity = "(( $n == 1 )) && ";
65         if (tfn[i].flag & ffMULT)
66         {
67             multiplicity = "";
68         }
69         if (ftp == efRND)
70         {
71             fprintf(fp, "%s) %sCOMPREPLY=( $(compgen -S ' ' -d $c) );;\n",
72                     tfn[i].opt, multiplicity);
73             continue;
74         }
75         fprintf(fp, "%s) %sCOMPREPLY=( $(compgen -S ' ' -X '!*.",
76                 tfn[i].opt, multiplicity);
77         const int genericCount = ftp2generic_count(ftp);
78         if (genericCount > 0)
79         {
80             fprintf(fp, "@(");
81             const int *const genericTypes = ftp2generic_list(ftp);
82             for (int j = 0; j < genericCount; j++)
83             {
84                 if (j > 0)
85                 {
86                     fprintf(fp, "|");
87                 }
88                 fprintf(fp, "%s", ftp2ext(genericTypes[j]));
89             }
90             fprintf(fp, ")");
91         }
92         else
93         {
94             fprintf(fp, "%s", ftp2ext(ftp));
95         }
96         fprintf(fp, "?(");
97         for (int j = 0; j < NZEXT; j++)
98         {
99             if (j > 0)
100             {
101                 fprintf(fp, "|");
102             }
103             fprintf(fp, "%s", z_ext[j]);
104         }
105         fprintf(fp, ")' -f $c ; compgen -S '/' -d $c ));;\n");
106     }
107 }
108
109 static void pr_opts(FILE *fp,
110                     int nfile,  t_filenm *fnm,
111                     int npargs, t_pargs pa[])
112 {
113     fprintf(fp, "if (( $COMP_CWORD <= 1 )) || [[ $c == -* ]]; then COMPREPLY=( $(compgen -S ' '  -W $'");
114     const char *sep = "";
115     for (int i = 0; i < nfile; i++)
116     {
117         fprintf(fp, "%s-%s", sep, fnm[i].opt+1);
118         sep = "\\n";
119     }
120     for (int i = 0; i < npargs; i++)
121     {
122         if (pa[i].type == etBOOL && *(pa[i].u.b))
123         {
124             fprintf(fp, "%s-no%s", sep, pa[i].option + 1);
125         }
126         else
127         {
128             fprintf(fp, "%s-%s", sep, pa[i].option + 1);
129         }
130         sep = "\\n";
131     }
132     fprintf(fp, "' -- $c)); return 0; fi\n");
133 }
134
135 static void pr_enums(FILE *fp, int npargs, t_pargs pa[])
136 {
137     for (int i = 0; i < npargs; i++)
138     {
139         if (pa[i].type == etENUM)
140         {
141             fprintf(fp, "%s) (( $n == 1 )) && COMPREPLY=( $(compgen -S ' ' -W $'", pa[i].option);
142             for (int j = 1; pa[i].u.c[j]; j++)
143             {
144                 fprintf(fp, "%s%s", (j == 1 ? "" : "\\n"), pa[i].u.c[j]);
145             }
146             fprintf(fp, "' -- $c ));;\n");
147         }
148     }
149 }
150
151 static void write_bashcompl(FILE *out,
152                             const char *funcName,
153                             int nfile,  t_filenm *fnm,
154                             int npargs, t_pargs *pa)
155 {
156     /* Advanced bash completions are handled by shell functions.
157      * p and c hold the previous and current word on the command line.
158      */
159     fprintf(out, "%s() {\n", funcName);
160     fprintf(out, "local IFS=$'\\n'\n");
161     fprintf(out, "local c=${COMP_WORDS[COMP_CWORD]}\n");
162     fprintf(out, "local n\n");
163     fprintf(out, "for ((n=1;n<COMP_CWORD;++n)) ; do [[ \"${COMP_WORDS[COMP_CWORD-n]}\" == -* ]] && break ; done\n");
164     fprintf(out, "local p=${COMP_WORDS[COMP_CWORD-n]}\n");
165     fprintf(out, "COMPREPLY=()\n");
166
167     pr_opts(out, nfile, fnm, npargs, pa);
168     fprintf(out, "case \"$p\" in\n");
169
170     pr_enums(out, npargs, pa);
171     pr_fopts(out, nfile, fnm);
172     fprintf(out, "esac }\n");
173 }
174
175 namespace gmx
176 {
177
178 class ShellCompletionWriter::Impl
179 {
180     public:
181         Impl(const std::string &binaryName, ShellCompletionFormat /*format*/)
182             : binaryName_(binaryName)
183         {
184         }
185
186         std::string completionFunctionName(const char *moduleName) const
187         {
188             // TODO: Consider if some characters need to be escaped.
189             return formatString("_%s_%s_compl", binaryName_.c_str(), moduleName);
190         }
191
192         std::string             binaryName_;
193         boost::scoped_ptr<File> file_;
194 };
195
196 ShellCompletionWriter::ShellCompletionWriter(const std::string     &binaryName,
197                                              ShellCompletionFormat  format)
198     : impl_(new Impl(binaryName, format))
199 {
200 }
201
202 ShellCompletionWriter::~ShellCompletionWriter()
203 {
204 }
205
206 File *ShellCompletionWriter::outputFile()
207 {
208     return impl_->file_.get();
209 }
210
211 void ShellCompletionWriter::startCompletions()
212 {
213     impl_->file_.reset(new File(impl_->binaryName_ + "-completion.bash", "w"));
214     impl_->file_->writeLine("shopt -s extglob");
215 }
216
217 void ShellCompletionWriter::writeLegacyModuleCompletions(
218         const char *moduleName,
219         int nfile,  t_filenm *fnm,
220         int npargs, t_pargs *pa)
221 {
222     int      npar;
223     t_pargs *par;
224
225     // Remove hidden arguments.
226     snew(par, npargs);
227     npar = 0;
228     for (int i = 0; i < npargs; i++)
229     {
230         if (!is_hidden(&pa[i]))
231         {
232             par[npar] = pa[i];
233             npar++;
234         }
235     }
236
237     write_bashcompl(impl_->file_->handle(),
238                     impl_->completionFunctionName(moduleName).c_str(),
239                     nfile, fnm, npar, par);
240
241     sfree(par);
242 }
243
244 void ShellCompletionWriter::writeModuleCompletions(
245         const char    *moduleName,
246         const Options  & /*options*/)
247 {
248     // TODO: Implement.
249     impl_->file_->writeLine(
250             impl_->completionFunctionName(moduleName) + "() {\nCOMPREPLY=()\n}\n");
251 }
252
253 void ShellCompletionWriter::writeWrapperCompletions(
254         const ModuleNameList &modules)
255 {
256     impl_->file_->writeLine("_" + impl_->binaryName_ + "_compl() {");
257     impl_->file_->writeLine("local i c m");
258     impl_->file_->writeLine("local IFS=$'\\n'\n");
259     impl_->file_->writeLine("COMPREPLY=()");
260     impl_->file_->writeLine("unset COMP_WORDS[0]");
261     impl_->file_->writeLine("for ((i=1;i<COMP_CWORD;++i)) ; do");
262     impl_->file_->writeLine("[[ \"${COMP_WORDS[i]}\" != -* ]] && break");
263     impl_->file_->writeLine("unset COMP_WORDS[i]");
264     impl_->file_->writeLine("done");
265     impl_->file_->writeLine("if (( i == COMP_CWORD )); then");
266     impl_->file_->writeLine("c=${COMP_WORDS[COMP_CWORD]}");
267     // TODO: Get rid of these hard-coded options.
268     std::string completions("-h\\n-quiet\\n-version\\n-nocopyright");
269     for (ModuleNameList::const_iterator i = modules.begin();
270          i != modules.end(); ++i)
271     {
272         completions.append("\\n");
273         completions.append(*i);
274     }
275     impl_->file_->writeLine("COMPREPLY=( $(compgen -S ' ' -W $'" + completions + "' -- $c) )");
276     impl_->file_->writeLine("return 0");
277     impl_->file_->writeLine("fi");
278     impl_->file_->writeLine("m=${COMP_WORDS[i]}");
279     impl_->file_->writeLine("COMP_WORDS=( \"${COMP_WORDS[@]}\" )");
280     impl_->file_->writeLine("COMP_CWORD=$((COMP_CWORD-i))");
281     impl_->file_->writeLine("case \"$m\" in");
282     for (ModuleNameList::const_iterator i = modules.begin();
283          i != modules.end(); ++i)
284     {
285         const char *const name = i->c_str();
286         impl_->file_->writeLine(formatString("%s) %s ;;", name,
287                                              impl_->completionFunctionName(name).c_str()));
288     }
289     impl_->file_->writeLine("esac }");
290 }
291
292 void ShellCompletionWriter::finishCompletions()
293 {
294     impl_->file_->close();
295     impl_->file_.reset();
296 }
297
298 } // namespace gmx