From 7280a56ee91fd90b4537f68f7bfeed3eade6f04b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 13 Aug 2017 22:57:53 +0200 Subject: [PATCH] Added command line parser to tl. --- src/buddies/strm2cif/strm2cif.cc | 406 +------------------------ src/tl/tl.pro | 6 +- src/tl/tlCommandLineParser.cc | 336 +++++++++++++++++++++ src/tl/tlCommandLineParser.h | 420 ++++++++++++++++++++++++++ src/unit_tests/tlCommandLineParser.cc | 286 ++++++++++++++++++ src/unit_tests/unit_tests.pro | 3 +- 6 files changed, 1054 insertions(+), 403 deletions(-) create mode 100644 src/tl/tlCommandLineParser.cc create mode 100644 src/tl/tlCommandLineParser.h create mode 100644 src/unit_tests/tlCommandLineParser.cc diff --git a/src/buddies/strm2cif/strm2cif.cc b/src/buddies/strm2cif/strm2cif.cc index df0c89c07..36f00f43e 100644 --- a/src/buddies/strm2cif/strm2cif.cc +++ b/src/buddies/strm2cif/strm2cif.cc @@ -24,422 +24,28 @@ #include "dbReader.h" #include "dbCIFWriter.h" #include "tlLog.h" +#include "tlCommandLineParser.h" #include -namespace bd -{ - -class ArgBase -{ -public: - ArgBase (const char *option, const char *brief_doc, const char *long_doc) - : m_option (option), m_brief_doc (brief_doc), m_long_doc (long_doc) - { - // .. nothing yet .. - } - - virtual ~ArgBase () - { - // .. nothing yet .. - } - - const std::string &option () const - { - return m_option; - } - - const std::string &brief_doc () const - { - return m_brief_doc; - } - - const std::string &long_doc () const - { - return m_long_doc; - } - - virtual void take_value (tl::Extractor & /*ex*/) - { - // .. nothing yet .. - } - - virtual ArgBase *clone () const - { - return new ArgBase (*this); - } - - virtual bool wants_value () const - { - return false; - } - -private: - std::string m_option; - std::string m_brief_doc; - std::string m_long_doc; -}; - -template -void extract (tl::Extractor &ex, T &t, bool /*for_list*/ = false) -{ - ex.read (t); -} - -void extract (tl::Extractor &ex, std::string &t, bool for_list = false) -{ - if (*ex == '"' || *ex == '\'') { - ex.read_quoted (t); - } else if (for_list) { - ex.read (t, ","); - } else { - t = ex.get (); - } -} - -template -void extract (tl::Extractor &ex, std::vector &t, bool /*for_list*/ = false) -{ - while (! ex.at_end ()) { - t.push_back (T ()); - extract (ex, t.back (), true); - ex.test (","); - } -} - -template -struct type_without_const_ref -{ - typedef T inner_type; -}; - -template -struct type_without_const_ref -{ - typedef T inner_type; -}; - -template -struct wants_value_traits -{ - bool operator() () const { return true; } -}; - -template <> -struct wants_value_traits -{ - bool operator() () const { return false; } -}; - -template -class arg_direct_setter - : public ArgBase -{ -public: - arg_direct_setter (const char *option, T *value, const char *brief_doc, const char *long_doc) - : ArgBase (option, brief_doc, long_doc), mp_value (value) - { - // .. nothing yet .. - } - - virtual void take_value (tl::Extractor &ex) - { - extract (ex, *mp_value); - } - - virtual ArgBase *clone () const - { - return new arg_direct_setter (*this); - } - - virtual bool wants_value () const - { - return wants_value_traits () (); - } - -private: - T *mp_value; -}; - -template -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) - : ArgBase (option, brief_doc, long_doc), mp_object (object), mp_setter (setter) - { - // .. nothing yet .. - } - - virtual void take_value (tl::Extractor &ex) - { - typename type_without_const_ref::innter_type t = T (); - extract (ex, t); - (mp_object->*mp_setter) (t); - } - - virtual ArgBase *clone () const - { - return new arg_method_setter (*this); - } - - virtual bool wants_value () const - { - return wants_value_traits () (); - } - -private: - C *mp_object; - void (C::*mp_setter)(T); -}; - -template -arg_method_setter arg (const char *option, C *object, void (C::*setter)(T), const char *brief_doc, const char *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 = "") -{ - return arg_direct_setter (option, value, brief_doc, long_doc); -} - -struct ParsedOption -{ - ParsedOption (const std::string &option) - : optional (false) - { - tl::Extractor ex (option.c_str ()); - while (! ex.at_end ()) { - if (ex.test ("--")) { - optional = true; - ex.read_word (long_option, "_-"); - if (ex.test ("=")) { - ex.read_word (name); - } - } else if (ex.test ("-")) { - optional = true; - ex.read_word (short_option, ""); - if (ex.test ("=")) { - ex.read_word (name); - } - } else { - optional = ex.test ("?"); - ex.read_word (name); - } - ex.test("|"); - } - } - - bool optional; - std::string long_option, short_option, name; -}; - -class CommandLineOptions -{ -public: - CommandLineOptions () - { - // Populate with the built-in options - *this << ArgBase ("-h|--help", "Shows the usage", ""); - } - - ~CommandLineOptions () - { - for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { - delete *a; - } - m_args.clear (); - } - - CommandLineOptions &operator<< (const ArgBase &a) - { - m_args.push_back (a.clone ()); - return *this; - } - - void brief (const std::string &text) - { - m_brief = text; - } - - void parse (int argc, char *argv[]) - { - for (int i = 0; i < argc; ++i) { - std::string arg = argv[i]; - if (arg == "-h" || arg == "--help") { - produce_help (tl::to_string (QFileInfo (tl::to_qstring (argv[0])).fileName ())); - throw tl::CancelException (); - } - } - - // @@@ implement parsing - } - -private: - std::string m_brief; - std::vector m_args; - - void print_string_formatted (const std::string &indent, unsigned int columns, const std::string &text) - { - tl::info << indent << tl::noendl; - - unsigned int c = 0; - const char *t = text.c_str (); - while (*t) { - - const char *tt = t; - bool at_beginning = (c == 0); - while (*t && *t != ' ' && *t != '\n') { - ++t; - ++c; - if (c == columns && !at_beginning) { - tl::info << ""; - tl::info << indent << tl::noendl; - c = 0; - } - } - - tl::info << std::string (tt, 0, t - tt) << tl::noendl; - while (*t == ' ') { - ++t; - } - if (*t == '\n') { - ++t; - tl::info << tl::endl << indent << tl::noendl; - c = 0; - } else { - if (c + 1 == columns) { - tl::info << tl::endl << indent << tl::noendl; - c = 0; - } else { - tl::info << " " << tl::noendl; - c += 1; - } - } - while (*t == ' ') { - ++t; - } - - } - - tl::info << ""; - } - - std::string pad_string (unsigned int columns, const std::string &text) - { - std::string s = text; - while (s.size () < size_t (columns)) { - s += " "; - } - return s; - } - - void produce_help (const std::string &program_name) - { - int columns = 60; - - tl::info << "Usage:" << tl::endl; - tl::info << " " << program_name << " [options]" << tl::noendl; - - for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { - ParsedOption option ((*a)->option ()); - if (! option.name.empty ()) { - if (option.optional) { - tl::info << " [<" << option.name << ">]" << tl::noendl; - } else { - tl::info << " <" << option.name << ">" << tl::noendl; - } - } - } - - tl::info << tl::endl; - print_string_formatted (" ", columns, m_brief); - tl::info << tl::endl; - - unsigned int short_option_width = 0; - unsigned int long_option_width = 0; - unsigned int name_width = 0; - - for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { - ParsedOption option ((*a)->option ()); - name_width = std::max (name_width, (unsigned int) option.name.size ()); - short_option_width = std::max (short_option_width, (unsigned int) option.short_option.size ()); - long_option_width = std::max (long_option_width, (unsigned int) option.long_option.size ()); - } - - tl::info << "Arguments:" << tl::endl; - - for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { - ParsedOption option ((*a)->option ()); - if (! option.short_option.empty () || ! option.long_option.empty ()) { - continue; - } - std::string n = "<" + option.name + ">"; - if (option.optional) { - n += " (optional)"; - } - tl::info << " " << pad_string (name_width + 13, n) << (*a)->brief_doc (); - tl::info << ""; - - if (! (*a)->long_doc ().empty ()) { - print_string_formatted (" ", columns, (*a)->long_doc ()); - tl::info << ""; - } - } - - tl::info << ""; - tl::info << "Options:" << tl::endl; - - for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { - ParsedOption option ((*a)->option ()); - if (option.short_option.empty () && option.long_option.empty ()) { - continue; - } - std::string name; - if ((*a)->wants_value ()) { - name = option.name; - if (name.empty ()) { - name = "value"; - } - } - tl::info << " " - << pad_string (short_option_width + 5, option.short_option.empty () ? "" : "-" + option.short_option) << " " - << pad_string (long_option_width + 5, option.long_option.empty () ? "" : "--" + option.long_option) << " " - << pad_string (name_width + 3, name) << " " - << (*a)->brief_doc (); - tl::info << ""; - - if (! (*a)->long_doc ().empty ()) { - print_string_formatted (" ", columns, (*a)->long_doc ()); - tl::info << ""; - } - } - } -}; - -} - int main (int argc, char *argv []) { db::CIFWriterOptions cif_options; std::string infile, outfile; - bd::CommandLineOptions cmd; + tl::CommandLineOptions cmd; - cmd << bd::arg("-od|--dummy-calls", &cif_options.dummy_calls, "Produces dummy calls", + cmd << tl::arg("-od|--dummy-calls", &cif_options.dummy_calls, "Produces dummy calls", "If this option is given, the writer will produce dummy cell calls on global level for all top cells" ) - << bd::arg("-ob|--blank-separator", &cif_options.blank_separator, "Uses blanks as x/y separators", + << tl::arg("-ob|--blank-separator", &cif_options.blank_separator, "Uses blanks as x/y separators", "If this option is given, blank characters will be used to separate x and y values. " "Otherwise comma characters will be used.\n" "Use this option if your CIF consumer cannot read comma characters as x/y separators." ) - << bd::arg("input", &infile, "The input file (any format, may be gzip compressed)") - << bd::arg("output", &outfile, "The output file") + << tl::arg("input", &infile, "The input file (any format, may be gzip compressed)") + << tl::arg("output", &outfile, "The output file") ; cmd.brief ("This program will convert the given file to a CIF file"); diff --git a/src/tl/tl.pro b/src/tl/tl.pro index 7f2b919a9..acb2d42a2 100644 --- a/src/tl/tl.pro +++ b/src/tl/tl.pro @@ -43,7 +43,8 @@ SOURCES = \ tlFileSystemWatcher.cc \ tlFileUtils.cc \ tlWebDAV.cc \ - tlArch.cc + tlArch.cc \ + tlCommandLineParser.cc HEADERS = \ tlAlgorithm.h \ @@ -90,7 +91,8 @@ HEADERS = \ tlCpp.h \ tlFileUtils.h \ tlWebDAV.h \ - tlArch.h + tlArch.h \ + tlCommandLineParser.h INCLUDEPATH = DEPENDPATH = diff --git a/src/tl/tlCommandLineParser.cc b/src/tl/tlCommandLineParser.cc new file mode 100644 index 000000000..d401b936a --- /dev/null +++ b/src/tl/tlCommandLineParser.cc @@ -0,0 +1,336 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "tlLog.h" +#include "tlCommandLineParser.h" + +#include + +namespace tl +{ + +// ------------------------------------------------------------------------ +// ArgBase implementation + +ArgBase::ParsedOption::ParsedOption (const std::string &option) + : optional (false) +{ + tl::Extractor ex (option.c_str ()); + while (! ex.at_end ()) { + if (ex.test ("--")) { + optional = true; + ex.read_word (long_option, "_-"); + if (ex.test ("=")) { + ex.read_word (name); + } + } else if (ex.test ("-")) { + optional = true; + ex.read_word (short_option, ""); + if (ex.test ("=")) { + ex.read_word (name); + } + } else { + optional = ex.test ("?"); + ex.read_word (name); + } + ex.test("|"); + } +} + +ArgBase::ArgBase (const char *option, const char *brief_doc, const char *long_doc) + : m_option (option), m_brief_doc (brief_doc), m_long_doc (long_doc) +{ + // .. nothing yet .. +} + +ArgBase::~ArgBase () +{ + // .. nothing yet .. +} + +bool +ArgBase::is_option () const +{ + return !m_option.short_option.empty () || !m_option.long_option.empty (); +} + +// ------------------------------------------------------------------------ +// CommandLineOptions implementation + +CommandLineOptions::CommandLineOptions () +{ + // Populate with the built-in options + *this << ArgBase ("-h|--help", "Shows the usage", ""); +} + +CommandLineOptions::~CommandLineOptions () +{ + for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { + delete *a; + } + m_args.clear (); +} + +CommandLineOptions & +CommandLineOptions::operator<< (const ArgBase &a) +{ + m_args.push_back (a.clone ()); + return *this; +} + +static void +print_string_formatted (const std::string &indent, unsigned int columns, const std::string &text) +{ + tl::info << indent << tl::noendl; + + unsigned int c = 0; + const char *t = text.c_str (); + while (*t) { + + const char *tt = t; + bool at_beginning = (c == 0); + while (*t && *t != ' ' && *t != '\n') { + ++t; + ++c; + if (c == columns && !at_beginning) { + tl::info << ""; + tl::info << indent << tl::noendl; + c = 0; + } + } + + tl::info << std::string (tt, 0, t - tt) << tl::noendl; + while (*t == ' ') { + ++t; + } + if (*t == '\n') { + ++t; + tl::info << tl::endl << indent << tl::noendl; + c = 0; + } else { + if (c + 1 == columns) { + tl::info << tl::endl << indent << tl::noendl; + c = 0; + } else { + tl::info << " " << tl::noendl; + c += 1; + } + } + while (*t == ' ') { + ++t; + } + + } + + tl::info << ""; +} + +static std::string +pad_string (unsigned int columns, const std::string &text) +{ + std::string s = text; + while (s.size () < size_t (columns)) { + s += " "; + } + return s; +} + +void +CommandLineOptions::produce_help (const std::string &program_name) +{ + int columns = 60; + + tl::info << "Usage:" << tl::endl; + tl::info << " " << program_name << " [options]" << tl::noendl; + + for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { + if (! (*a)->option ().name.empty ()) { + if ((*a)->option ().optional) { + tl::info << " [<" << (*a)->option ().name << ">]" << tl::noendl; + } else { + tl::info << " <" << (*a)->option ().name << ">" << tl::noendl; + } + } + } + + tl::info << tl::endl; + print_string_formatted (" ", columns, m_brief); + tl::info << tl::endl; + + unsigned int short_option_width = 0; + unsigned int long_option_width = 0; + unsigned int name_width = 0; + + for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { + name_width = std::max (name_width, (unsigned int) (*a)->option ().name.size ()); + short_option_width = std::max (short_option_width, (unsigned int) (*a)->option ().short_option.size ()); + long_option_width = std::max (long_option_width, (unsigned int) (*a)->option ().long_option.size ()); + } + + tl::info << "Arguments:" << tl::endl; + + for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { + if ((*a)->is_option ()) { + continue; + } + std::string n = "<" + (*a)->option ().name + ">"; + if ((*a)->option ().optional) { + n += " (optional)"; + } + tl::info << " " << pad_string (name_width + 13, n) << (*a)->brief_doc (); + tl::info << ""; + + if (! (*a)->long_doc ().empty ()) { + print_string_formatted (" ", columns, (*a)->long_doc ()); + tl::info << ""; + } + } + + tl::info << ""; + tl::info << "Options:" << tl::endl; + + for (std::vector::const_iterator a = m_args.begin (); a != m_args.end (); ++a) { + if (! (*a)->is_option ()) { + continue; + } + std::string name; + if ((*a)->wants_value ()) { + name = (*a)->option ().name; + if (name.empty ()) { + 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) << " " + << pad_string (name_width + 3, name) << " " + << (*a)->brief_doc (); + tl::info << ""; + + if (! (*a)->long_doc ().empty ()) { + print_string_formatted (" ", columns, (*a)->long_doc ()); + tl::info << ""; + } + } +} + +void +CommandLineOptions::parse (int argc, char *argv[]) +{ + for (int i = 0; i < argc; ++i) { + std::string arg_as_utf8 = tl::to_string (QString::fromLocal8Bit (argv [i])); + if (arg_as_utf8 == "-h" || arg_as_utf8 == "--help") { + produce_help (tl::to_string (QFileInfo (QString::fromLocal8Bit (argv [0])).fileName ())); + throw tl::CancelException (); + } + } + + 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; + } + + for (int i = 1; i < argc; ++i) { + + ArgBase *arg = 0; + + std::string arg_as_utf8 = tl::to_string (QString::fromLocal8Bit (argv [i])); + tl::Extractor ex (arg_as_utf8.c_str ()); + + 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) { + throw tl::Exception (tl::to_string (QObject::tr ("Unknown command line option --%1 (use -h for help)").arg (tl::to_qstring (n)))); + } + + } 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)))); + } + + } else { + + if (next_plain_arg == m_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 ()) { + + if (! arg->is_option () || ex.test ("=")) { + arg->take_value (ex); + } else { + + if (! ex.at_end ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Syntax error in argument at \"..%1\" (use -h for help)").arg (tl::to_qstring (ex.get ())))); + } + ++i; + if (i == argc) { + throw tl::Exception (tl::to_string (QObject::tr ("Value missing for last argument (use -h for help)"))); + } + + std::string arg_as_utf8 = tl::to_string (QString::fromLocal8Bit (argv [i])); + tl::Extractor ex_value (arg_as_utf8); + arg->take_value (ex_value); + + } + + } else { + if (ex.test ("=")) { + arg->take_value (ex); + } else { + arg->mark_present (); + } + } + + } + + if (next_plain_arg != m_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 new file mode 100644 index 000000000..cad3d8bed --- /dev/null +++ b/src/tl/tlCommandLineParser.h @@ -0,0 +1,420 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#if !defined(HDR_tlCommandLineParser_h) +#define HDR_tlCommandLineParser_h + +#include "tlCommon.h" +#include "tlLog.h" + +#include + +namespace tl +{ + +/** + * @brief A base class for argument getters + * + * The argument getters are both declarations (delivering names and doc) + * as well as providers for the translation methods. Argument getters + * are attached to the CommandLineOptions object to form the + * command line declaration. + */ +class TL_PUBLIC ArgBase +{ +public: + /** + * @brief A parsed version of the option string + */ + struct ParsedOption + { + /** + * @brief Constructor + * This constructor populates the optional flag and + * the long_option, short_option and name values. + */ + ParsedOption (const std::string &option); + + bool optional; + std::string long_option, short_option, name; + }; + + /** + * @brief Constructor + * + * @param option The option descriptor + * @param brief_doc A short documentation + * @param long_doc A long documentation (or empty to skip this) + * + * The option descriptor is either + * "name" - A mandatory input parameter with name "name" + * "?name" - An optional input parameter with name "name" + * "-o" - A short option without a parameter (boolean) + * "-o=value" - A short option with a value named "value" + * "--long-option" - A long option + * "--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 + */ + ArgBase (const char *option, const char *brief_doc, const char *long_doc); + + /** + * @brief Destructor + */ + virtual ~ArgBase (); + + /** + * @brief The option descriptor (see constructor) + */ + const ParsedOption &option () const + { + return m_option; + } + + /** + * @brief The short documentation + */ + const std::string &brief_doc () const + { + return m_brief_doc; + } + + /** + * @brief The long documentation + */ + const std::string &long_doc () const + { + return m_long_doc; + } + + /** + * @brief Returns true, if the argument is an option + */ + bool is_option () const; + + /** + * @brief Gets a value from the extractor into the target of the argument + */ + virtual void take_value (tl::Extractor & /*ex*/) + { + // .. nothing yet .. + } + + /** + * @brief Marks an option to be present (for boolean options) + */ + virtual void mark_present () + { + // .. nothing yet .. + } + + /** + * @brief Clones this method + */ + virtual ArgBase *clone () const + { + return new ArgBase (*this); + } + + /** + * @brief Returns a value indicating whether the argument wants a value + * This flag indicates that a value shall be assigned. For boolean types + * no value needs to be supplied. For other types a value is required. + */ + virtual bool wants_value () const + { + return false; + } + +private: + ParsedOption m_option; + std::string m_brief_doc; + std::string m_long_doc; +}; + +/** + * @brief A helper for extracting values by type + */ +template +void extract (tl::Extractor &ex, T &t, bool /*for_list*/ = false) +{ + ex.read (t); +} + +/** + * @brief A specialization for the string type + */ +void extract (tl::Extractor &ex, std::string &t, bool for_list = false) +{ + if (*ex == '"' || *ex == '\'') { + ex.read_quoted (t); + } else if (for_list) { + ex.read (t, ","); + } else { + t = ex.get (); + } +} + +/** + * @brief A specialization for a list of any type (vector) + */ +template +void extract (tl::Extractor &ex, std::vector &t, bool /*for_list*/ = false) +{ + while (! ex.at_end ()) { + t.push_back (T ()); + extract (ex, t.back (), true); + ex.test (","); + } +} + +/** + * @brief A helper to mark "presence" + */ +template +void mark_presence (T &) +{ + // .. the default implementation does nothing .. +} + +void mark_presence (bool &t) +{ + t = true; +} + +template +void mark_presence_setter (C *, void (C::*) (T)) +{ + // .. the default implementation does nothing .. +} + +template +void mark_presence_setter (C *c, void (C::*ptr) (bool)) +{ + (c->*ptr) (true); +} + + +/** + * @brief A helper template to extract the actual type from (T) or (const T &) + */ +template +struct type_without_const_ref +{ + typedef T inner_type; +}; + +template +struct type_without_const_ref +{ + typedef T inner_type; +}; + +/** + * @brief A helper template to determine types that don't need a value + */ +template +struct wants_value_traits +{ + bool operator() () const { return true; } +}; + +template <> +struct wants_value_traits +{ + bool operator() () const { return false; } +}; + +/** + * @brief A specialization of the ArgBase class for a setter with direct access to the value + */ +template +class arg_direct_setter + : public ArgBase +{ +public: + arg_direct_setter (const char *option, T *value, const char *brief_doc, const char *long_doc) + : ArgBase (option, brief_doc, long_doc), mp_value (value) + { + // .. nothing yet .. + } + + virtual void take_value (tl::Extractor &ex) + { + extract (ex, *mp_value); + } + + virtual void mark_present () + { + mark_presence (*mp_value); + } + + virtual ArgBase *clone () const + { + return new arg_direct_setter (*this); + } + + virtual bool wants_value () const + { + return wants_value_traits () (); + } + +private: + T *mp_value; +}; + +/** + * @brief A specialization of the ArgBase class for a setter that is a method of a class + */ +template +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) + : ArgBase (option, brief_doc, long_doc), mp_object (object), mp_setter (setter) + { + // .. nothing yet .. + } + + virtual void take_value (tl::Extractor &ex) + { + typedef typename type_without_const_ref::inner_type inner_type; + inner_type t = inner_type (); + extract (ex, t); + (mp_object->*mp_setter) (t); + } + + virtual void mark_present () + { + mark_presence_setter (mp_object, mp_setter); + } + + virtual ArgBase *clone () const + { + return new arg_method_setter (*this); + } + + virtual bool wants_value () const + { + return wants_value_traits () (); + } + +private: + C *mp_object; + void (C::*mp_setter)(T); +}; + +/** + * @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 = "") +{ + 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 = "") +{ + return arg_direct_setter (option, value, brief_doc, long_doc); +} + +/** + * @brief The command line parser class + * + * To establish a command line parser use code like this: + * + * @code + * int + * main (int argc, char *argv []) + * { + * bool has_x = false; + * int int_value = 0; + * + * CommandLineOptions cmd; + * cmd << arg("-x|--long-option", &has_x, "X Option", "This is a long documentation for X option") + * << arg("int_value", &int_value, "A mandatory integer value"); + * ; + * + * try { + * cmd.parse (argc, argv); + * ... use has_x, int_value .. + * } catch (tl::CancelException &ex) { + * return 1; + * } catch (std::exception &ex) { + * tl::error << ex.what (); + * return 1; + * } catch (tl::Exception &ex) { + * tl::error << ex.msg (); + * return 1; + * } catch (...) { + * tl::error << "ERROR: unspecific error"; + * } + * } + * @endcode + */ +class TL_PUBLIC CommandLineOptions +{ +public: + /** + * @brief Constructor + */ + CommandLineOptions (); + + /** + * @brief Destructor + */ + ~CommandLineOptions (); + + /** + * @brief Adds the argument declaration & getter + */ + CommandLineOptions &operator<< (const ArgBase &a); + + /** + * @brief Adds the brief documentation + */ + void brief (const std::string &text) + { + m_brief = text; + } + + /** + * @brief Parses the command line and executes the getters + * + * This method will throw a tl::CancelException if the help function was used. + * It will throw other exceptions on syntax errors or if mandatory arguments are + * missing. + */ + void parse (int argc, char *argv[]); + +private: + std::string m_brief; + std::vector m_args; + + void produce_help (const std::string &program_name); +}; + +} + +#endif diff --git a/src/unit_tests/tlCommandLineParser.cc b/src/unit_tests/tlCommandLineParser.cc new file mode 100644 index 000000000..672a38c7d --- /dev/null +++ b/src/unit_tests/tlCommandLineParser.cc @@ -0,0 +1,286 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "tlCommandLineParser.h" +#include "utHead.h" + +TEST(1) +{ + std::string a; + int b = 0; + bool c = false; + double d = 1; + bool e = false; + std::string f; + + tl::CommandLineOptions cmd; + cmd << tl::arg ("a", &a, "") + << tl::arg ("?b", &b, "") + << tl::arg ("-c", &c, "") + << tl::arg ("--dlong|-d", &d, "") + << tl::arg ("--elong", &e, "") + << tl::arg ("-f|--flong=value", &f, ""); + + { + char *argv[] = { "x", "y" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "y"); + EXPECT_EQ (b, 0); + + { + char *argv[] = { "x", "z", "17" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "z"); + EXPECT_EQ (b, 17); + + b = 0; + { + char *argv[] = { "x", "u", "-c" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "u"); + EXPECT_EQ (b, 0); + EXPECT_EQ (c, true); + + b = 0; + c = false; + { + char *argv[] = { "x", "u", "-c", "-d=21" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "u"); + EXPECT_EQ (b, 0); + EXPECT_EQ (c, true); + EXPECT_EQ (d, 21); + + b = 0; + c = false; + { + char *argv[] = { "x", "u", "-d", "22", "-c" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "u"); + EXPECT_EQ (b, 0); + EXPECT_EQ (c, true); + EXPECT_EQ (d, 22); + + e = false; + { + char *argv[] = { "x", "u", "--dlong", "23" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "u"); + EXPECT_EQ (d, 23); + EXPECT_EQ (e, false); + + { + char *argv[] = { "x", "u", "--dlong=24", "--elong" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "u"); + EXPECT_EQ (d, 24); + EXPECT_EQ (e, true); + + { + char *argv[] = { "x", "u", "-c", "-f=foo" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "u"); + EXPECT_EQ (f, "foo"); + + { + char *argv[] = { "x", "u", "--flong", "bar" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (a, "u"); + EXPECT_EQ (f, "bar"); +} + + +struct Values +{ + Values () + { + b = 0; + d = 1; + c = e = false; + } + + void set_a (const std::string &x) { a = x; } + void set_b (int x) { b = x; } + void set_c (bool x) { c = x; } + void set_d (const double &x) { d = x; } + void set_e (bool x) { e = x; } + void set_f (std::string x) { f = x; } + + std::string a; + int b; + bool c; + double d; + bool e; + std::string f; +}; + +TEST(2) +{ + Values v; + + tl::CommandLineOptions cmd; + cmd << tl::arg ("a", &v, &Values::set_a, "") + << tl::arg ("?b", &v, &Values::set_b, "") + << tl::arg ("-c", &v, &Values::set_c, "") + << tl::arg ("--dlong|-d", &v, &Values::set_d, "") + << tl::arg ("--elong", &v, &Values::set_e, "") + << tl::arg ("-f|--flong=value", &v, &Values::set_f, ""); + + { + char *argv[] = { "x", "y" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "y"); + EXPECT_EQ (v.b, 0); + + { + char *argv[] = { "x", "z", "17" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "z"); + EXPECT_EQ (v.b, 17); + + v.b = 0; + { + char *argv[] = { "x", "u", "-c" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "u"); + EXPECT_EQ (v.b, 0); + EXPECT_EQ (v.c, true); + + v.b = 0; + v.c = false; + { + char *argv[] = { "x", "u", "-c", "-d=21" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "u"); + EXPECT_EQ (v.b, 0); + EXPECT_EQ (v.c, true); + EXPECT_EQ (v.d, 21); + + v.b = 0; + v.c = false; + { + char *argv[] = { "x", "u", "-d", "22", "-c" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "u"); + EXPECT_EQ (v.b, 0); + EXPECT_EQ (v.c, true); + EXPECT_EQ (v.d, 22); + + v.e = false; + { + char *argv[] = { "x", "u", "--dlong", "23" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "u"); + EXPECT_EQ (v.d, 23); + EXPECT_EQ (v.e, false); + + { + char *argv[] = { "x", "u", "--dlong=24", "--elong" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "u"); + EXPECT_EQ (v.d, 24); + EXPECT_EQ (v.e, true); + + { + char *argv[] = { "x", "u", "-c", "-f=foo" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "u"); + EXPECT_EQ (v.f, "foo"); + + { + char *argv[] = { "x", "u", "--flong", "bar" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (v.a, "u"); + EXPECT_EQ (v.f, "bar"); +} + +TEST(3) +{ + std::vector a; + std::vector b; + + tl::CommandLineOptions cmd; + cmd << tl::arg ("a", &a, "") + << tl::arg ("-b", &b, ""); + + { + char *argv[] = { "x", "r,u,v" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (int (a.size ()), 3); + EXPECT_EQ (a[0], "r"); + EXPECT_EQ (a[1], "u"); + EXPECT_EQ (a[2], "v"); + EXPECT_EQ (b.empty (), true); + + a.clear (); + { + char *argv[] = { "x", "\"r,u\",v" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (int (a.size ()), 2); + EXPECT_EQ (a[0], "r,u"); + EXPECT_EQ (a[1], "v"); + EXPECT_EQ (b.empty (), true); + + a.clear (); + { + char *argv[] = { "x", "'\"'", "-b=1,5,-13" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (int (a.size ()), 1); + EXPECT_EQ (a[0], "\""); + EXPECT_EQ (int (b.size ()), 3); + EXPECT_EQ (b[0], 1); + EXPECT_EQ (b[1], 5); + EXPECT_EQ (b[2], -13); + + a.clear (); + b.clear (); + { + char *argv[] = { "x", "", "-b", "-13,21" }; + cmd.parse (sizeof (argv) / sizeof (argv[0]), argv); + } + EXPECT_EQ (int (a.size ()), 0); + EXPECT_EQ (int (b.size ()), 2); + EXPECT_EQ (b[0], -13); + EXPECT_EQ (b[1], 21); +} diff --git a/src/unit_tests/unit_tests.pro b/src/unit_tests/unit_tests.pro index 07622628d..cbeda1ae8 100644 --- a/src/unit_tests/unit_tests.pro +++ b/src/unit_tests/unit_tests.pro @@ -101,7 +101,8 @@ SOURCES = \ tlFileUtils.cc \ tlHttpStream.cc \ tlWebDAV.cc \ - laySnap.cc + laySnap.cc \ + tlCommandLineParser.cc # main components: SOURCES += \