Enhancements on command line parser

* Advanced options
* Groups
* strm2gds uses the new features already.
This commit is contained in:
Matthias Koefferlein 2017-08-17 09:26:15 +02:00
parent 09033d834e
commit dede3afe1b
4 changed files with 230 additions and 93 deletions

View File

@ -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"

View File

@ -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;
}

View File

@ -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<ArgBase *>::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<ArgBase *>::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<ArgBase *> plain_args;
std::map<std::string, ArgBase *> arg_by_short_option, arg_by_long_option;
for (std::vector<ArgBase *>::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<ArgBase *>::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<ArgBase *>::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<std::string, ArgBase *>::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<ArgBase *>::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<std::string, ArgBase *>::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)")));
}
}

View File

@ -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 <class C, class T>
arg_method_setter<C, T> arg (const char *option, C *object, void (C::*setter)(T), const char *brief_doc, const char *long_doc = "")
arg_method_setter<C, T> 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<C, T> (option, object, setter, brief_doc, long_doc);
}
template <class T>
arg_direct_setter<T> arg (const char *option, T *value, const char *brief_doc, const char *long_doc = "")
arg_direct_setter<T> arg (const std::string &option, T *value, const std::string &brief_doc, const std::string &long_doc = "")
{
return arg_direct_setter<T> (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