From dede3afe1bd40e4830194204bda21c5cd4f1c9a7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 17 Aug 2017 09:26:15 +0200 Subject: [PATCH] Enhancements on command line parser * Advanced options * Groups * strm2gds uses the new features already. --- src/buddies/bd/bdWriterOptions.cc | 40 ++++--- src/buddies/strm2gds/strm2gds.cc | 87 +++++++++------ src/tl/tlCommandLineParser.cc | 178 ++++++++++++++++++++++++------ src/tl/tlCommandLineParser.h | 18 +-- 4 files changed, 230 insertions(+), 93 deletions(-) diff --git a/src/buddies/bd/bdWriterOptions.cc b/src/buddies/bd/bdWriterOptions.cc index c74448d36..ad4746a24 100644 --- a/src/buddies/bd/bdWriterOptions.cc +++ b/src/buddies/bd/bdWriterOptions.cc @@ -39,13 +39,17 @@ GenericWriterOptions::GenericWriterOptions () void GenericWriterOptions::add_options (tl::CommandLineOptions &cmd, const std::string &format) { - cmd << tl::arg ("-os|--scale-factor=factor", &scale_factor, "Scales the layout upon writing", + std::string group ("[Output options - General]"); + + cmd << tl::arg (group + + "-os|--scale-factor=factor", &scale_factor, "Scales the layout upon writing", "Specifies layout scaling. If given, the saved layout will be scaled by the " "given factor." ); if (format == "GDS2" || format == "GDS2Text" || format == "OASIS") { - cmd << tl::arg ("-ou|--dbu=dbu", &dbu, "Uses the specified database unit", + cmd << tl::arg (group + + "-ou|--dbu=dbu", &dbu, "Uses the specified database unit", "Specifies the database unit to save the layout in. The database unit is given " "in micron units. By default, the original unit is used. The layout will not " "change physically because internally, the coordinates are scaled to match the " @@ -53,35 +57,43 @@ GenericWriterOptions::add_options (tl::CommandLineOptions &cmd, const std::strin ); } - cmd << tl::arg ("-ox|--drop-empty-cells", &dont_write_empty_cells, "Drops empty cells", + cmd << tl::arg (group + + "#-ox|--drop-empty-cells", &dont_write_empty_cells, "Drops empty cells", "If given, empty cells won't be written. See --keep-instances for more options." ); if (format == "GDS2" || format == "GDS2Text") { - cmd << tl::arg ("-ok|--keep-instances", &keep_instances, "Keeps instances of dropped cells", + cmd << tl::arg (group + + "#-ok|--keep-instances", &keep_instances, "Keeps instances of dropped cells", "If given, instances of dropped cell's won't be removed. Hence, ghost cells are " "produced. The resulting layout may not be readable by consumers that require " - "all instantiated cells to be present as actual cells." + "all instantiated cells to be present as actual cells.\n" + "Dropped cells are those which are removed by a negative cell selection (see " + "--write-cells) " ); } if (format == "GDS2" || format == "GDS2Text" || format == "OASIS") { - cmd << tl::arg ("-oc|--write-context-info", &write_context_info, "Writes context information", + cmd << tl::arg (group + + "#-oc|--write-context-info", &write_context_info, "Writes context information", "Include context information for PCell instances and other information in a format-specific " "way. The resulting layout may show unexpected features for other consumers." ); } - cmd << tl::arg ("-ow|--write-cells=sel", &cell_selection, "Specifies cells to write", + cmd << tl::arg (group + + "#-ow|--write-cells=sel", &cell_selection, "Specifies cells to write", "This option specifies the cells to write. The value of this option is a sequence of " - "select/unselect operations. A select operation is an optional plus sign (+), followed by " - "a cell filter. An unselect operation is a minus sign (-) followed by a cell filter. " - "A cell filter is a plain cell name, a glob pattern (using '*' and '?' for placeholders). " - "If a cell filter is enclosed in round brackets, only this cell is specified. Otherwise " - "the cell and it's children are specified.\n" + "positive and negative cell select operations. " + "A select operation is an optional plus (+) or minus sign (-), followed by " + "a cell filter. By default a select operation is positive, with a minus sign, the " + "select operation is negative and will unselect the matching cells." + "A cell filter is a plain cell name or a glob pattern (using '*' and '?' for placeholders). " + "If a cell filter is enclosed in round brackets, it will apply only to the matching cells. " + "Otherwise it will apply to these cells plus their children.\n" "\n" - "Multiple operations can be specified by adding them with a comma separator. " - "Cell selection and unselection happens in the order given. Hence it's possible " + "Multiple operations can be specified by combining them with a comma. " + "Positive and negative selection happens in the order given. Hence it's possible " "to select a cell with it's children and then unselect some children of this cell.\n" "\n" "Examples:\n\n" diff --git a/src/buddies/strm2gds/strm2gds.cc b/src/buddies/strm2gds/strm2gds.cc index 620512231..00396e9f4 100644 --- a/src/buddies/strm2gds/strm2gds.cc +++ b/src/buddies/strm2gds/strm2gds.cc @@ -28,7 +28,7 @@ #include "tlCommandLineParser.h" int -main (int argc, char *argv []) +main_func (int argc, char *argv []) { bd::init (); @@ -38,39 +38,50 @@ main (int argc, char *argv []) tl::CommandLineOptions cmd; - cmd << tl::arg ("-ov|--max-vertex-count=count", &gds2_options.max_vertex_count, "Specifies the maximum number of points per polygon", + std::string group = "[Output options - GDS2 specific]"; + + cmd << tl::arg (group + + "-ov|--max-vertex-count=count", &gds2_options.max_vertex_count, "Specifies the maximum number of points per polygon", "If this number is given, polygons are cut into smaller parts if they have more " "than the specified number of points. If not given, the maximum number of points will be used. " "This is 8190 unless --multi-xy-records is given." ) - << tl::arg ("-om|--multi-xy-records", &gds2_options.multi_xy_records, "Allows unlimited number of points", + << tl::arg (group + + "#-om|--multi-xy-records", &gds2_options.multi_xy_records, "Allows unlimited number of points", "If this option is given, multiple XY records will be written to accomodate an unlimited number " "of points per polygon or path. However, such files may not be compatible with some consumers." ) - << tl::arg ("-oz|--no-zero-length-paths", &gds2_options.no_zero_length_paths, "Converts zero-length paths to polygons", + << tl::arg (group + + "#-oz|--no-zero-length-paths", &gds2_options.no_zero_length_paths, "Converts zero-length paths to polygons", "If this option is given, zero-length paths (such with one point) are not written as paths " "but converted to polygons. This avoids compatibility issues with consumers of this layout file." ) - << tl::arg ("-on|--cellname-length=length", &gds2_options.max_cellname_length, "Limits cell names to the given length", + << tl::arg (group + + "-on|--cellname-length=length", &gds2_options.max_cellname_length, "Limits cell names to the given length", "If this option is given, long cell names will truncated if their length exceeds the given length." ) - << tl::arg ("-ol|--libname=libname", &gds2_options.libname, "Uses the given library name", + << tl::arg (group + + "-ol|--libname=libname", &gds2_options.libname, "Uses the given library name", "This option can specify the GDS2 LIBNAME for the output file. By default, the original LIBNAME is " "written." ) - << tl::arg ("-or|--user-units=unit", &gds2_options.user_units, "Specifies the user unit to use", + << tl::arg (group + + "#-or|--user-units=unit", &gds2_options.user_units, "Specifies the user unit to use", "Specifies the GDS2 user unit. By default micrometers are used for the user unit." ) - << tl::arg ("!-ot|--no-timestamps", &gds2_options.write_timestamps, "Don't write timestamps", + << tl::arg (group + + "#!-ot|--no-timestamps", &gds2_options.write_timestamps, "Don't write timestamps", "Writes a dummy time stamp instead of the actual time. With this option, GDS2 files become " "bytewise indentical even if written at different times. This option is useful if binary " "identity is important (i.e. in regression scenarios)." ) - << tl::arg ("-op|--write-cell-properties", &gds2_options.write_cell_properties, "Write cell properties", + << tl::arg (group + + "#-op|--write-cell-properties", &gds2_options.write_cell_properties, "Write cell properties", "This option enables a GDS2 extension that allows writing of cell properties to GDS2 files. " "Consumers that don't support this feature, may not be able to read such a GDS2 files." ) - << tl::arg ("-oq|--write-file-properties", &gds2_options.write_file_properties, "Write file properties", + << tl::arg (group + + "#-oq|--write-file-properties", &gds2_options.write_file_properties, "Write file properties", "This option enables a GDS2 extension that allows writing of file properties to GDS2 files. " "Consumers that don't support this feature, may not be able to read such a GDS2 files." ) @@ -82,31 +93,37 @@ main (int argc, char *argv []) cmd.brief ("This program will convert the given file to a GDS2 file"); + cmd.parse (argc, argv); + + db::Manager m; + db::Layout layout (&m); + db::LayerMap map; + + { + tl::InputStream stream (infile); + db::Reader reader (stream); + map = reader.read (layout); + } + + { + db::SaveLayoutOptions save_options; + save_options.set_options (gds2_options); + generic_writer_options.configure (save_options, layout); + + tl::OutputStream stream (outfile); + db::GDS2Writer writer; + writer.write (layout, stream, save_options); + } + + return 0; +} + +int +main (int argc, char *argv []) +{ try { - - cmd.parse (argc, argv); - - db::Manager m; - db::Layout layout (&m); - db::LayerMap map; - - { - tl::InputStream stream (infile); - db::Reader reader (stream); - map = reader.read (layout); - } - - { - db::SaveLayoutOptions save_options; - save_options.set_options (gds2_options); - generic_writer_options.configure (save_options, layout); - - tl::OutputStream stream (outfile); - db::GDS2Writer writer; - writer.write (layout, stream, save_options); - } - - } catch (tl::CancelException &ex) { + return main_func (argc, argv); + } catch (tl::CancelException & /*ex*/) { return 1; } catch (std::exception &ex) { tl::error << ex.what (); @@ -117,6 +134,4 @@ main (int argc, char *argv []) } catch (...) { tl::error << "ERROR: unspecific error"; } - - return 0; } diff --git a/src/tl/tlCommandLineParser.cc b/src/tl/tlCommandLineParser.cc index 33ff81e3d..15cd27f91 100644 --- a/src/tl/tlCommandLineParser.cc +++ b/src/tl/tlCommandLineParser.cc @@ -32,10 +32,29 @@ namespace tl // ArgBase implementation ArgBase::ParsedOption::ParsedOption (const std::string &option) - : optional (false), inverted (false) + : optional (false), inverted (false), advanced (false), non_advanced (false) { tl::Extractor ex (option.c_str ()); + while (! ex.at_end ()) { + + if (ex.test ("#")) { + advanced = true; + } else if (ex.test ("/")) { + non_advanced = true; + } else if (ex.test ("[")) { + const char *t = ex.get (); + while (! ex.at_end () && *ex != ']') { + ++ex; + } + group += std::string (t, 0, ex.get () - t); + ex.test ("]"); + } else { + break; + } + + } + if (ex.test ("!")) { inverted = true; } @@ -61,7 +80,7 @@ ArgBase::ParsedOption::ParsedOption (const std::string &option) } } -ArgBase::ArgBase (const char *option, const char *brief_doc, const char *long_doc) +ArgBase::ArgBase (const std::string &option, const std::string &brief_doc, const std::string &long_doc) : m_option (option), m_brief_doc (brief_doc), m_long_doc (long_doc) { // .. nothing yet .. @@ -98,7 +117,29 @@ public: void action (CommandLineOptions *options) const { - options->produce_help (options->program_name ()); + options->produce_help (options->program_name (), false); + throw tl::CancelException (); + } +}; + +class AdvancedHelpArg + : public ArgBase +{ +public: + AdvancedHelpArg () + : ArgBase ("/--help-all", "Shows all options (including advanced) and exits", "") + { + // .. nothing yet .. + } + + ArgBase *clone () const + { + return new AdvancedHelpArg (); + } + + void action (CommandLineOptions *options) const + { + options->produce_help (options->program_name (), true); throw tl::CancelException (); } }; @@ -130,7 +171,7 @@ class VersionArg { public: VersionArg () - : ArgBase ("--version", "Produces the version and exits", "") + : ArgBase ("--version", "Shows the version and exits", "") { // .. nothing yet .. } @@ -147,6 +188,42 @@ public: } }; +class VerbosityArg + : public ArgBase +{ +public: + VerbosityArg () + : ArgBase ("-d|--debug-level", "Sets the verbosity level", + "The verbosity level is an integer. Typical values are:\n" + "* 0: silent\n" + "* 10: somewhat verbose\n" + "* 11: somewhat verbose plus timing information\n" + "* 20: verbose\n" + "* 21: verbose plus timing information\n" + "..." + ) + { + // .. nothing yet .. + } + + ArgBase *clone () const + { + return new VerbosityArg (); + } + + bool wants_value () const + { + return true; + } + + void take_value (tl::Extractor &ex) + { + int d = 0; + ex.read (d); + tl::verbosity (d); + } +}; + // ------------------------------------------------------------------------ // CommandLineOptions implementation @@ -156,7 +233,7 @@ std::string CommandLineOptions::m_license; CommandLineOptions::CommandLineOptions () { // Populate with the built-in options - *this << HelpArg () << VersionArg () << LicenseArg (); + *this << HelpArg () << AdvancedHelpArg () << VersionArg () << LicenseArg () << VerbosityArg (); } CommandLineOptions::~CommandLineOptions () @@ -242,6 +319,9 @@ struct NameCompare if (! a->is_option ()) { return false; } + if (a->option ().group != b->option ().group) { + return a->option ().group < b->option ().group; + } if (a->option ().short_option.empty () != b->option ().short_option.empty ()) { return a->option ().short_option.empty () < b->option ().short_option.empty (); } @@ -253,7 +333,7 @@ struct NameCompare }; void -CommandLineOptions::produce_help (const std::string &program_name) +CommandLineOptions::produce_help (const std::string &program_name, bool advanced) { int columns = 80; @@ -314,17 +394,34 @@ CommandLineOptions::produce_help (const std::string &program_name) "(with two dashes). If a value is required, it can be specified either " "as the following argument or added to the option with an equal sign (=)."); - tl::info << " List of options:" << tl::endl; + tl::info << tl::endl << " List of options:" << tl::endl; - tl::info << " " - << pad_string (short_option_width + 5, "Short") << " " - << pad_string (long_option_width + 5, "Long") << " " - << pad_string (name_width + 3, "Value") << " " << "Description" << tl::endl; + std::string header = pad_string (short_option_width + 5, "Short") + " " + + pad_string (long_option_width + 5, "Long") + " " + + pad_string (name_width + 3, "Value") + " " + "Description"; + + tl::info << " " << header << tl::endl; + + std::string prev_group; + bool hidden = false; for (std::vector::const_iterator a = sorted_args.begin (); a != sorted_args.end (); ++a) { + if (! (*a)->is_option ()) { continue; + } else if ((*a)->option ().advanced && !advanced) { + hidden = true; + continue; + } else if ((*a)->option ().non_advanced && advanced) { + continue; } + + if ((*a)->option ().group != prev_group) { + prev_group = (*a)->option ().group; + tl::info << tl::endl << " " << prev_group << ":" << tl::endl; + tl::info << " " << header << tl::endl; + } + std::string name; if ((*a)->wants_value ()) { name = (*a)->option ().name; @@ -332,6 +429,7 @@ CommandLineOptions::produce_help (const std::string &program_name) name = "value"; } } + tl::info << " " << pad_string (short_option_width + 5, (*a)->option ().short_option.empty () ? "" : "-" + (*a)->option ().short_option) << " " << pad_string (long_option_width + 5, (*a)->option ().long_option.empty () ? "" : "--" + (*a)->option ().long_option) << " " @@ -343,6 +441,11 @@ CommandLineOptions::produce_help (const std::string &program_name) print_string_formatted (" ", columns, (*a)->long_doc ()); tl::info << ""; } + + } + + if (hidden) { + tl::info << tl::endl << " See --help-all for more options." << tl::endl; } } @@ -363,11 +466,24 @@ CommandLineOptions::parse (int argc, char *argv[]) { m_program_name = tl::to_string (QFileInfo (QString::fromLocal8Bit (argv [0])).fileName ()); - std::vector::const_iterator next_plain_arg = m_args.begin (); - while (next_plain_arg != m_args.end () && (*next_plain_arg)->is_option ()) { - ++next_plain_arg; + std::vector plain_args; + std::map arg_by_short_option, arg_by_long_option; + + for (std::vector::const_iterator i = m_args.begin (); i != m_args.end (); ++i) { + if ((*i)->is_option ()) { + if (! (*i)->option ().short_option.empty ()) { + arg_by_short_option.insert (std::make_pair ((*i)->option ().short_option, *i)); + } + if (! (*i)->option ().long_option.empty ()) { + arg_by_long_option.insert (std::make_pair ((*i)->option ().long_option, *i)); + } + } else { + plain_args.push_back (*i); + } } + std::vector::const_iterator next_plain_arg = plain_args.begin (); + for (int i = 1; i < argc; ++i) { ArgBase *arg = 0; @@ -378,43 +494,31 @@ CommandLineOptions::parse (int argc, char *argv[]) if (ex.test ("--")) { std::string n; - ex.read_word (n); - for (std::vector::const_iterator a = m_args.begin (); a != m_args.end () && !arg; ++a) { - if ((*a)->option ().long_option == n) { - arg = *a; - } - } - - if (!arg) { + ex.read_word (n, "_-"); + std::map::const_iterator a = arg_by_long_option.find (n); + if (a == arg_by_long_option.end ()) { throw tl::Exception (tl::to_string (QObject::tr ("Unknown command line option --%1 (use -h for help)").arg (tl::to_qstring (n)))); } + arg = a->second; } else if (ex.test ("-")) { std::string n; ex.read_word (n); - for (std::vector::const_iterator a = m_args.begin (); a != m_args.end () && !arg; ++a) { - if ((*a)->option ().short_option == n) { - arg = *a; - } - } - - if (!arg) { - throw tl::Exception (tl::to_string (QObject::tr ("Unknown command line option -%1 (use -h for help)").arg (tl::to_qstring (n)))); + std::map::const_iterator a = arg_by_short_option.find (n); + if (a == arg_by_short_option.end ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Unknown command line option --%1 (use -h for help)").arg (tl::to_qstring (n)))); } + arg = a->second; } else { - if (next_plain_arg == m_args.end ()) { + if (next_plain_arg == plain_args.end ()) { throw tl::Exception (tl::to_string (QObject::tr ("Unknown command line component %1 - no further plain argument expected (use -h for help)").arg (tl::to_qstring (arg_as_utf8)))); } arg = *next_plain_arg++; - while (next_plain_arg != m_args.end () && (*next_plain_arg)->is_option ()) { - ++next_plain_arg; - } - } if (arg->wants_value ()) { @@ -438,11 +542,13 @@ CommandLineOptions::parse (int argc, char *argv[]) } } else { + if (ex.test ("=")) { arg->take_value (ex); } else { arg->mark_present (arg->option ().inverted); } + } // Exection the action if there is one @@ -450,7 +556,7 @@ CommandLineOptions::parse (int argc, char *argv[]) } - if (next_plain_arg != m_args.end () && !(*next_plain_arg)->option ().optional) { + if (next_plain_arg != plain_args.end () && !(*next_plain_arg)->option ().optional) { throw tl::Exception (tl::to_string (QObject::tr ("Additional arguments required (use -h for help)"))); } } diff --git a/src/tl/tlCommandLineParser.h b/src/tl/tlCommandLineParser.h index 99ee13718..35af88ea9 100644 --- a/src/tl/tlCommandLineParser.h +++ b/src/tl/tlCommandLineParser.h @@ -56,8 +56,9 @@ public: */ ParsedOption (const std::string &option); - bool optional, inverted; + bool optional, inverted, advanced, non_advanced; std::string long_option, short_option, name; + std::string group; }; /** @@ -77,8 +78,11 @@ public: * "--long-option=value" - A long option with a value * "-o|--long-option" - A short/long option * "-o|--long-option=value" - A short/long option with a value + * "[group]..." - List the option under this group (group = group title) + * "#..." - Advanced option - listed with --help-all only + * "/..." - Non-ddvanced option - listed with -h|--help only */ - ArgBase (const char *option, const char *brief_doc, const char *long_doc); + ArgBase (const std::string &option, const std::string &brief_doc, const std::string &long_doc); /** * @brief Destructor @@ -264,7 +268,7 @@ class arg_direct_setter : public ArgBase { public: - arg_direct_setter (const char *option, T *value, const char *brief_doc, const char *long_doc) + arg_direct_setter (const std::string &option, T *value, const std::string &brief_doc, const std::string &long_doc) : ArgBase (option, brief_doc, long_doc), mp_value (value) { // .. nothing yet .. @@ -302,7 +306,7 @@ class arg_method_setter : public ArgBase { public: - arg_method_setter (const char *option, C *object, void (C::*setter)(T), const char *brief_doc, const char *long_doc) + arg_method_setter (const std::string &option, C *object, void (C::*setter)(T), const std::string &brief_doc, const std::string &long_doc) : ArgBase (option, brief_doc, long_doc), mp_object (object), mp_setter (setter) { // .. nothing yet .. @@ -340,13 +344,13 @@ private: * @brief Polymorphic production methods for the argument getters */ template -arg_method_setter arg (const char *option, C *object, void (C::*setter)(T), const char *brief_doc, const char *long_doc = "") +arg_method_setter arg (const std::string &option, C *object, void (C::*setter)(T), const std::string &brief_doc, const std::string &long_doc = "") { return arg_method_setter (option, object, setter, brief_doc, long_doc); } template -arg_direct_setter arg (const char *option, T *value, const char *brief_doc, const char *long_doc = "") +arg_direct_setter arg (const std::string &option, T *value, const std::string &brief_doc, const std::string &long_doc = "") { return arg_direct_setter (option, value, brief_doc, long_doc); } @@ -423,7 +427,7 @@ public: /** * @brief Produces the help text */ - void produce_help (const std::string &program_name); + void produce_help (const std::string &program_name, bool advanced); /** * @brief Produces the version text