Merge branch release-2018
[alexxy/gromacs.git] / src / gromacs / commandline / pargs.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,2015,2016,2017,2018, 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 "gmxpre.h"
38
39 #include "pargs.h"
40
41 #include <cstdlib>
42 #include <cstring>
43
44 #include <algorithm>
45 #include <list>
46
47 #include "gromacs/commandline/cmdlinehelpcontext.h"
48 #include "gromacs/commandline/cmdlinehelpwriter.h"
49 #include "gromacs/commandline/cmdlineparser.h"
50 #include "gromacs/fileio/oenv.h"
51 #include "gromacs/fileio/timecontrol.h"
52 #include "gromacs/options/basicoptions.h"
53 #include "gromacs/options/behaviorcollection.h"
54 #include "gromacs/options/filenameoption.h"
55 #include "gromacs/options/filenameoptionmanager.h"
56 #include "gromacs/options/options.h"
57 #include "gromacs/options/timeunitmanager.h"
58 #include "gromacs/utility/arrayref.h"
59 #include "gromacs/utility/basenetwork.h"
60 #include "gromacs/utility/classhelpers.h"
61 #include "gromacs/utility/cstringutil.h"
62 #include "gromacs/utility/exceptions.h"
63 #include "gromacs/utility/fatalerror.h"
64 #include "gromacs/utility/gmxassert.h"
65 #include "gromacs/utility/path.h"
66 #include "gromacs/utility/programcontext.h"
67 #include "gromacs/utility/smalloc.h"
68 #include "gromacs/utility/stringutil.h"
69
70 /* The source code in this file should be thread-safe.
71       Please keep it that way. */
72
73 int nenum(const char *const enumc[])
74 {
75     int i;
76
77     i = 1;
78     /* we *can* compare pointers directly here! */
79     while (enumc[i] && enumc[0] != enumc[i])
80     {
81         i++;
82     }
83
84     return i;
85 }
86
87 int opt2parg_int(const char *option, int nparg, t_pargs pa[])
88 {
89     int i;
90
91     for (i = 0; (i < nparg); i++)
92     {
93         if (strcmp(pa[i].option, option) == 0)
94         {
95             return *pa[i].u.i;
96         }
97     }
98
99     gmx_fatal(FARGS, "No integer option %s in pargs", option);
100
101     return 0;
102 }
103
104 gmx_bool opt2parg_bool(const char *option, int nparg, t_pargs pa[])
105 {
106     int i;
107
108     for (i = 0; (i < nparg); i++)
109     {
110         if (strcmp(pa[i].option, option) == 0)
111         {
112             return *pa[i].u.b;
113         }
114     }
115
116     gmx_fatal(FARGS, "No boolean option %s in pargs", option);
117
118     return FALSE;
119 }
120
121 real opt2parg_real(const char *option, int nparg, t_pargs pa[])
122 {
123     int i;
124
125     for (i = 0; (i < nparg); i++)
126     {
127         if (strcmp(pa[i].option, option) == 0)
128         {
129             return *pa[i].u.r;
130         }
131     }
132
133     gmx_fatal(FARGS, "No real option %s in pargs", option);
134
135     return 0.0;
136 }
137
138 const char *opt2parg_str(const char *option, int nparg, t_pargs pa[])
139 {
140     int i;
141
142     for (i = 0; (i < nparg); i++)
143     {
144         if (strcmp(pa[i].option, option) == 0)
145         {
146             return *(pa[i].u.c);
147         }
148     }
149
150     gmx_fatal(FARGS, "No string option %s in pargs", option);
151
152     return nullptr;
153 }
154
155 gmx_bool opt2parg_bSet(const char *option, int nparg, const t_pargs *pa)
156 {
157     int i;
158
159     for (i = 0; (i < nparg); i++)
160     {
161         if (strcmp(pa[i].option, option) == 0)
162         {
163             return pa[i].bSet;
164         }
165     }
166
167     gmx_fatal(FARGS, "No such option %s in pargs", option);
168
169     return FALSE; /* Too make some compilers happy */
170 }
171
172 const char *opt2parg_enum(const char *option, int nparg, t_pargs pa[])
173 {
174     int i;
175
176     for (i = 0; (i < nparg); i++)
177     {
178         if (strcmp(pa[i].option, option) == 0)
179         {
180             return pa[i].u.c[0];
181         }
182     }
183
184     gmx_fatal(FARGS, "No such option %s in pargs", option);
185
186     return nullptr;
187 }
188
189 /********************************************************************
190  * parse_common_args()
191  */
192
193 namespace gmx
194 {
195
196 namespace
197 {
198
199 /*! \brief
200  * Returns the index of the default xvg format.
201  *
202  * \ingroup module_commandline
203  */
204 int getDefaultXvgFormat(gmx::ArrayRef<const char *const> xvgFormats)
205 {
206     const char *const select = getenv("GMX_VIEW_XVG");
207     if (select != nullptr)
208     {
209         ArrayRef<const char *const>::const_iterator i =
210             std::find(xvgFormats.begin(), xvgFormats.end(), std::string(select));
211         if (i != xvgFormats.end())
212         {
213             return std::distance(xvgFormats.begin(), i);
214         }
215         else
216         {
217             return exvgNONE - 1;
218         }
219     }
220     /* The default is the first option */
221     return 0;
222 }
223
224 /*! \brief
225  * Conversion helper between t_pargs/t_filenm and Options.
226  *
227  * This class holds the necessary mapping between the old C structures and
228  * the new C++ options to allow copying values back after parsing for cases
229  * where the C++ options do not directly provide the type of value required for
230  * the C structures.
231  *
232  * \ingroup module_commandline
233  */
234 class OptionsAdapter
235 {
236     public:
237         /*! \brief
238          * Initializes the adapter to convert from a specified command line.
239          *
240          * The command line is required, because t_pargs wants to return
241          * strings by reference to the original command line.
242          * OptionsAdapter creates a copy of the `argv` array (but not the
243          * strings) to make this possible, even if the parser removes
244          * options it has recognized.
245          */
246         OptionsAdapter(int argc, const char *const argv[])
247             : argv_(argv, argv + argc)
248         {
249         }
250
251         /*! \brief
252          * Converts a t_filenm option into an Options option.
253          *
254          * \param options Options object to add the new option to.
255          * \param fnm     t_filenm option to convert.
256          */
257         void filenmToOptions(Options *options, t_filenm *fnm);
258         /*! \brief
259          * Converts a t_pargs option into an Options option.
260          *
261          * \param     options Options object to add the new option to.
262          * \param     pa      t_pargs option to convert.
263          */
264         void pargsToOptions(Options *options, t_pargs *pa);
265
266         /*! \brief
267          * Copies values back from options to t_pargs/t_filenm.
268          */
269         void copyValues(bool bReadNode);
270
271     private:
272         struct FileNameData
273         {
274             //! Creates a conversion helper for a given `t_filenm` struct.
275             explicit FileNameData(t_filenm *fnm) : fnm(fnm), optionInfo(nullptr)
276             {
277             }
278
279             //! t_filenm structure to receive the final values.
280             t_filenm                 *fnm;
281             //! Option info object for the created FileNameOption.
282             FileNameOptionInfo       *optionInfo;
283             //! Value storage for the created FileNameOption.
284             std::vector<std::string>  values;
285         };
286         struct ProgramArgData
287         {
288             //! Creates a conversion helper for a given `t_pargs` struct.
289             explicit ProgramArgData(t_pargs *pa)
290                 : pa(pa), optionInfo(nullptr), enumIndex(0), boolValue(false)
291             {
292             }
293
294             //! t_pargs structure to receive the final values.
295             t_pargs                 *pa;
296             //! Option info object for the created option.
297             OptionInfo              *optionInfo;
298             //! Value storage for a non-enum StringOption (unused for other types).
299             std::string              stringValue;
300             //! Value storage for an enum option (unused for other types).
301             int                      enumIndex;
302             //! Value storage for a BooleanOption (unused for other types).
303             bool                     boolValue;
304         };
305
306         std::vector<const char *>    argv_;
307         // These are lists instead of vectors to avoid relocating existing
308         // objects in case the container is reallocated (the Options object
309         // contains pointes to members of the objects, which would get
310         // invalidated).
311         std::list<FileNameData>      fileNameOptions_;
312         std::list<ProgramArgData>    programArgs_;
313
314         GMX_DISALLOW_COPY_AND_ASSIGN(OptionsAdapter);
315 };
316
317 void OptionsAdapter::filenmToOptions(Options *options, t_filenm *fnm)
318 {
319     if (fnm->opt == nullptr)
320     {
321         // Existing code may use opt2fn() instead of ftp2fn() for
322         // options that use the default option name, so we need to
323         // keep the old behavior instead of fixing opt2fn().
324         // TODO: Check that this is not the case, remove this, and make
325         // opt2*() work even if fnm->opt is NULL for some options.
326         fnm->opt = ftp2defopt(fnm->ftp);
327     }
328     const bool        bRead     = ((fnm->flag & ffREAD)  != 0);
329     const bool        bWrite    = ((fnm->flag & ffWRITE) != 0);
330     const bool        bOptional = ((fnm->flag & ffOPT)   != 0);
331     const bool        bLibrary  = ((fnm->flag & ffLIB)   != 0);
332     const bool        bMultiple = ((fnm->flag & ffMULT)  != 0);
333     const bool        bMissing  = ((fnm->flag & ffALLOW_MISSING) != 0);
334     const char *const name      = &fnm->opt[1];
335     const char *      defName   = fnm->fn;
336     int               defType   = -1;
337     if (defName == nullptr)
338     {
339         defName = ftp2defnm(fnm->ftp);
340     }
341     else if (Path::hasExtension(defName))
342     {
343         defType = fn2ftp(defName);
344         GMX_RELEASE_ASSERT(defType != efNR,
345                            "File name option specifies an invalid extension");
346     }
347     fileNameOptions_.emplace_back(fnm);
348     FileNameData &data = fileNameOptions_.back();
349     data.optionInfo = options->addOption(
350                 FileNameOption(name).storeVector(&data.values)
351                     .defaultBasename(defName).defaultType(defType)
352                     .legacyType(fnm->ftp).legacyOptionalBehavior()
353                     .readWriteFlags(bRead, bWrite).required(!bOptional)
354                     .libraryFile(bLibrary).multiValue(bMultiple)
355                     .allowMissing(bMissing)
356                     .description(ftp2desc(fnm->ftp)));
357 }
358
359 void OptionsAdapter::pargsToOptions(Options *options, t_pargs *pa)
360 {
361     const bool        bHidden = startsWith(pa->desc, "HIDDEN");
362     const char *const name    = &pa->option[1];
363     const char *const desc    = (bHidden ? &pa->desc[6] : pa->desc);
364     programArgs_.emplace_back(pa);
365     ProgramArgData   &data = programArgs_.back();
366     switch (pa->type)
367     {
368         case etINT:
369             data.optionInfo = options->addOption(
370                         IntegerOption(name).store(pa->u.i)
371                             .description(desc).hidden(bHidden));
372             return;
373         case etINT64:
374             data.optionInfo = options->addOption(
375                         Int64Option(name).store(pa->u.is)
376                             .description(desc).hidden(bHidden));
377             return;
378         case etREAL:
379             data.optionInfo = options->addOption(
380                         RealOption(name).store(pa->u.r)
381                             .description(desc).hidden(bHidden));
382             return;
383         case etTIME:
384             data.optionInfo = options->addOption(
385                         RealOption(name).store(pa->u.r).timeValue()
386                             .description(desc).hidden(bHidden));
387             return;
388         case etSTR:
389         {
390             const char *const defValue = (*pa->u.c != nullptr ? *pa->u.c : "");
391             data.optionInfo = options->addOption(
392                         StringOption(name).store(&data.stringValue)
393                             .defaultValue(defValue)
394                             .description(desc).hidden(bHidden));
395             return;
396         }
397         case etBOOL:
398             data.optionInfo = options->addOption(
399                         BooleanOption(name).store(&data.boolValue)
400                             .defaultValue(*pa->u.b)
401                             .description(desc).hidden(bHidden));
402             return;
403         case etRVEC:
404             data.optionInfo = options->addOption(
405                         RealOption(name).store(*pa->u.rv).vector()
406                             .description(desc).hidden(bHidden));
407             return;
408         case etENUM:
409         {
410             const int defaultIndex = (pa->u.c[0] != nullptr ? nenum(pa->u.c) - 1 : 0);
411             data.optionInfo = options->addOption(
412                         EnumIntOption(name).store(&data.enumIndex)
413                             .defaultValue(defaultIndex)
414                             .enumValueFromNullTerminatedArray(pa->u.c + 1)
415                             .description(desc).hidden(bHidden));
416             return;
417         }
418     }
419     GMX_THROW(NotImplementedError("Argument type not implemented"));
420 }
421
422 void OptionsAdapter::copyValues(bool bReadNode)
423 {
424     std::list<FileNameData>::const_iterator file;
425     for (file = fileNameOptions_.begin(); file != fileNameOptions_.end(); ++file)
426     {
427         if (!bReadNode && (file->fnm->flag & ffREAD))
428         {
429             continue;
430         }
431         if (file->optionInfo->isSet())
432         {
433             file->fnm->flag |= ffSET;
434         }
435         file->fnm->nfiles = file->values.size();
436         snew(file->fnm->fns, file->fnm->nfiles);
437         for (int i = 0; i < file->fnm->nfiles; ++i)
438         {
439             file->fnm->fns[i] = gmx_strdup(file->values[i].c_str());
440         }
441     }
442     std::list<ProgramArgData>::const_iterator arg;
443     for (arg = programArgs_.begin(); arg != programArgs_.end(); ++arg)
444     {
445         arg->pa->bSet = arg->optionInfo->isSet();
446         switch (arg->pa->type)
447         {
448             case etSTR:
449             {
450                 if (arg->pa->bSet)
451                 {
452                     std::vector<const char *>::const_iterator pos =
453                         std::find(argv_.begin(), argv_.end(), arg->stringValue);
454                     GMX_RELEASE_ASSERT(pos != argv_.end(),
455                                        "String argument got a value not in argv");
456                     *arg->pa->u.c = *pos;
457                 }
458                 break;
459             }
460             case etBOOL:
461                 *arg->pa->u.b = arg->boolValue;
462                 break;
463             case etENUM:
464                 *arg->pa->u.c = arg->pa->u.c[arg->enumIndex + 1];
465                 break;
466             default:
467                 // For other types, there is nothing type-specific to do.
468                 break;
469         }
470     }
471 }
472
473 } // namespace
474
475 } // namespace gmx
476
477 gmx_bool parse_common_args(int *argc, char *argv[], unsigned long Flags,
478                            int nfile, t_filenm fnm[], int npargs, t_pargs *pa,
479                            int ndesc, const char **desc,
480                            int nbugs, const char **bugs,
481                            gmx_output_env_t **oenv)
482 {
483     /* This array should match the order of the enum in oenv.h */
484     const char *const xvg_formats[] = { "xmgrace", "xmgr", "none" };
485
486     // Lambda function to test the (local) Flags parameter against a bit mask.
487     auto isFlagSet = [Flags](unsigned long bits) {
488             return (Flags & bits) == bits;
489         };
490
491     try
492     {
493         double                          tbegin        = 0.0, tend = 0.0, tdelta = 0.0;
494         bool                            bBeginTimeSet = false, bEndTimeSet = false, bDtSet = false;
495         bool                            bView         = false;
496         int                             xvgFormat     = 0;
497         gmx::OptionsAdapter             adapter(*argc, argv);
498         gmx::Options                    options;
499         gmx::OptionsBehaviorCollection  behaviors(&options);
500         gmx::FileNameOptionManager      fileOptManager;
501
502         fileOptManager.disableInputOptionChecking(
503                 isFlagSet(PCA_NOT_READ_NODE) || isFlagSet(PCA_DISABLE_INPUT_FILE_CHECKING));
504         options.addManager(&fileOptManager);
505
506         if (isFlagSet(PCA_CAN_SET_DEFFNM))
507         {
508             fileOptManager.addDefaultFileNameOption(&options, "deffnm");
509         }
510         if (isFlagSet(PCA_CAN_BEGIN))
511         {
512             options.addOption(
513                     gmx::DoubleOption("b")
514                         .store(&tbegin).storeIsSet(&bBeginTimeSet).timeValue()
515                         .description("Time of first frame to read from trajectory (default unit %t)"));
516         }
517         if (isFlagSet(PCA_CAN_END))
518         {
519             options.addOption(
520                     gmx::DoubleOption("e")
521                         .store(&tend).storeIsSet(&bEndTimeSet).timeValue()
522                         .description("Time of last frame to read from trajectory (default unit %t)"));
523         }
524         if (isFlagSet(PCA_CAN_DT))
525         {
526             options.addOption(
527                     gmx::DoubleOption("dt")
528                         .store(&tdelta).storeIsSet(&bDtSet).timeValue()
529                         .description("Only use frame when t MOD dt = first time (default unit %t)"));
530         }
531         gmx::TimeUnit  timeUnit = gmx::TimeUnit_Default;
532         if (isFlagSet(PCA_TIME_UNIT))
533         {
534             std::shared_ptr<gmx::TimeUnitBehavior> timeUnitBehavior(
535                     new gmx::TimeUnitBehavior());
536             timeUnitBehavior->setTimeUnitStore(&timeUnit);
537             timeUnitBehavior->setTimeUnitFromEnvironment();
538             timeUnitBehavior->addTimeUnitOption(&options, "tu");
539             behaviors.addBehavior(timeUnitBehavior);
540         }
541         if (isFlagSet(PCA_CAN_VIEW))
542         {
543             options.addOption(
544                     gmx::BooleanOption("w").store(&bView)
545                         .description("View output [REF].xvg[ref], [REF].xpm[ref], "
546                                      "[REF].eps[ref] and [REF].pdb[ref] files"));
547         }
548
549         bool bXvgr = false;
550         for (int i = 0; i < nfile; i++)
551         {
552             bXvgr = bXvgr || (fnm[i].ftp == efXVG);
553         }
554         xvgFormat = gmx::getDefaultXvgFormat(xvg_formats);
555         if (bXvgr)
556         {
557             options.addOption(
558                     gmx::EnumIntOption("xvg").enumValue(xvg_formats)
559                         .store(&xvgFormat)
560                         .description("xvg plot formatting"));
561         }
562
563         /* Now append the program specific arguments */
564         for (int i = 0; i < nfile; i++)
565         {
566             adapter.filenmToOptions(&options, &fnm[i]);
567         }
568         for (int i = 0; i < npargs; i++)
569         {
570             adapter.pargsToOptions(&options, &pa[i]);
571         }
572
573         const gmx::CommandLineHelpContext *context =
574             gmx::GlobalCommandLineHelpContext::get();
575         if (context != nullptr)
576         {
577             GMX_RELEASE_ASSERT(gmx_node_rank() == 0,
578                                "Help output should be handled higher up and "
579                                "only get called only on the master rank");
580             gmx::CommandLineHelpWriter(options)
581                 .setHelpText(gmx::constArrayRefFromArray<const char *>(desc, ndesc))
582                 .setKnownIssues(gmx::constArrayRefFromArray(bugs, nbugs))
583                 .writeHelp(*context);
584             return FALSE;
585         }
586
587         /* Now parse all the command-line options */
588         gmx::CommandLineParser(&options).skipUnknown(isFlagSet(PCA_NOEXIT_ON_ARGS))
589             .parse(argc, argv);
590         behaviors.optionsFinishing();
591         options.finish();
592
593         /* set program name, command line, and default values for output options */
594         output_env_init(oenv, gmx::getProgramContext(),
595                         (time_unit_t)(timeUnit + 1), bView,
596                         (xvg_format_t)(xvgFormat + 1), 0);
597
598         /* Extract Time info from arguments */
599         if (bBeginTimeSet)
600         {
601             setTimeValue(TBEGIN, tbegin);
602         }
603         if (bEndTimeSet)
604         {
605             setTimeValue(TEND, tend);
606         }
607         if (bDtSet)
608         {
609             setTimeValue(TDELTA, tdelta);
610         }
611
612         adapter.copyValues(!isFlagSet(PCA_NOT_READ_NODE));
613
614         return TRUE;
615     }
616     GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR;
617 }