Fixes to filename shell completions
[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         if (ftp == efRND)
65         {
66             fprintf(fp, "%s) COMPREPLY=( $(compgen -S ' ' -d $c) );;\n",
67                     tfn[i].opt);
68             continue;
69         }
70         fprintf(fp, "%s) COMPREPLY=( $(compgen -S ' ' -X '!*.", tfn[i].opt);
71         const int genericCount = ftp2generic_count(ftp);
72         if (genericCount > 0)
73         {
74             fprintf(fp, "@(");
75             const int *const genericTypes = ftp2generic_list(ftp);
76             for (int j = 0; j < genericCount; j++)
77             {
78                 if (j > 0)
79                 {
80                     fprintf(fp, "|");
81                 }
82                 fprintf(fp, "%s", ftp2ext(genericTypes[j]));
83             }
84             fprintf(fp, ")");
85         }
86         else
87         {
88             fprintf(fp, "%s", ftp2ext(ftp));
89         }
90         fprintf(fp, "?(");
91         for (int j = 0; j < NZEXT; j++)
92         {
93             if (j > 0)
94             {
95                 fprintf(fp, "|");
96             }
97             fprintf(fp, "%s", z_ext[j]);
98         }
99         fprintf(fp, ")' -f $c ; compgen -S '/' -d $c ));;\n");
100     }
101 }
102
103 static void pr_opts(FILE *fp,
104                     int nfile,  t_filenm *fnm,
105                     int npargs, t_pargs pa[])
106 {
107     fprintf(fp, "if (( $COMP_CWORD <= 1 )) || [[ $c == -* ]]; then COMPREPLY=( $(compgen -S ' '  -W $'");
108     const char *sep = "";
109     for (int i = 0; i < nfile; i++)
110     {
111         fprintf(fp, "%s-%s", sep, fnm[i].opt+1);
112         sep = "\\n";
113     }
114     for (int i = 0; i < npargs; i++)
115     {
116         if (pa[i].type == etBOOL && *(pa[i].u.b))
117         {
118             fprintf(fp, "%s-no%s", sep, pa[i].option + 1);
119         }
120         else
121         {
122             fprintf(fp, "%s-%s", sep, pa[i].option + 1);
123         }
124         sep = "\\n";
125     }
126     fprintf(fp, "' -- $c)); return 0; fi\n");
127 }
128
129 static void pr_enums(FILE *fp, int npargs, t_pargs pa[])
130 {
131     for (int i = 0; i < npargs; i++)
132     {
133         if (pa[i].type == etENUM)
134         {
135             fprintf(fp, "%s) COMPREPLY=( $(compgen -S ' ' -W $'", pa[i].option);
136             for (int j = 1; pa[i].u.c[j]; j++)
137             {
138                 fprintf(fp, "%s%s", (j == 1 ? "" : "\\n"), pa[i].u.c[j]);
139             }
140             fprintf(fp, "' -- $c ));;\n");
141         }
142     }
143 }
144
145 static void write_bashcompl(FILE *out,
146                             const char *funcName,
147                             int nfile,  t_filenm *fnm,
148                             int npargs, t_pargs *pa)
149 {
150     /* Advanced bash completions are handled by shell functions.
151      * p and c hold the previous and current word on the command line.
152      */
153     fprintf(out, "%s() {\n", funcName);
154     fprintf(out, "local p c\n");
155     fprintf(out, "local IFS=$'\\n'\n");
156     fprintf(out, "COMPREPLY=() c=${COMP_WORDS[COMP_CWORD]} p=${COMP_WORDS[COMP_CWORD-1]}\n");
157     pr_opts(out, nfile, fnm, npargs, pa);
158     fprintf(out, "case \"$p\" in\n");
159
160     pr_enums(out, npargs, pa);
161     pr_fopts(out, nfile, fnm);
162     fprintf(out, "esac }\n");
163 }
164
165 namespace gmx
166 {
167
168 class ShellCompletionWriter::Impl
169 {
170     public:
171         Impl(const std::string &binaryName, ShellCompletionFormat /*format*/)
172             : binaryName_(binaryName)
173         {
174         }
175
176         std::string completionFunctionName(const char *moduleName) const
177         {
178             // TODO: Consider if some characters need to be escaped.
179             return formatString("_%s_%s_compl", binaryName_.c_str(), moduleName);
180         }
181
182         std::string             binaryName_;
183         boost::scoped_ptr<File> file_;
184 };
185
186 ShellCompletionWriter::ShellCompletionWriter(const std::string     &binaryName,
187                                              ShellCompletionFormat  format)
188     : impl_(new Impl(binaryName, format))
189 {
190 }
191
192 ShellCompletionWriter::~ShellCompletionWriter()
193 {
194 }
195
196 File *ShellCompletionWriter::outputFile()
197 {
198     return impl_->file_.get();
199 }
200
201 void ShellCompletionWriter::startCompletions()
202 {
203     impl_->file_.reset(new File(impl_->binaryName_ + "-completion.bash", "w"));
204     impl_->file_->writeLine("shopt -s extglob");
205 }
206
207 void ShellCompletionWriter::writeLegacyModuleCompletions(
208         const char *moduleName,
209         int nfile,  t_filenm *fnm,
210         int npargs, t_pargs *pa)
211 {
212     int      npar;
213     t_pargs *par;
214
215     // Remove hidden arguments.
216     snew(par, npargs);
217     npar = 0;
218     for (int i = 0; i < npargs; i++)
219     {
220         if (!is_hidden(&pa[i]))
221         {
222             par[npar] = pa[i];
223             npar++;
224         }
225     }
226
227     write_bashcompl(impl_->file_->handle(),
228                     impl_->completionFunctionName(moduleName).c_str(),
229                     nfile, fnm, npar, par);
230
231     sfree(par);
232 }
233
234 void ShellCompletionWriter::writeModuleCompletions(
235         const char    *moduleName,
236         const Options  & /*options*/)
237 {
238     // TODO: Implement.
239     impl_->file_->writeLine(
240             impl_->completionFunctionName(moduleName) + "() {\nCOMPREPLY=()\n}\n");
241 }
242
243 void ShellCompletionWriter::writeWrapperCompletions(
244         const ModuleNameList &modules)
245 {
246     impl_->file_->writeLine("_" + impl_->binaryName_ + "_compl() {");
247     impl_->file_->writeLine("local i c m");
248     impl_->file_->writeLine("local IFS=$'\\n'\n");
249     impl_->file_->writeLine("COMPREPLY=()");
250     impl_->file_->writeLine("unset COMP_WORDS[0]");
251     impl_->file_->writeLine("for ((i=1;i<COMP_CWORD;++i)) ; do");
252     impl_->file_->writeLine("if [[ \"${COMP_WORDS[i]}\" != -* ]]; then break ; fi");
253     impl_->file_->writeLine("unset COMP_WORDS[i]");
254     impl_->file_->writeLine("done");
255     impl_->file_->writeLine("if (( i == COMP_CWORD )); then");
256     impl_->file_->writeLine("c=${COMP_WORDS[COMP_CWORD]}");
257     // TODO: Get rid of these hard-coded options.
258     std::string completions("-h\\n-quiet\\n-version\\n-nocopyright");
259     for (ModuleNameList::const_iterator i = modules.begin();
260          i != modules.end(); ++i)
261     {
262         completions.append("\\n");
263         completions.append(*i);
264     }
265     impl_->file_->writeLine("COMPREPLY=( $(compgen -S ' ' -W $'" + completions + "' -- $c) )");
266     impl_->file_->writeLine("return 0");
267     impl_->file_->writeLine("fi");
268     impl_->file_->writeLine("m=${COMP_WORDS[i]}");
269     impl_->file_->writeLine("COMP_WORDS=( \"${COMP_WORDS[@]}\" )");
270     impl_->file_->writeLine("COMP_CWORD=$((COMP_CWORD-i))");
271     impl_->file_->writeLine("case \"$m\" in");
272     for (ModuleNameList::const_iterator i = modules.begin();
273          i != modules.end(); ++i)
274     {
275         const char *const name = i->c_str();
276         impl_->file_->writeLine(formatString("%s) %s ;;", name,
277                                              impl_->completionFunctionName(name).c_str()));
278     }
279     impl_->file_->writeLine("esac }");
280 }
281
282 void ShellCompletionWriter::finishCompletions()
283 {
284     impl_->file_->close();
285     impl_->file_.reset();
286 }
287
288 } // namespace gmx