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