diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 51b1d77d99..f34ca612fa 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -57,9 +57,15 @@ jobs: - name: build run: cmake --build build + - name: interface tests + run: cd build && ctest -L cli --output-on-failure + - name: unit tests run: cd build && ctest -L unittest --output-on-failure --parallel 4 + - name: compatibility tests + run: cd build && ctest -L compatibility --output-on-failure + - name: binary tests run: cd build && ctest -L binary --output-on-failure # @@ -116,9 +122,15 @@ jobs: - name: build run: cmake --build build + - name: interface tests + run: cd build && ctest -L cli --output-on-failure + - name: unit tests run: cd build && ctest -L unittest --output-on-failure --parallel 4 + - name: compatibility tests + run: cd build && ctest -L compatibility --output-on-failure + - name: binary tests run: cd build && ctest -L binary --output-on-failure # @@ -171,9 +183,15 @@ jobs: - name: build run: cmake --build build + - name: interface tests + run: cd build && ctest -L cli --output-on-failure + - name: unit tests run: cd build && ctest -L unittest --output-on-failure --parallel 4 + - name: compatibility tests + run: cd build && ctest -L compatibility --output-on-failure + - name: binary tests run: cd build && ctest -L binary --output-on-failure # @@ -243,9 +261,15 @@ jobs: - name: build run: cmake --build build + - name: interface tests + run: cd build && ctest -L cli --output-on-failure + - name: unit tests run: cd build && ctest -L unittest --output-on-failure --parallel 4 + - name: compatibility tests + run: cd build && ctest -L compatibility --output-on-failure + - name: binary tests run: cd build && ctest -L binary --output-on-failure # diff --git a/cpp/cmd/connectomeedit.cpp b/cpp/cmd/connectomeedit.cpp index 45c1e2403a..8660d6fc68 100644 --- a/cpp/cmd/connectomeedit.cpp +++ b/cpp/cmd/connectomeedit.cpp @@ -34,12 +34,12 @@ void usage() { SYNOPSIS = "Perform basic operations on a connectome"; ARGUMENTS - + Argument ("input", "the input connectome.").type_text () + + Argument ("input", "the input connectome.").type_file_in() + Argument ("operation", "the operation to apply," " one of: " + join(operations, ", ") + ".").type_choice (operations) - + Argument ("output", "the output connectome.").type_text(); + + Argument ("output", "the output connectome.").type_file_out(); } // clang-format on diff --git a/cpp/cmd/dirstat.cpp b/cpp/cmd/dirstat.cpp index 2b69063373..81aec27075 100644 --- a/cpp/cmd/dirstat.cpp +++ b/cpp/cmd/dirstat.cpp @@ -88,11 +88,12 @@ void usage() { + Argument ("dirs", "the text file or image containing the directions.").type_file_in(); OPTIONS + // TODO This could be a different command-line argument type + Option ("output", "output selected metrics as a space-delimited list," "suitable for use in scripts." " This will produce one line of values per selected shell." " Valid metrics are as specified in the description above.") - + Argument ("list") + + Argument ("list").type_text() + DWI::ShellsOption + DWI::GradImportOptions(); diff --git a/cpp/cmd/dwi2fod.cpp b/cpp/cmd/dwi2fod.cpp index a69e5ce77b..bc32ee5465 100644 --- a/cpp/cmd/dwi2fod.cpp +++ b/cpp/cmd/dwi2fod.cpp @@ -115,7 +115,7 @@ void usage() { + Argument ("algorithm", "the algorithm to use for FOD estimation. " "(options are: " + join(algorithms, ",") + ")").type_choice (algorithms) + Argument ("dwi", "the input diffusion-weighted image").type_image_in() - + Argument ("response odf", "pairs of input tissue response and output ODF images").allow_multiple(); + + Argument ("response odf", "pairs of input tissue response and output ODF images").type_file_in().type_image_out().allow_multiple(); OPTIONS + DWI::GradImportOptions() diff --git a/cpp/cmd/fixel2peaks.cpp b/cpp/cmd/fixel2peaks.cpp index 54a2e0b136..961b697fce 100644 --- a/cpp/cmd/fixel2peaks.cpp +++ b/cpp/cmd/fixel2peaks.cpp @@ -47,8 +47,8 @@ void usage() { + Fixel::format_description; ARGUMENTS - + Argument ("in", "the input fixel information").type_various () - + Argument ("out", "the output peaks image").type_image_out (); + + Argument ("in", "the input fixel information").type_directory_in().type_image_in() + + Argument ("out", "the output peaks image").type_image_out(); OPTIONS + Option ("number", "maximum number of fixels in each voxel" diff --git a/cpp/cmd/fixelcfestats.cpp b/cpp/cmd/fixelcfestats.cpp index 238ce2d9b3..3e6bd312da 100644 --- a/cpp/cmd/fixelcfestats.cpp +++ b/cpp/cmd/fixelcfestats.cpp @@ -108,9 +108,7 @@ void usage() { + Argument ("contrast", "the contrast matrix," " specified as rows of weights").type_file_in () - // .type_various() rather than .type_directory_in() to catch people trying to - // pass a track file, and give a more informative error message - + Argument ("connectivity", "the fixel-fixel connectivity matrix").type_various () + + Argument ("connectivity", "the fixel-fixel connectivity matrix").type_directory_in() + Argument ("out_fixel_directory", "the output directory where results will be saved." " Will be created if it does not exist").type_text(); @@ -173,12 +171,6 @@ class SubjectFixelImport : public Math::Stats::SubjectDataImportBase { }; void run() { - if (Path::has_suffix(argument[4], ".tck")) - throw Exception("This version of fixelcfestats requires as input not a track file, but a " - "pre-calculated fixel-fixel connectivity matrix; in addition, input fixel " - "data must be pre-smoothed. Please check command / pipeline documentation " - "specific to this software version."); - const value_type cfe_dh = get_option_value("cfe_dh", Fixel::Filter::cfe_default_dh); const value_type cfe_e = get_option_value("cfe_e", Fixel::Filter::cfe_default_e); const value_type cfe_h = get_option_value("cfe_h", Fixel::Filter::cfe_default_h); diff --git a/cpp/cmd/fixelfilter.cpp b/cpp/cmd/fixelfilter.cpp index efc1525da7..ca2042912f 100644 --- a/cpp/cmd/fixelfilter.cpp +++ b/cpp/cmd/fixelfilter.cpp @@ -53,10 +53,10 @@ void usage() { + Fixel::format_description; ARGUMENTS - + Argument ("input", "the input: either a fixel data file, or a fixel directory (see Description)").type_various() + + Argument ("input", "the input: either a fixel data file, or a fixel directory (see Description)").type_image_in().type_directory_in() + Argument ("filter", "the filtering operation to perform;" " options are: " + join (filters, ", ")).type_choice (filters) - + Argument ("output", "the output: either a fixel data file, or a fixel directory (see Description)").type_various(); + + Argument ("output", "the output: either a fixel data file, or a fixel directory (see Description)").type_image_out().type_directory_out(); OPTIONS + Option ("matrix", "provide a fixel-fixel connectivity matrix" diff --git a/cpp/cmd/mrcalc.cpp b/cpp/cmd/mrcalc.cpp index c713664577..14f06d9fa1 100644 --- a/cpp/cmd/mrcalc.cpp +++ b/cpp/cmd/mrcalc.cpp @@ -406,7 +406,7 @@ ARGUMENTS + Argument ("operand", "an input image," " intensity value," " or special keyword" - " (see Description)").type_various().allow_multiple(); + " (see Description)").type_image_in().type_image_out().type_float().type_text().allow_multiple(); OPTIONS diff --git a/cpp/cmd/mrconvert.cpp b/cpp/cmd/mrconvert.cpp index d78886a4ef..eb77121528 100644 --- a/cpp/cmd/mrconvert.cpp +++ b/cpp/cmd/mrconvert.cpp @@ -234,7 +234,7 @@ void usage() { + Option ("copy_properties", "clear all generic properties" " and replace with the properties from the image / file specified.") - + Argument ("source").type_various() + + Argument ("source").type_image_in().type_file_in() + Stride::Options diff --git a/cpp/cmd/mtnormalise.cpp b/cpp/cmd/mtnormalise.cpp index b87d0a2170..a733024920 100644 --- a/cpp/cmd/mtnormalise.cpp +++ b/cpp/cmd/mtnormalise.cpp @@ -72,7 +72,7 @@ void usage() { ARGUMENTS + Argument("input output", "list of all input and output tissue compartment files" - " (see example usage).").type_various().allow_multiple(); + " (see example usage).").type_image_in().type_image_out().allow_multiple(); OPTIONS + Option("mask", "the mask defines the data used to compute the intensity normalisation." diff --git a/cpp/cmd/shconv.cpp b/cpp/cmd/shconv.cpp index 65b6f645ad..e4095ced2a 100644 --- a/cpp/cmd/shconv.cpp +++ b/cpp/cmd/shconv.cpp @@ -51,7 +51,7 @@ void usage() { + Math::SH::encoding_description; ARGUMENTS - + Argument ("odf response", "pairs of input ODF image and corresponding responses").allow_multiple() + + Argument ("odf response", "pairs of input ODF image and corresponding responses").type_image_in().type_file_in().allow_multiple() + Argument ("SH_out", "the output spherical harmonics coefficients image.").type_image_out(); OPTIONS diff --git a/cpp/cmd/tckconvert.cpp b/cpp/cmd/tckconvert.cpp index 26b8cd359b..fe157e8e37 100644 --- a/cpp/cmd/tckconvert.cpp +++ b/cpp/cmd/tckconvert.cpp @@ -60,8 +60,8 @@ void usage() { " output-0000.txt, output-0001.txt, output-0002.txt, ..."); ARGUMENTS - + Argument ("input", "the input track file.").type_various() - + Argument ("output", "the output track file.").type_file_out(); + + Argument ("input", "the input track file.").type_tracks_in().type_file_in().type_text() + + Argument ("output", "the output track file.").type_tracks_out().type_file_out(); OPTIONS + Option ("scanner2voxel", diff --git a/cpp/cmd/tckmap.cpp b/cpp/cmd/tckmap.cpp index f69f3d327e..c4cd8f0355 100644 --- a/cpp/cmd/tckmap.cpp +++ b/cpp/cmd/tckmap.cpp @@ -64,7 +64,7 @@ const OptionGroup OutputDimOption = OptionGroup ("Options for the dimensionality "(references an internal direction set)," " or a path to a text file containing a set of directions" " stored as azimuth/elevation pairs") - + Argument ("path").type_various() + + Argument ("path").type_file_in().type_integer() + Option ("tod", "generate a Track Orientation Distribution (TOD) in each voxel;" " need to specify the maximum spherical harmonic degree lmax to use" diff --git a/cpp/cmd/transformcalc.cpp b/cpp/cmd/transformcalc.cpp index cf2d48fcac..14b556a22a 100644 --- a/cpp/cmd/transformcalc.cpp +++ b/cpp/cmd/transformcalc.cpp @@ -47,7 +47,7 @@ void usage() { SYNOPSIS = "Perform calculations on linear transformation matrices"; ARGUMENTS - + Argument ("inputs", "the input(s) for the specified operation").allow_multiple() + + Argument ("inputs", "the input(s) for the specified operation").type_image_in().type_file_in().allow_multiple() + Argument ("operation", "the operation to perform;" " one of: " + join(operations, ", ") + " (see description section for details).").type_choice (operations) diff --git a/cpp/cmd/transformcompose.cpp b/cpp/cmd/transformcompose.cpp index 349548a633..c6a5b24c71 100644 --- a/cpp/cmd/transformcompose.cpp +++ b/cpp/cmd/transformcompose.cpp @@ -92,7 +92,7 @@ void usage() { + Argument ("output", "the output file" " (may be a linear transformation text file," " or a deformation warp field image," - " depending on usage)").type_various(); + " depending on usage)").type_file_out().type_image_out(); OPTIONS + Option ("template", "define the output grid defined by a template image") diff --git a/cpp/cmd/transformconvert.cpp b/cpp/cmd/transformconvert.cpp index d302b693a5..803751ca8e 100644 --- a/cpp/cmd/transformconvert.cpp +++ b/cpp/cmd/transformconvert.cpp @@ -56,7 +56,7 @@ void usage() { ""); ARGUMENTS - + Argument ("input", "the input(s) for the specified operation").allow_multiple() + + Argument ("input", "the input(s) for the specified operation").type_file_in().type_image_in().allow_multiple() + Argument ("operation", "the operation to perform;" " one of: " + join(operations, ", ")).type_choice (operations) + Argument ("output", "the output transformation matrix.").type_file_out (); diff --git a/cpp/core/app.cpp b/cpp/core/app.cpp index 926f14dd3d..03fc1eff4d 100644 --- a/cpp/core/app.cpp +++ b/cpp/core/app.cpp @@ -186,46 +186,7 @@ std::string underline(const std::string &text, bool ignore_whitespace = false) { } // namespace -const char *argtype_description(ArgType type) { - switch (type) { - case Boolean: - return ("boolean"); - case Integer: - return ("integer"); - case Float: - return ("float"); - case Text: - return ("string"); - case ArgFileIn: - return ("file in"); - case ArgFileOut: - return ("file out"); - case ArgDirectoryIn: - return ("directory in"); - case ArgDirectoryOut: - return ("directory out"); - case ImageIn: - return ("image in"); - case ImageOut: - return ("image out"); - case Choice: - return ("choice"); - case IntSeq: - return ("int seq"); - case FloatSeq: - return ("float seq"); - case TracksIn: - return ("tracks in"); - case TracksOut: - return ("tracks out"); - case Various: - return ("various"); - default: - return ("undefined"); - } -} - -std::string help_head(int format) { +std::string help_head(const bool format) { if (!format) { return std::string(NAME) + ": " + (project_version ? std::string("external MRtrix3 project, version ") + project_version + @@ -250,13 +211,13 @@ std::string help_head(int format) { (project_version ? "external MRtrix3 project" : "part of the MRtrix3 package") + "\n\n"; } -std::string help_synopsis(int format) { +std::string help_synopsis(const bool format) { if (!format) return SYNOPSIS; return bold("SYNOPSIS") + "\n\n" + paragraph("", SYNOPSIS, help_formatting.purpose_indents) + "\n"; } -std::string help_tail(int format) { +std::string help_tail(const bool format) { std::string retval; if (!format) return retval; @@ -271,7 +232,7 @@ std::string help_tail(int format) { }(); } -std::string usage_syntax(int format) { +std::string usage_syntax(const bool format) { std::string s = "USAGE"; if (format) s = bold(s) + "\n\n "; @@ -281,16 +242,16 @@ std::string usage_syntax(int format) { for (size_t i = 0; i < ARGUMENTS.size(); ++i) { - if (ARGUMENTS[i].flags & Optional) + if (ARGUMENTS[i].flags.optional()) s += " ["; s += std::string(" ") + ARGUMENTS[i].id; - if (ARGUMENTS[i].flags & AllowMultiple) { - if (!(ARGUMENTS[i].flags & Optional)) + if (ARGUMENTS[i].flags.allow_multiple()) { + if (ARGUMENTS[i].flags.required()) s += std::string(" [ ") + ARGUMENTS[i].id; s += " ..."; } - if (ARGUMENTS[i].flags & (Optional | AllowMultiple)) + if (ARGUMENTS[i].flags.any()) s += " ]"; } return s + "\n\n"; @@ -307,7 +268,7 @@ Description &Description::operator+(const char *const text[]) { return *this; } -std::string Description::syntax(int format) const { +std::string Description::syntax(const bool format) const { if (!size()) return std::string(); std::string s; @@ -323,7 +284,7 @@ Example::Example(const std::string &title, const std::string &code, const std::s Example::operator std::string() const { return title + ": $ " + code + " " + description; } -std::string Example::syntax(int format) const { +std::string Example::syntax(const bool format) const { std::string s = paragraph("", format ? underline(title + ":") + "\n" : title + ": ", help_formatting.purpose_indents); s += std::string(help_formatting.example_indent, ' ') + "$ " + code + "\n"; if (!description.empty()) @@ -338,7 +299,7 @@ ExampleList &ExampleList::operator+(const Example &example) { return *this; } -std::string ExampleList::syntax(int format) const { +std::string ExampleList::syntax(const bool format) const { if (!size()) return std::string(); std::string s; @@ -349,7 +310,7 @@ std::string ExampleList::syntax(int format) const { return s; } -std::string Argument::syntax(int format) const { +std::string Argument::syntax(const bool format) const { std::string retval = paragraph((format ? underline(id, true) : id), desc, help_formatting.arg_indents); if (format) retval += "\n"; @@ -361,14 +322,14 @@ ArgumentList &ArgumentList::operator+(const Argument &argument) { return *this; } -std::string ArgumentList::syntax(int format) const { +std::string ArgumentList::syntax(const bool format) const { std::string s; for (size_t i = 0; i < size(); ++i) s += (*this)[i].syntax(format); return s + "\n"; } -std::string Option::syntax(int format) const { +std::string Option::syntax(const bool format) const { std::string opt("-"); opt += id; @@ -378,7 +339,7 @@ std::string Option::syntax(int format) const { for (size_t i = 0; i < size(); ++i) opt += std::string(" ") + (*this)[i].id; - if (format && (flags & AllowMultiple)) + if (format && flags.allow_multiple()) opt += " (multiple uses permitted)"; if (format) @@ -388,16 +349,18 @@ std::string Option::syntax(int format) const { return opt; } -std::string OptionGroup::header(int format) const { return format ? bold(name) + "\n\n" : std::string(name) + ":\n"; } +std::string OptionGroup::header(const bool format) const { + return format ? bold(name) + "\n\n" : std::string(name) + ":\n"; +} -std::string OptionGroup::contents(int format) const { +std::string OptionGroup::contents(const bool format) const { std::string s; for (size_t i = 0; i < size(); ++i) s += (*this)[i].syntax(format); return s; } -std::string OptionGroup::footer(int format) { return format ? "" : "\n"; } +std::string OptionGroup::footer(const bool format) { return format ? "" : "\n"; } OptionList &OptionList::operator+(const OptionGroup &option_group) { push_back(option_group); @@ -420,7 +383,7 @@ OptionList &OptionList::operator+(const Argument &argument) { return *this; } -std::string OptionList::syntax(int format) const { +std::string OptionList::syntax(const bool format) const { std::vector group_names; for (size_t i = 0; i < size(); ++i) { if (std::find(group_names.begin(), group_names.end(), (*this)[i].name) == group_names.end()) @@ -446,71 +409,42 @@ std::string OptionList::syntax(int format) const { std::string Argument::usage() const { std::ostringstream stream; - stream << "ARGUMENT " << id << " " << (flags & Optional ? '1' : '0') << " " << (flags & AllowMultiple ? '1' : '0') - << " "; - - switch (type) { - case Undefined: - assert(0); - break; - case Boolean: - stream << "BOOL"; - break; - case Integer: { - const auto int_range = std::get(limits); - stream << "INT " << int_range.min << " " << int_range.max; - break; - } - case Float: { - const auto float_range = std::get(this->limits); - stream << "FLOAT " << float_range.min << " " << float_range.max; - break; - } - case Text: - stream << "TEXT"; - break; - case ArgFileIn: - stream << "FILEIN"; - break; - case ArgFileOut: - stream << "FILEOUT"; - break; - case ArgDirectoryIn: - stream << "DIRIN"; - break; - case ArgDirectoryOut: - stream << "DIROUT"; - break; - case Choice: { - stream << "CHOICE"; - const auto &choices = std::get>(limits); + stream << "ARGUMENT " << id // + << " " << (flags.optional() ? '1' : '0') // + << " " << (flags.allow_multiple() ? '1' : '0'); // + + if (types[ArgTypeFlags::Text]) + stream << " TEXT"; + if (types[ArgTypeFlags::Boolean]) + stream << " BOOL"; + if (types[ArgTypeFlags::Integer]) + stream << " INT " << int_limits.min() << " " << int_limits.max(); + if (types[ArgTypeFlags::Float]) + stream << " FLOAT " << float_limits.min() << " " << float_limits.max(); + if (types[ArgTypeFlags::FileIn]) + stream << " FILEIN"; + if (types[ArgTypeFlags::FileOut]) + stream << " FILEOUT"; + if (types[ArgTypeFlags::DirectoryIn]) + stream << " DIRIN"; + if (types[ArgTypeFlags::DirectoryOut]) + stream << " DIROUT"; + if (types[ArgTypeFlags::ImageIn]) + stream << " IMAGEIN"; + if (types[ArgTypeFlags::ImageOut]) + stream << " IMAGEOUT"; + if (types[ArgTypeFlags::IntSeq]) + stream << " ISEQ"; + if (types[ArgTypeFlags::FloatSeq]) + stream << " FSEQ"; + if (types[ArgTypeFlags::TracksIn]) + stream << " TRACKSIN"; + if (types[ArgTypeFlags::TracksOut]) + stream << " TRACKSOUT"; + if (types[ArgTypeFlags::Choice]) { + stream << " CHOICE"; for (const std::string &p : choices) stream << " " << p; - break; - } - case ImageIn: - stream << "IMAGEIN"; - break; - case ImageOut: - stream << "IMAGEOUT"; - break; - case IntSeq: - stream << "ISEQ"; - break; - case FloatSeq: - stream << "FSEQ"; - break; - case TracksIn: - stream << "TRACKSIN"; - break; - case TracksOut: - stream << "TRACKSOUT"; - break; - case Various: - stream << "VARIOUS"; - break; - default: - assert(0); } stream << "\n"; if (!desc.empty()) @@ -521,7 +455,7 @@ std::string Argument::usage() const { std::string Option::usage() const { std::ostringstream stream; - stream << "OPTION " << id << " " << (flags & Optional ? '1' : '0') << " " << (flags & AllowMultiple ? '1' : '0') + stream << "OPTION " << id << " " << (flags.optional() ? '1' : '0') << " " << (flags.allow_multiple() ? '1' : '0') << "\n"; if (!desc.empty()) @@ -533,7 +467,7 @@ std::string Option::usage() const { return stream.str(); } -std::string get_help_string(int format) { +std::string get_help_string(const bool format) { return help_head(format) + help_synopsis(format) + usage_syntax(format) + ARGUMENTS.syntax(format) + DESCRIPTION.syntax(format) + EXAMPLES.syntax(format) + OPTIONS.syntax(format) + __standard_options.header(format) + __standard_options.contents(format) + __standard_options.footer(format) + @@ -631,16 +565,16 @@ std::string markdown_usage() { // Syntax line: for (size_t i = 0; i < ARGUMENTS.size(); ++i) { - if (ARGUMENTS[i].flags & Optional) + if (ARGUMENTS[i].flags.optional()) s += "["; s += std::string(" ") + ARGUMENTS[i].id; - if (ARGUMENTS[i].flags & AllowMultiple) { - if (!(ARGUMENTS[i].flags & Optional)) + if (ARGUMENTS[i].flags.allow_multiple()) { + if (ARGUMENTS[i].flags.required()) s += std::string(" [ ") + ARGUMENTS[i].id; s += " ..."; } - if (ARGUMENTS[i].flags & (Optional | AllowMultiple)) + if (ARGUMENTS[i].flags.any()) s += " ]"; } s += "\n\n"; @@ -677,7 +611,7 @@ std::string markdown_usage() { for (size_t a = 0; a < opt.size(); ++a) f += std::string(" ") + opt[a].id; f += "**"; - if (opt.flags & AllowMultiple) + if (opt.flags.allow_multiple()) f += " *(multiple uses permitted)*"; f += std::string("
") + opt.desc + "\n\n"; return f; @@ -736,16 +670,16 @@ std::string restructured_text_usage() { // Syntax line: for (size_t i = 0; i < ARGUMENTS.size(); ++i) { - if (ARGUMENTS[i].flags & Optional) + if (ARGUMENTS[i].flags.optional()) s += "["; s += std::string(" ") + ARGUMENTS[i].id; - if (ARGUMENTS[i].flags & AllowMultiple) { - if (!(ARGUMENTS[i].flags & Optional)) + if (ARGUMENTS[i].flags.allow_multiple()) { + if (ARGUMENTS[i].flags.required()) s += std::string(" [ ") + ARGUMENTS[i].id; s += " ..."; } - if (ARGUMENTS[i].flags & (Optional | AllowMultiple)) + if (ARGUMENTS[i].flags.any()) s += " ]"; } s += "\n\n"; @@ -803,7 +737,7 @@ std::string restructured_text_usage() { for (size_t a = 0; a < opt.size(); ++a) f += std::string(" ") + opt[a].id; f += std::string("** "); - if (opt.flags & AllowMultiple) + if (opt.flags.allow_multiple()) f += "*(multiple uses permitted)* "; auto desc = split_lines(opt.desc, false); f += escape_special(desc[0]); @@ -971,15 +905,15 @@ void parse() { size_t num_args_required = 0; size_t num_optional_arguments = 0; - ArgFlags flags = None; + ArgModifierFlags flags; for (size_t i = 0; i < ARGUMENTS.size(); ++i) { - if (ARGUMENTS[i].flags) { - if (flags && flags != ARGUMENTS[i].flags) - throw Exception("FIXME: all arguments declared optional() or allow_multiple() should have matching flags in " - "command-line syntax"); + if (ARGUMENTS[i].flags.any()) { + if (flags.any() && flags != ARGUMENTS[i].flags) + throw Exception("FIXME: all arguments declared optional() or allow_multiple()" + " should have matching flags in command-line syntax"); flags = ARGUMENTS[i].flags; ++num_optional_arguments; - if (!(flags & Optional)) + if (!flags.optional()) ++num_args_required; } else ++num_args_required; @@ -1024,7 +958,7 @@ void parse() { size_t num_arg_per_multi = num_optional_arguments ? num_extra_arguments / num_optional_arguments : 0; if (num_arg_per_multi * num_optional_arguments != num_extra_arguments) throw Exception("number of optional arguments provided are not equal for all arguments"); - if (!(flags & Optional)) + if (flags.required()) ++num_arg_per_multi; // assign arguments to their corresponding definitions: @@ -1032,7 +966,7 @@ void parse() { if (n == next) { if (n) ++index; - if (ARGUMENTS[index].flags != None) + if (ARGUMENTS[index].flags.any()) next = n + num_arg_per_multi; else ++next; @@ -1048,10 +982,10 @@ void parse() { if (option[k].opt == &OPTIONS[i][j]) count++; - if (count < 1 && !(OPTIONS[i][j].flags & Optional)) + if (count < 1 && OPTIONS[i][j].flags.required()) throw Exception(std::string("mandatory option \"-") + OPTIONS[i][j].id + "\" must be specified"); - if (count > 1 && !(OPTIONS[i][j].flags & AllowMultiple)) + if (count > 1 && !OPTIONS[i][j].flags.allow_multiple()) throw Exception(std::string("multiple instances of option \"-") + OPTIONS[i][j].id + "\" are not allowed"); } } @@ -1074,63 +1008,130 @@ void parse() { // check for the existence of all specified input files (including optional ones that have been provided) // if necessary, also check for pre-existence of any output files with known paths // (if the output is e.g. given as a prefix, the argument should be flagged as type_text()) + // note that if an argument has multiple possible types, some checks can't be enforced for (const auto &i : argument) { const std::string text = std::string(i); - if (i.arg->type == ArgFileIn || i.arg->type == TracksIn) { - if (!Path::exists(text)) - throw Exception("required input file \"" + text + "\" not found"); - if (!Path::is_file(text)) - throw Exception("required input \"" + text + "\" is not a file"); + assert(i.arg->types.any()); + { + ArgTypeFlags types_not_input_file(i.arg->types); + types_not_input_file.reset(ArgTypeFlags::FileIn); + types_not_input_file.reset(ArgTypeFlags::TracksIn); + if (!types_not_input_file.any()) { + if (!Path::exists(text)) + throw Exception("required input file \"" + text + "\" not found"); + if (!Path::is_file(text)) + throw Exception("required input \"" + text + "\" is not a file"); + } } - if (i.arg->type == ArgDirectoryIn) { - if (!Path::exists(text)) - throw Exception("required input directory \"" + text + "\" not found"); - if (!Path::is_dir(text)) - throw Exception("required input \"" + text + "\" is not a directory"); + { + ArgTypeFlags types_not_input_directory(i.arg->types); + types_not_input_directory.reset(ArgTypeFlags::DirectoryIn); + if (!types_not_input_directory.any()) { + if (!Path::exists(text)) + throw Exception("required input directory \"" + text + "\" not found"); + if (!Path::is_dir(text)) + throw Exception("required input \"" + text + "\" is not a directory"); + } } - if (i.arg->type == ArgFileOut || i.arg->type == TracksOut) { - if (text.find_last_of(PATH_SEPARATORS) == text.size() - 1) - throw Exception("output path \"" + std::string(i) + - "\" is not a valid file path (ends with directory path separator)"); - check_overwrite(text); + { + ArgTypeFlags types_not_output_file(i.arg->types); + types_not_output_file.reset(ArgTypeFlags::FileOut); + types_not_output_file.reset(ArgTypeFlags::TracksOut); + if (!types_not_output_file.any()) { + if (text.find_last_of(PATH_SEPARATORS) == text.size() - 1) + throw Exception("output path \"" + std::string(i) + "\" is not a valid file path" + + " (ends with directory path separator)"); + } + } + { + ArgTypeFlags types_not_output_filesystem(i.arg->types); + types_not_output_filesystem.reset(ArgTypeFlags::FileOut); + types_not_output_filesystem.reset(ArgTypeFlags::DirectoryOut); + types_not_output_filesystem.reset(ArgTypeFlags::TracksOut); + if (!types_not_output_filesystem.any()) + check_overwrite(text); + } + { + ArgTypeFlags types_not_input_tractogram(i.arg->types); + types_not_input_tractogram.reset(ArgTypeFlags::TracksIn); + if (!types_not_input_tractogram.any()) { + if (!Path::has_suffix(text, ".tck")) + throw Exception("input file \"" + text + "\" is not a valid track file"); + } + } + { + ArgTypeFlags types_not_output_tractogram(i.arg->types); + types_not_output_tractogram.reset(ArgTypeFlags::TracksOut); + if (!types_not_output_tractogram.any()) { + if (!Path::has_suffix(text, ".tck")) + throw Exception("output track file \"" + text + "\" must use the .tck suffix"); + } } - if (i.arg->type == ArgDirectoryOut) - check_overwrite(text); - if (i.arg->type == TracksIn && !Path::has_suffix(text, ".tck")) - throw Exception("input file \"" + text + "\" is not a valid track file"); - if (i.arg->type == TracksOut && !Path::has_suffix(text, ".tck")) - throw Exception("output track file \"" + text + "\" must use the .tck suffix"); } for (const auto &i : option) { for (size_t j = 0; j != i.opt->size(); ++j) { const Argument &arg = i.opt->operator[](j); + assert(arg.types.any()); const std::string text = std::string(i.args[j]); - if (arg.type == ArgFileIn || arg.type == TracksIn) { - if (!Path::exists(text)) - throw Exception("input file \"" + text + "\" for option \"-" + std::string(i.opt->id) + "\" not found"); - if (!Path::is_file(text)) - throw Exception("input \"" + text + "\" for option \"-" + std::string(i.opt->id) + "\" is not a file"); + { + ArgTypeFlags types_not_input_file(arg.types); + types_not_input_file.reset(ArgTypeFlags::FileIn); + types_not_input_file.reset(ArgTypeFlags::TracksIn); + if (!types_not_input_file.any()) { + if (!Path::exists(text)) + throw Exception("input file \"" + text + "\" for option \"-" + std::string(i.opt->id) + "\" not found"); + if (!Path::is_file(text)) + throw Exception("input \"" + text + "\" for option \"-" + std::string(i.opt->id) + "\" is not a file"); + } } - if (arg.type == ArgDirectoryIn) { - if (!Path::exists(text)) - throw Exception("input directory \"" + text + "\" for option \"-" + std::string(i.opt->id) + "\" not found"); - if (!Path::is_dir(text)) - throw Exception("input \"" + text + "\" for option \"-" + std::string(i.opt->id) + "\" is not a directory"); + { + ArgTypeFlags types_not_input_directory(arg.types); + types_not_input_directory.reset(ArgTypeFlags::DirectoryIn); + if (!types_not_input_directory.any()) { + if (!Path::exists(text)) + throw Exception("input directory \"" + text + "\"" + " for option \"-" + std::string(i.opt->id) + + "\" not found"); + if (!Path::is_dir(text)) + throw Exception("input \"" + text + "\"" + " for option \"-" + std::string(i.opt->id) + + "\" is not a directory"); + } } - if (arg.type == ArgFileOut || arg.type == TracksOut) { - if (text.find_last_of(PATH_SEPARATORS) == text.size() - 1) - throw Exception("output path \"" + text + "\" for option \"-" + std::string(i.opt->id) + - "\" is not a valid file path (ends with directory path separator)"); - check_overwrite(text); + { + ArgTypeFlags types_not_output_file(arg.types); + types_not_output_file.reset(ArgTypeFlags::FileOut); + types_not_output_file.reset(ArgTypeFlags::TracksOut); + if (!types_not_output_file.any()) { + if (text.find_last_of(PATH_SEPARATORS) == text.size() - 1) + throw Exception("output path \"" + text + "\"" + " for option \"-" + std::string(i.opt->id) + "\"" + + " is not a valid file path (ends with directory path separator)"); + } + } + { + ArgTypeFlags types_not_output_filesystem(arg.types); + types_not_output_filesystem.reset(ArgTypeFlags::FileOut); + types_not_output_filesystem.reset(ArgTypeFlags::DirectoryOut); + types_not_output_filesystem.reset(ArgTypeFlags::TracksOut); + if (!types_not_output_filesystem.any()) + check_overwrite(text); + } + { + ArgTypeFlags types_not_input_tractogram(arg.types); + types_not_input_tractogram.reset(ArgTypeFlags::TracksIn); + if (!types_not_input_tractogram.any()) { + if (!Path::has_suffix(text, ".tck")) + throw Exception("input file \"" + text + "\"" + " for option \"-" + std::string(i.opt->id) + "\"" + + " is not a valid track file"); + } + } + { + ArgTypeFlags types_not_output_tractogram(arg.types); + types_not_output_tractogram.reset(ArgTypeFlags::TracksOut); + if (!types_not_output_tractogram.any()) { + if (!Path::has_suffix(text, ".tck")) + throw Exception("output track file \"" + text + "\"" + " for option \"-" + std::string(i.opt->id) + "\"" + + " must use the .tck suffix"); + } } - if (arg.type == ArgDirectoryOut) - check_overwrite(text); - if (arg.type == TracksIn && !Path::has_suffix(text, ".tck")) - throw Exception("input file \"" + text + "\" for option \"-" + std::string(i.opt->id) + - "\" is not a valid track file"); - if (arg.type == TracksOut && !Path::has_suffix(text, ".tck")) - throw Exception("output track file \"" + text + "\" for option \"-" + std::string(i.opt->id) + - "\" must use the .tck suffix"); } } @@ -1205,13 +1206,38 @@ const std::vector get_options(const std::string &name) { } int64_t App::ParsedArgument::as_int() const { - if (arg->type == Integer) { - // Check to see if there are any alpha characters in here - // - If a single character at the end, use as integer multiplier - // - Unless there's a dot point before the multiplier; in which case, - // parse the number as a float, multiply, then cast to integer - // - If a single 'e' or 'E' in the middle, parse as float and convert to integer + std::string as_choice_msg; + + if (arg->types[ArgTypeFlags::Choice]) { + const std::string selection = lowercase(p); + auto it = std::find(arg->choices.begin(), arg->choices.end(), selection); + if (it == arg->choices.end()) { + std::string msg = std::string("unexpected value supplied for "); + if (opt != nullptr) + msg += std::string("option \"") + opt->id; + else + msg += std::string("argument \"") + arg->id; + msg += std::string("\" "); + as_choice_msg = std::string("received \"" + std::string(p) + "\"; "); + as_choice_msg += std::string("valid choices are: ") + join(arg->choices, ", "); + msg += "(" + as_choice_msg + ")"; + if (!arg->types[ArgTypeFlags::Integer]) + throw Exception(msg); + } else { + return static_cast(std::distance(arg->choices.begin(), it)); + } + } + + assert(arg->types[ArgTypeFlags::Integer]); + + // Check to see if there are any alpha characters in here + // - If a single character at the end, use as integer multiplier + // - Unless there's a dot point before the multiplier; in which case, + // parse the number as a float, multiply, then cast to integer + // - If a single 'e' or 'E' in the middle, parse as float and convert to integer + std::string as_int_msg; + try { size_t alpha_count = 0; bool alpha_is_last = false; bool contains_dotpoint = false; @@ -1228,7 +1254,7 @@ int64_t App::ParsedArgument::as_int() const { contains_dotpoint = true; } if (alpha_count > 1) - throw Exception("error converting string " + str(p) + " to integer: too many letters"); + throw Exception("too many letters"); int64_t retval = 0; if (alpha_count) { if (alpha_is_last) { @@ -1254,7 +1280,7 @@ int64_t App::ParsedArgument::as_int() const { multiplier = 1000000000000; break; default: - throw Exception("error converting string " + str(p) + " to integer: unexpected postfix \'" + postfix + "\'"); + throw Exception(std::string("unexpected postfix \'") + postfix + "\'"); } if (contains_dotpoint) { const default_type prefix = to(num); @@ -1266,57 +1292,57 @@ int64_t App::ParsedArgument::as_int() const { const default_type as_float = to(p); retval = std::round(as_float); } else { - throw Exception("error converting string " + str(p) + " to integer: unexpected character"); + throw Exception("unexpected character"); } } else { retval = to(p); } - const auto range = std::get(arg->limits); - if (retval < range.min || retval > range.max) { - std::string msg("value supplied for "); - if (opt) - msg += std::string("option \"") + opt->id; - else - msg += std::string("argument \"") + arg->id; - msg += "\" is out of bounds (valid range: " + str(range.min) + " to " + str(range.max) + - ", value supplied: " + str(retval) + ")"; - throw Exception(msg); - } + if (retval < arg->int_limits.min() || retval > arg->int_limits.max()) + throw Exception(std::string("out of bounds") // + + " (valid range: " + str(arg->int_limits.min()) // + + " to " + str(arg->int_limits.max()) + ";" // + + " value supplied: " + str(retval) + ")"); // return retval; + } catch (Exception &e_int) { + as_int_msg = e_int[0]; + if (!arg->types[ArgTypeFlags::Choice]) + throw Exception("unable to parse string " + str(p) + " supplied for " + + (opt == nullptr ? std::string("argument \"") + arg->id : std::string("option \"") + opt->id) + + " as integer: " + as_int_msg); } - if (arg->type == Choice) { - std::string selection = lowercase(p); - const auto &choices = std::get>(arg->limits); - auto it = std::find(choices.begin(), choices.end(), selection); - if (it == choices.end()) { - std::string msg = std::string("unexpected value supplied for "); - if (opt != nullptr) - msg += std::string("option \"") + opt->id; - else - msg += std::string("argument \"") + arg->id; - msg += std::string("\" (received \"" + std::string(p) + "\"; valid choices are: ") + join(choices, ", ") + ")"; - throw Exception(msg); - } - return static_cast(std::distance(choices.begin(), it)); - } - assert(0); - return (0); + Exception full_msg(std::string("Unable to interpret value supplied for ") + + (opt == nullptr ? std::string("argument \"") + arg->id : std::string("option \"") + opt->id) + + " as either integer or choice selection"); + full_msg.push_back("Error when interpreted as choice selection:"); + full_msg.push_back(as_choice_msg); + full_msg.push_back("Error when interpreted as integer:"); + full_msg.push_back(as_int_msg); + throw full_msg; +} + +uint64_t App::ParsedArgument::as_uint() const { + const int64_t signed_value = as_int(); + if (signed_value < 0) + throw Exception("Attempting to interpret negative user-specified value (" // + + str(signed_value) // + + " as unsigned integer"); // + return uint64_t(signed_value); } default_type App::ParsedArgument::as_float() const { - assert(arg->type == Float); + assert(arg->types[ArgTypeFlags::Float]); const default_type retval = to(p); - const auto range = std::get(arg->limits); - if (retval < range.min || retval > range.max) { + if (retval < arg->float_limits.min() || retval > arg->float_limits.max()) { std::string msg("value supplied for "); if (opt) msg += std::string("option \"") + opt->id; else msg += std::string("argument \"") + arg->id; - msg += "\" is out of bounds (valid range: " + str(range.min) + " to " + str(range.max) + - ", value supplied: " + str(retval) + ")"; + msg += "\" is out of bounds"; + msg += " (valid range: " + str(arg->float_limits.min()) + " to " + str(arg->float_limits.max()) + ";"; + msg += " value supplied: " + str(retval) + ")"; throw Exception(msg); } @@ -1324,7 +1350,7 @@ default_type App::ParsedArgument::as_float() const { } std::vector ParsedArgument::as_sequence_int() const { - assert(arg->type == IntSeq); + assert(arg->types[ArgTypeFlags::IntSeq]); try { return parse_ints(p); } catch (Exception &e) { @@ -1334,7 +1360,7 @@ std::vector ParsedArgument::as_sequence_int() const { } std::vector ParsedArgument::as_sequence_uint() const { - assert(arg->type == IntSeq); + assert(arg->types[ArgTypeFlags::IntSeq]); try { return parse_ints(p); } catch (Exception &e) { @@ -1344,7 +1370,7 @@ std::vector ParsedArgument::as_sequence_uint() const { } std::vector ParsedArgument::as_sequence_float() const { - assert(arg->type == FloatSeq); + assert(arg->types[ArgTypeFlags::FloatSeq]); try { return parse_floats(p); } catch (Exception &e) { @@ -1383,17 +1409,16 @@ ParsedOption::ParsedOption(const Option *option, const std::vector const auto &p = arguments[i]; if (!starts_with_dash(p)) continue; - if (((*option)[i].type == ImageIn || (*option)[i].type == ImageOut) && is_dash(arguments[i])) - continue; - if ((*option)[i].type == Integer || (*option)[i].type == Float || (*option)[i].type == IntSeq || - (*option)[i].type == FloatSeq || (*option)[i].type == Various) + if ((*option)[i].types[ArgTypeFlags::ImageIn] || (*option)[i].types[ArgTypeFlags::ImageOut] || + (*option)[i].types[ArgTypeFlags::Integer] || (*option)[i].types[ArgTypeFlags::Float] || + (*option)[i].types[ArgTypeFlags::IntSeq] || (*option)[i].types[ArgTypeFlags::FloatSeq]) continue; WARN(std::string("Value \"") + arguments[i] + "\" is being used as " + ((option->size() == 1) ? "the expected argument " : ("one of the " + str(option->size()) + " expected arguments ")) + - "for option \"-" + option->id + "\", yet this itself looks like a separate command-line option; " + + "for option \"-" + option->id + "\"," + " yet this itself looks like a separate command-line option; " + "the requisite input" + ((option->size() == 1) ? " " : "s ") + "to command-line option \"-" + option->id + - "\" may have been erroneously omitted, which may cause " + "other command-line parsing errors"); + "\" may have been erroneously omitted, which may cause other command-line parsing errors"); } } diff --git a/cpp/core/app.h b/cpp/core/app.h index 98401bcfb8..4df2902692 100644 --- a/cpp/core/app.h +++ b/cpp/core/app.h @@ -51,8 +51,6 @@ extern std::vector raw_arguments_list; extern const char *project_version; extern const char *project_build_date; -const char *argtype_description(ArgType type); - struct HelpFormatting { struct Indents { ssize_t header; @@ -71,10 +69,10 @@ extern const std::string help_command; extern const std::string core_reference; -std::string help_head(int format); -std::string help_synopsis(int format); -std::string help_tail(int format); -std::string usage_syntax(int format); +std::string help_head(const bool format); +std::string help_synopsis(const bool format); +std::string help_tail(const bool format); +std::string usage_syntax(const bool format); //! \addtogroup CmdParse // @{ @@ -86,7 +84,7 @@ class Description : public std::vector { Description &operator+(const char *const text[]); - std::string syntax(int format) const; + std::string syntax(const bool format) const; }; //! object for storing a single example command usage @@ -96,7 +94,7 @@ class Example { const std::string title, code, description; operator std::string() const; - std::string syntax(int format) const; + std::string syntax(const bool format) const; }; //! a class to hold the list of Example's @@ -104,7 +102,7 @@ class ExampleList : public std::vector { public: ExampleList &operator+(const Example &example); - std::string syntax(int format) const; + std::string syntax(const bool format) const; }; //! a class to hold the list of Argument's @@ -112,7 +110,7 @@ class ArgumentList : public std::vector { public: ArgumentList &operator+(const Argument &argument); - std::string syntax(int format) const; + std::string syntax(const bool format) const; }; //! a class to hold the list of option groups @@ -126,7 +124,7 @@ class OptionList : public std::vector { OptionGroup &back(); - std::string syntax(int format) const; + std::string syntax(const bool format) const; }; void check_overwrite(const std::string &name); @@ -159,10 +157,16 @@ class ParsedArgument { public: operator std::string() const { return p; } - const std::string &as_text() const { return p; } - bool as_bool() const { return to(p); } + const std::string &as_text() const { + assert(arg->types[ArgTypeFlags::Text]); + return p; + } + bool as_bool() const { + assert(arg->types[ArgTypeFlags::Boolean]); + return to(p); + } int64_t as_int() const; - uint64_t as_uint() const { return uint64_t(as_int()); } + uint64_t as_uint() const; default_type as_float() const; std::vector as_sequence_int() const; diff --git a/cpp/core/cmdline_option.h b/cpp/core/cmdline_option.h index b6e47404f1..948902f5a4 100644 --- a/cpp/core/cmdline_option.h +++ b/cpp/core/cmdline_option.h @@ -16,16 +16,13 @@ #pragma once +#include #include #include #include #include #include -#ifdef None -#undef None -#endif - #include "mrtrix.h" #include "types.h" #include @@ -39,33 +36,58 @@ namespace MR::App { * \ref command_line_parsing page. * */ -//! \cond skip -enum ArgType { - Undefined, - Text, - Boolean, - Integer, - Float, - ArgFileIn, - ArgFileOut, - ArgDirectoryIn, - ArgDirectoryOut, - Choice, - ImageIn, - ImageOut, - IntSeq, - FloatSeq, - TracksIn, - TracksOut, - Various +class ArgTypeFlags : public std::bitset<15> { +public: + ArgTypeFlags() = default; + inline static constexpr ssize_t Text = 0; + inline static constexpr ssize_t Boolean = 1; + inline static constexpr ssize_t Integer = 2; + inline static constexpr ssize_t Float = 3; + inline static constexpr ssize_t FileIn = 4; + inline static constexpr ssize_t FileOut = 5; + inline static constexpr ssize_t DirectoryIn = 6; + inline static constexpr ssize_t DirectoryOut = 7; + inline static constexpr ssize_t ImageIn = 8; + inline static constexpr ssize_t ImageOut = 9; + inline static constexpr ssize_t IntSeq = 10; + inline static constexpr ssize_t FloatSeq = 11; + inline static constexpr ssize_t TracksIn = 12; + inline static constexpr ssize_t TracksOut = 13; + inline static constexpr ssize_t Choice = 14; +}; + +class ArgModifierFlags { +public: + ArgModifierFlags() = default; + ArgModifierFlags(const ArgModifierFlags &) = default; + ~ArgModifierFlags() = default; + ArgModifierFlags &operator=(const ArgModifierFlags &) = default; + void set_optional() { data.set(Optional); } + void set_required() { data.reset(Optional); } + void set_allow_multiple() { data.set(AllowMultiple); } + bool optional() const { return data[Optional]; } + bool required() const { return !data[Optional]; } + bool allow_multiple() const { return data[AllowMultiple]; } + bool any() const { return data.any(); } + bool operator!=(const ArgModifierFlags &that) const { return data != that.data; } + +private: + std::bitset<2> data; + inline static constexpr ssize_t Optional = 0; + inline static constexpr ssize_t AllowMultiple = 1; }; -using ArgFlags = int; -constexpr ArgFlags None = 0; -constexpr ArgFlags Optional = 0x1; -constexpr ArgFlags AllowMultiple = 0x2; //! \endcond +namespace { +template typename std::enable_if::value, T>::type void_rangemax() { + return std::numeric_limits::max(); +} +template typename std::enable_if::value, T>::type void_rangemax() { + return std::numeric_limits::infinity(); +} +} // namespace + //! \addtogroup CmdParse // @{ @@ -105,27 +127,39 @@ class Argument { * and description. If default arguments are used, the object corresponds * to the end-of-list specifier, as detailed in \ref command_line_parsing. */ Argument(std::string name, std::string description = std::string()) - : id(std::move(name)), desc(std::move(description)), type(Undefined), flags(None) {} + : id(std::move(name)), desc(std::move(description)) {} //! the argument name std::string id; //! the argument description std::string desc; - //! the argument type - ArgType type; + //! the argument type(s) + ArgTypeFlags types; //! the argument flags (AllowMultiple & Optional) - ArgFlags flags; - - struct IntRange { - int64_t min, max; + ArgModifierFlags flags; + + std::vector choices; + + template class ScalarRange { + public: + ScalarRange() : _min(T(0)), _max(T(0)) {} + operator bool() const { return _min != T(0) || _max != T(0); } + void set(T i) { + _min = i; + _max = void_rangemax(); + } + void set(T i, T j) { + _min = i; + _max = j; + } + T min() const { return _min; } + T max() const { return _max; } + + private: + T _min, _max; }; - struct FloatRange { - default_type min, max; - }; - - //! a structure to store the various parameters of the Argument - using Limits = std::variant, IntRange, FloatRange>; - Limits limits; + ScalarRange int_limits; + ScalarRange float_limits; operator bool() const { return !id.empty(); } @@ -141,35 +175,32 @@ class Argument { * \note Only one argument can be specified as optional and/or multiple. */ Argument &optional() { - flags |= Optional; + flags.set_optional(); return *this; } //! specifies that multiple such arguments can be specified /*! See optional() for details. */ Argument &allow_multiple() { - flags |= AllowMultiple; + flags.set_allow_multiple(); return *this; } //! specifies that the argument should be a text string Argument &type_text() { - assert(type == Undefined); - type = Text; + types.set(ArgTypeFlags::Text); return *this; } //! specifies that the argument should be an input image Argument &type_image_in() { - assert(type == Undefined); - type = ImageIn; + types.set(ArgTypeFlags::ImageIn); return *this; } //! specifies that the argument should be an output image Argument &type_image_out() { - assert(type == Undefined); - type = ImageOut; + types.set(ArgTypeFlags::ImageOut); return *this; } @@ -177,17 +208,15 @@ class Argument { /*! if desired, a range of allowed values can be specified. */ Argument &type_integer(const int64_t min = std::numeric_limits::min(), const int64_t max = std::numeric_limits::max()) { - assert(type == Undefined); - type = Integer; - limits = IntRange{min, max}; + types.set(ArgTypeFlags::Integer); + int_limits.set(min, max); return *this; } //! specifies that the argument should be a boolean /*! Valid responses are 0,no,false or any non-zero integer, yes, true. */ Argument &type_bool() { - assert(type == Undefined); - type = Boolean; + types.set(ArgTypeFlags::Boolean); return *this; } @@ -195,9 +224,8 @@ class Argument { /*! if desired, a range of allowed values can be specified. */ Argument &type_float(const default_type min = -std::numeric_limits::infinity(), const default_type max = std::numeric_limits::infinity()) { - assert(type == Undefined); - type = Float; - limits = FloatRange{min, max}; + types.set(ArgTypeFlags::Float); + float_limits.set(min, max); return *this; } @@ -212,77 +240,61 @@ class Argument { * .type_choice (mode_list); * \endcode * \note Each string in the list must be supplied in \b lowercase. */ - Argument &type_choice(const std::vector &choices) { - assert(type == Undefined); - type = Choice; - limits = choices; + Argument &type_choice(const std::vector &c) { + types.set(ArgTypeFlags::Choice); + choices = c; return *this; } //! specifies that the argument should be an input file Argument &type_file_in() { - assert(type == Undefined); - type = ArgFileIn; + types.set(ArgTypeFlags::FileIn); return *this; } //! specifies that the argument should be an output file Argument &type_file_out() { - assert(type == Undefined); - type = ArgFileOut; + types.set(ArgTypeFlags::FileOut); return *this; } //! specifies that the argument should be an input directory Argument &type_directory_in() { - assert(type == Undefined); - type = ArgDirectoryIn; + types.set(ArgTypeFlags::DirectoryIn); return *this; } //! specifies that the argument should be an output directory Argument &type_directory_out() { - assert(type == Undefined); - type = ArgDirectoryOut; + types.set(ArgTypeFlags::DirectoryOut); return *this; } //! specifies that the argument should be a sequence of comma-separated integer values Argument &type_sequence_int() { - assert(type == Undefined); - type = IntSeq; + types.set(ArgTypeFlags::IntSeq); return *this; } //! specifies that the argument should be a sequence of comma-separated floating-point values. Argument &type_sequence_float() { - assert(type == Undefined); - type = FloatSeq; + types.set(ArgTypeFlags::FloatSeq); return *this; } //! specifies that the argument should be an input tracks file Argument &type_tracks_in() { - assert(type == Undefined); - type = TracksIn; + types.set(ArgTypeFlags::TracksIn); return *this; } //! specifies that the argument should be an output tracks file Argument &type_tracks_out() { - assert(type == Undefined); - type = TracksOut; - return *this; - } - - //! specifies that the argument could be one of various types - Argument &type_various() { - assert(type == Undefined); - type = Various; + types.set(ArgTypeFlags::TracksOut); return *this; } - std::string syntax(int format) const; + std::string syntax(const bool format) const; std::string usage() const; }; @@ -325,10 +337,11 @@ class Argument { */ class Option : public std::vector { public: - Option() : flags(Optional) {} + Option() { flags.set_optional(); } - Option(std::string name, std::string description) - : id(std::move(name)), desc(std::move(description)), flags(Optional) {} + Option(std::string name, std::string description) : id(std::move(name)), desc(std::move(description)) { + flags.set_optional(); + } Option &operator+(const Argument &arg) { push_back(arg); @@ -341,7 +354,7 @@ class Option : public std::vector { //! the option description std::string desc; //! option flags (AllowMultiple and/or Optional) - ArgFlags flags; + ArgModifierFlags flags; //! specifies that the option is required /*! An option specified as required must be supplied on the command line. @@ -357,20 +370,20 @@ class Option : public std::vector { * \endcode */ Option &required() { - flags &= ~Optional; + flags.set_required(); return (*this); } //! specifies that multiple such options can be specified /*! See required() for details. */ Option &allow_multiple() { - flags |= AllowMultiple; + flags.set_allow_multiple(); return *this; } bool is(const std::string &name) const { return name == id; } - std::string syntax(int format) const; + std::string syntax(const bool format) const; std::string usage() const; }; @@ -411,9 +424,9 @@ class OptionGroup : public std::vector