2 * This file is part of the GROMACS molecular simulation package.
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 by the GROMACS development team.
7 * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
8 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9 * and including many others, as listed in the AUTHORS file in the
10 * top-level source directory and at http://www.gromacs.org.
12 * GROMACS is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 2.1
15 * of the License, or (at your option) any later version.
17 * GROMACS is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with GROMACS; if not, see
24 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 * If you want to redistribute modifications to GROMACS, please
28 * consider that scientific software is very special. Version
29 * control is crucial - bugs must be traceable. We will be happy to
30 * consider code for inclusion in the official distribution, but
31 * derived work must not be called official GROMACS. Details are found
32 * in the README & COPYING files - if they are missing, get the
33 * official version at http://www.gromacs.org.
35 * To help us fund GROMACS development, we humbly ask that you cite
36 * the research papers on the package. Check out http://www.gromacs.org.
48 #include "gromacs/commandline/cmdlinehelpcontext.h"
49 #include "gromacs/commandline/cmdlinehelpwriter.h"
50 #include "gromacs/commandline/cmdlineparser.h"
51 #include "gromacs/fileio/oenv.h"
52 #include "gromacs/fileio/timecontrol.h"
53 #include "gromacs/options/basicoptions.h"
54 #include "gromacs/options/behaviorcollection.h"
55 #include "gromacs/options/filenameoption.h"
56 #include "gromacs/options/filenameoptionmanager.h"
57 #include "gromacs/options/options.h"
58 #include "gromacs/options/timeunitmanager.h"
59 #include "gromacs/utility/arrayref.h"
60 #include "gromacs/utility/basenetwork.h"
61 #include "gromacs/utility/classhelpers.h"
62 #include "gromacs/utility/enumerationhelpers.h"
63 #include "gromacs/utility/exceptions.h"
64 #include "gromacs/utility/fatalerror.h"
65 #include "gromacs/utility/gmxassert.h"
66 #include "gromacs/utility/path.h"
67 #include "gromacs/utility/programcontext.h"
68 #include "gromacs/utility/stringutil.h"
70 /* The source code in this file should be thread-safe.
71 Please keep it that way. */
73 int nenum(const char* const enumc[])
78 /* we *can* compare pointers directly here! */
79 while (enumc[i] && enumc[0] != enumc[i])
87 int opt2parg_int(const char* option, int nparg, t_pargs pa[])
91 for (i = 0; (i < nparg); i++)
93 if (strcmp(pa[i].option, option) == 0)
99 gmx_fatal(FARGS, "No integer option %s in pargs", option);
102 gmx_bool opt2parg_bool(const char* option, int nparg, t_pargs pa[])
106 for (i = 0; (i < nparg); i++)
108 if (strcmp(pa[i].option, option) == 0)
114 gmx_fatal(FARGS, "No boolean option %s in pargs", option);
119 real opt2parg_real(const char* option, int nparg, t_pargs pa[])
123 for (i = 0; (i < nparg); i++)
125 if (strcmp(pa[i].option, option) == 0)
131 gmx_fatal(FARGS, "No real option %s in pargs", option);
134 const char* opt2parg_str(const char* option, int nparg, t_pargs pa[])
138 for (i = 0; (i < nparg); i++)
140 if (strcmp(pa[i].option, option) == 0)
146 gmx_fatal(FARGS, "No string option %s in pargs", option);
149 gmx_bool opt2parg_bSet(const char* option, int nparg, const t_pargs* pa)
153 for (i = 0; (i < nparg); i++)
155 if (strcmp(pa[i].option, option) == 0)
161 gmx_fatal(FARGS, "No such option %s in pargs", option);
163 return FALSE; /* Too make some compilers happy */
166 const char* opt2parg_enum(const char* option, int nparg, t_pargs pa[])
170 for (i = 0; (i < nparg); i++)
172 if (strcmp(pa[i].option, option) == 0)
178 gmx_fatal(FARGS, "No such option %s in pargs", option);
181 /********************************************************************
182 * parse_common_args()
191 //! Names for XvgFormat
192 const gmx::EnumerationArray<XvgFormat, const char*> c_xvgFormatNames = {
193 { "xmgrace", "xmgr", "none" }
196 /*! \brief Returns the default xvg format, as modified by GMX_VIEW_XVG
197 * if that environment variable is set.
199 * \ingroup module_commandline
201 XvgFormat getDefaultXvgFormat()
203 const char* const select = getenv("GMX_VIEW_XVG");
204 if (select != nullptr)
206 for (XvgFormat c : keysOf(c_xvgFormatNames))
208 if (std::strcmp(select, c_xvgFormatNames[c]) == 0)
213 return XvgFormat::None;
215 return XvgFormat::Xmgrace;
219 * Conversion helper between t_pargs/t_filenm and Options.
221 * This class holds the necessary mapping between the old C structures and
222 * the new C++ options to allow copying values back after parsing for cases
223 * where the C++ options do not directly provide the type of value required for
226 * \ingroup module_commandline
232 * Initializes the adapter to convert from a specified command line.
234 * The command line is required, because t_pargs wants to return
235 * strings by reference to the original command line.
236 * OptionsAdapter creates a copy of the `argv` array (but not the
237 * strings) to make this possible, even if the parser removes
238 * options it has recognized.
240 OptionsAdapter(int argc, const char* const argv[]) : argv_(argv, argv + argc) {}
243 * Converts a t_filenm option into an Options option.
245 * \param options Options object to add the new option to.
246 * \param fnm t_filenm option to convert.
248 void filenmToOptions(Options* options, t_filenm* fnm);
250 * Converts a t_pargs option into an Options option.
252 * \param options Options object to add the new option to.
253 * \param pa t_pargs option to convert.
255 void pargsToOptions(Options* options, t_pargs* pa);
258 * Copies values back from options to t_pargs/t_filenm.
265 //! Creates a conversion helper for a given `t_filenm` struct.
266 explicit FileNameData(t_filenm* fnm) : fnm(fnm), optionInfo(nullptr) {}
268 //! t_filenm structure to receive the final values.
270 //! Option info object for the created FileNameOption.
271 FileNameOptionInfo* optionInfo;
272 //! Value storage for the created FileNameOption.
273 std::vector<std::string> values;
275 struct ProgramArgData
277 //! Creates a conversion helper for a given `t_pargs` struct.
278 explicit ProgramArgData(t_pargs* pa) :
279 pa(pa), optionInfo(nullptr), enumIndex(0), boolValue(false)
283 //! t_pargs structure to receive the final values.
285 //! Option info object for the created option.
286 OptionInfo* optionInfo;
287 //! Value storage for a non-enum StringOption (unused for other types).
288 std::string stringValue;
289 //! Value storage for an enum option (unused for other types).
291 //! Value storage for a BooleanOption (unused for other types).
295 std::vector<const char*> argv_;
296 // These are lists instead of vectors to avoid relocating existing
297 // objects in case the container is reallocated (the Options object
298 // contains pointes to members of the objects, which would get
300 std::list<FileNameData> fileNameOptions_;
301 std::list<ProgramArgData> programArgs_;
303 GMX_DISALLOW_COPY_AND_ASSIGN(OptionsAdapter);
306 void OptionsAdapter::filenmToOptions(Options* options, t_filenm* fnm)
308 const bool bRead = ((fnm->flag & ffREAD) != 0);
309 const bool bWrite = ((fnm->flag & ffWRITE) != 0);
310 const bool bOptional = ((fnm->flag & ffOPT) != 0);
311 const bool bLibrary = ((fnm->flag & ffLIB) != 0);
312 const bool bMultiple = ((fnm->flag & ffMULT) != 0);
313 const bool bMissing = ((fnm->flag & ffALLOW_MISSING) != 0);
314 const char* const name = (fnm->opt ? &fnm->opt[1] : &ftp2defopt(fnm->ftp)[1]);
315 const char* defName = fnm->fn;
317 if (defName == nullptr)
319 defName = ftp2defnm(fnm->ftp);
321 else if (Path::hasExtension(defName))
323 defType = fn2ftp(defName);
324 GMX_RELEASE_ASSERT(defType != efNR, "File name option specifies an invalid extension");
326 fileNameOptions_.emplace_back(fnm);
327 FileNameData& data = fileNameOptions_.back();
328 data.optionInfo = options->addOption(FileNameOption(name)
329 .storeVector(&data.values)
330 .defaultBasename(defName)
331 .defaultType(defType)
332 .legacyType(fnm->ftp)
333 .legacyOptionalBehavior()
334 .readWriteFlags(bRead, bWrite)
335 .required(!bOptional)
336 .libraryFile(bLibrary)
337 .multiValue(bMultiple)
338 .allowMissing(bMissing)
339 .description(ftp2desc(fnm->ftp)));
342 void OptionsAdapter::pargsToOptions(Options* options, t_pargs* pa)
344 const bool bHidden = startsWith(pa->desc, "HIDDEN");
345 const char* const name = &pa->option[1];
346 const char* const desc = (bHidden ? &pa->desc[6] : pa->desc);
347 programArgs_.emplace_back(pa);
348 ProgramArgData& data = programArgs_.back();
352 data.optionInfo = options->addOption(
353 IntegerOption(name).store(pa->u.i).description(desc).hidden(bHidden));
356 data.optionInfo = options->addOption(
357 Int64Option(name).store(pa->u.is).description(desc).hidden(bHidden));
361 options->addOption(RealOption(name).store(pa->u.r).description(desc).hidden(bHidden));
364 data.optionInfo = options->addOption(
365 RealOption(name).store(pa->u.r).timeValue().description(desc).hidden(bHidden));
369 const char* const defValue = (*pa->u.c != nullptr ? *pa->u.c : "");
370 data.optionInfo = options->addOption(StringOption(name)
371 .store(&data.stringValue)
372 .defaultValue(defValue)
378 data.optionInfo = options->addOption(BooleanOption(name)
379 .store(&data.boolValue)
380 .defaultValue(*pa->u.b)
385 data.optionInfo = options->addOption(
386 RealOption(name).store(*pa->u.rv).vector().description(desc).hidden(bHidden));
390 // TODO This is the only use of LegacyEnumOption. It
391 // exists to support dozens of analysis tools use that
392 // don't make sense to fix without either test coverage or
393 // automated refactoring. No new uses of LegacyEnumOption
395 const int defaultIndex = (pa->u.c[0] != nullptr ? nenum(pa->u.c) - 1 : 0);
396 data.optionInfo = options->addOption(LegacyEnumOption<int>(name)
397 .store(&data.enumIndex)
398 .defaultValue(defaultIndex)
399 .enumValueFromNullTerminatedArray(pa->u.c + 1)
405 GMX_THROW(NotImplementedError("Argument type not implemented"));
408 void OptionsAdapter::copyValues()
410 std::list<FileNameData>::const_iterator file;
411 for (file = fileNameOptions_.begin(); file != fileNameOptions_.end(); ++file)
413 if (file->optionInfo->isSet())
415 file->fnm->flag |= ffSET;
417 file->fnm->filenames = file->values;
419 std::list<ProgramArgData>::const_iterator arg;
420 for (arg = programArgs_.begin(); arg != programArgs_.end(); ++arg)
422 arg->pa->bSet = arg->optionInfo->isSet();
423 switch (arg->pa->type)
429 std::vector<const char*>::const_iterator pos =
430 std::find(argv_.begin(), argv_.end(), arg->stringValue);
431 GMX_RELEASE_ASSERT(pos != argv_.end(),
432 "String argument got a value not in argv");
433 *arg->pa->u.c = *pos;
437 case etBOOL: *arg->pa->u.b = arg->boolValue; break;
438 case etENUM: *arg->pa->u.c = arg->pa->u.c[arg->enumIndex + 1]; break;
440 // For other types, there is nothing type-specific to do.
450 gmx_bool parse_common_args(int* argc,
461 gmx_output_env_t** oenv)
463 // Lambda function to test the (local) Flags parameter against a bit mask.
464 auto isFlagSet = [Flags](unsigned long bits) { return (Flags & bits) == bits; };
468 double tbegin = 0.0, tend = 0.0, tdelta = 0.0;
469 bool bBeginTimeSet = false, bEndTimeSet = false, bDtSet = false;
471 gmx::OptionsAdapter adapter(*argc, argv);
472 gmx::Options options;
473 gmx::OptionsBehaviorCollection behaviors(&options);
474 gmx::FileNameOptionManager fileOptManager;
476 fileOptManager.disableInputOptionChecking(isFlagSet(PCA_DISABLE_INPUT_FILE_CHECKING));
477 options.addManager(&fileOptManager);
479 if (isFlagSet(PCA_CAN_SET_DEFFNM))
481 fileOptManager.addDefaultFileNameOption(&options, "deffnm");
483 if (isFlagSet(PCA_CAN_BEGIN))
486 gmx::DoubleOption("b").store(&tbegin).storeIsSet(&bBeginTimeSet).timeValue().description("Time of first frame to read from trajectory (default unit %t)"));
488 if (isFlagSet(PCA_CAN_END))
491 gmx::DoubleOption("e").store(&tend).storeIsSet(&bEndTimeSet).timeValue().description("Time of last frame to read from trajectory (default unit %t)"));
493 if (isFlagSet(PCA_CAN_DT))
495 options.addOption(gmx::DoubleOption("dt").store(&tdelta).storeIsSet(&bDtSet).timeValue().description(
496 "Only use frame when t MOD dt = first time (default unit %t)"));
498 gmx::TimeUnit timeUnit = gmx::TimeUnit::Default;
499 if (isFlagSet(PCA_TIME_UNIT))
501 std::shared_ptr<gmx::TimeUnitBehavior> timeUnitBehavior(new gmx::TimeUnitBehavior());
502 timeUnitBehavior->setTimeUnitStore(&timeUnit);
503 timeUnitBehavior->setTimeUnitFromEnvironment();
504 timeUnitBehavior->addTimeUnitOption(&options, "tu");
505 behaviors.addBehavior(timeUnitBehavior);
507 if (isFlagSet(PCA_CAN_VIEW))
509 options.addOption(gmx::BooleanOption("w").store(&bView).description(
510 "View output [REF].xvg[ref], [REF].xpm[ref], "
511 "[REF].eps[ref] and [REF].pdb[ref] files"));
515 for (int i = 0; i < nfile; i++)
517 bXvgr = bXvgr || (fnm[i].ftp == efXVG);
519 XvgFormat xvgFormat = gmx::getDefaultXvgFormat();
522 options.addOption(gmx::EnumOption<XvgFormat>("xvg")
523 .enumValue(gmx::c_xvgFormatNames)
525 .description("xvg plot formatting"));
528 /* Now append the program specific arguments */
529 for (int i = 0; i < nfile; i++)
531 adapter.filenmToOptions(&options, &fnm[i]);
533 for (int i = 0; i < npargs; i++)
535 adapter.pargsToOptions(&options, &pa[i]);
538 const gmx::CommandLineHelpContext* context = gmx::GlobalCommandLineHelpContext::get();
539 if (context != nullptr)
541 GMX_RELEASE_ASSERT(gmx_node_rank() == 0,
542 "Help output should be handled higher up and "
543 "only get called only on the master rank");
544 gmx::CommandLineHelpWriter(options)
545 .setHelpText(gmx::constArrayRefFromArray<const char*>(desc, ndesc))
546 .setKnownIssues(gmx::constArrayRefFromArray(bugs, nbugs))
547 .writeHelp(*context);
551 /* Now parse all the command-line options */
552 gmx::CommandLineParser(&options)
553 .skipUnknown(isFlagSet(PCA_NOEXIT_ON_ARGS))
554 .allowPositionalArguments(isFlagSet(PCA_NOEXIT_ON_ARGS))
556 behaviors.optionsFinishing();
559 /* set program name, command line, and default values for output options */
560 // NOLINTNEXTLINE(bugprone-misplaced-widening-cast)
561 output_env_init(oenv, gmx::getProgramContext(), timeUnit, bView, xvgFormat, 0);
563 /* Extract Time info from arguments */
566 setTimeValue(TimeControl::Begin, tbegin);
570 setTimeValue(TimeControl::End, tend);
574 setTimeValue(TimeControl::Delta, tdelta);
577 adapter.copyValues();
581 GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR