From f8edfd22cb45b554a573d2722900196758e9e958 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 10 Dec 2009 09:47:29 +0200 Subject: Scanner-based parsing with support for element erasing Also implement argv_file_scanner which provides support for reading command line arguments from the argv array as well as files specified with command line options. New examples: file. New tests: ctor, erase, file. --- NEWS | 25 ++++- cli/header.cxx | 17 ++- cli/options.cli | 11 +- cli/options.cxx | 84 +++++++++++++-- cli/options.hxx | 30 ++++-- cli/options.ixx | 14 ++- cli/runtime-header.cxx | 82 ++++++++++++++- cli/runtime-inline.cxx | 63 ++++++++++- cli/runtime-source.cxx | 228 +++++++++++++++++++++++++++++++++++++++- cli/source.cxx | 49 ++++++--- doc/cli.1 | 5 + doc/cli.xhtml | 5 + doc/guide/index.xhtml | 148 ++++++++++++++++++++++++-- examples/README | 4 + examples/examples-8.0.sln | 6 ++ examples/examples-9.0.sln | 6 ++ examples/file/README | 38 +++++++ examples/file/driver.cxx | 36 +++++++ examples/file/file-8.0.vcproj | 237 ++++++++++++++++++++++++++++++++++++++++++ examples/file/file-9.0.vcproj | 233 +++++++++++++++++++++++++++++++++++++++++ examples/file/makefile | 74 +++++++++++++ examples/file/options.cli | 7 ++ examples/file/test.ops | 7 ++ examples/makefile | 2 +- tests/ctor/driver.cxx | 69 ++++++++++++ tests/ctor/makefile | 73 +++++++++++++ tests/ctor/test.cli | 9 ++ tests/erase/driver.cxx | 35 +++++++ tests/erase/makefile | 73 +++++++++++++ tests/erase/test.cli | 10 ++ tests/file/base.ops | 2 + tests/file/driver.cxx | 29 ++++++ tests/file/empty.ops | 3 + tests/file/makefile | 96 +++++++++++++++++ tests/file/test-000.ops | 6 ++ tests/file/test-000.std | 18 ++++ tests/file/test-001.ops | 3 + tests/file/test-001.std | 6 ++ tests/file/test-002.ops | 7 ++ tests/file/test-002.std | 17 +++ tests/file/test-003.std | 7 ++ tests/file/test.cli | 8 ++ tests/makefile | 2 +- 43 files changed, 1813 insertions(+), 71 deletions(-) create mode 100644 examples/file/README create mode 100644 examples/file/driver.cxx create mode 100644 examples/file/file-8.0.vcproj create mode 100644 examples/file/file-9.0.vcproj create mode 100644 examples/file/makefile create mode 100644 examples/file/options.cli create mode 100644 examples/file/test.ops create mode 100644 tests/ctor/driver.cxx create mode 100644 tests/ctor/makefile create mode 100644 tests/ctor/test.cli create mode 100644 tests/erase/driver.cxx create mode 100644 tests/erase/makefile create mode 100644 tests/erase/test.cli create mode 100644 tests/file/base.ops create mode 100644 tests/file/driver.cxx create mode 100644 tests/file/empty.ops create mode 100644 tests/file/makefile create mode 100644 tests/file/test-000.ops create mode 100644 tests/file/test-000.std create mode 100644 tests/file/test-001.ops create mode 100644 tests/file/test-001.std create mode 100644 tests/file/test-002.ops create mode 100644 tests/file/test-002.std create mode 100644 tests/file/test-003.std create mode 100644 tests/file/test.cli diff --git a/NEWS b/NEWS index 0d5f8f8..789c8bb 100644 --- a/NEWS +++ b/NEWS @@ -3,9 +3,8 @@ Version 1.1.0 * Support for option documentation. Option documentation is used to print the usage information as well as to generate the program documentation in the HTML and man page formats. For details, see Sections 2.5, "Adding - Documentation" and 3.3, "Option Documentation" in the CLI Language Getting - Started Guide. New CLI compiler command line options related to this - feature: + Documentation" and 3.3, "Option Documentation" in the Getting Started + Guide. New CLI compiler command line options related to this feature: --suppress-usage --long-usage @@ -25,8 +24,24 @@ Version 1.1.0 The CLI compiler usage, HTML documentation, and man page are auto-generated using this feature. - * New option, --generate-modifier, triggers generation of the option value - modifiers in addition to the accessors. + * New option, --generate-modifier, triggers the generation of the option + value modifiers in addition to the accessors. + + * Support for erasing the parsed elements from the argc/argv array. See + Section 3.1, "Option Class Definition" in the Getting Started Guide for + more information. + + * New scanner interface. Starting with this version, the option class has + a new constructor which accepts an abstract scanner interface. See Section + 3.1, "Option Class Definition" in the Getting Started Guide for more + information. + + * New option, --generate-file-scanner, triggers the generation of the + argv_file_scanner scanner implementation which provides support for + reading command line arguments from the argv array as well as files + specified with command line options. For more information see Section + 3.1, "Option Class Definition" in the Getting Started Guide as well as + the 'file' example. Version 1.0.0 diff --git a/cli/header.cxx b/cli/header.cxx index 0cc9ede..012933d 100644 --- a/cli/header.cxx +++ b/cli/header.cxx @@ -73,30 +73,39 @@ namespace // string um ("::cli::unknown_mode"); - os << name << " (int argc," << endl + os << name << " (int& argc," << endl << "char** argv," << endl + << "bool erase = false," << endl << um << " option = " << um << "::fail," << endl << um << " argument = " << um << "::stop);" << endl; os << name << " (int start," << endl - << "int argc," << endl + << "int& argc," << endl << "char** argv," << endl + << "bool erase = false," << endl << um << " option = " << um << "::fail," << endl << um << " argument = " << um << "::stop);" << endl; - os << name << " (int argc," << endl + os << name << " (int& argc," << endl << "char** argv," << endl << "int& end," << endl + << "bool erase = false," << endl << um << " option = " << um << "::fail," << endl << um << " argument = " << um << "::stop);" << endl; os << name << " (int start," << endl - << "int argc," << endl + << "int& argc," << endl << "char** argv," << endl << "int& end," << endl + << "bool erase = false," << endl + << um << " option = " << um << "::fail," << endl + << um << " argument = " << um << "::stop);" + << endl; + + os << name << " (::cli::scanner&," << endl << um << " option = " << um << "::fail," << endl << um << " argument = " << um << "::stop);" << endl; diff --git a/cli/options.cli b/cli/options.cli index b9df64f..afd0f98 100644 --- a/cli/options.cli +++ b/cli/options.cli @@ -3,8 +3,8 @@ // copyright : Copyright (c) 2009 Code Synthesis Tools CC // license : MIT; see accompanying LICENSE file -// NOTE: Make sure you have a working CLI compiler around -// before modifying this file. +// NOTE: Make sure you have a working CLI compiler around before +// modifying this file. // include ; @@ -28,6 +28,13 @@ class options "Generate option value modifiers in addition to accessors." }; + bool --generate-file-scanner + { + "Generate the \c{argv_file_scanner} implementation. This scanner is + capable of reading command line arguments from the \c{argv} array as + well as files specified with command line options." + }; + bool --suppress-inline { "Generate all functions non-inline. By default simple functions are diff --git a/cli/options.cxx b/cli/options.cxx index 399069e..6d4ee87 100644 --- a/cli/options.cxx +++ b/cli/options.cxx @@ -132,7 +132,22 @@ namespace cli next () { if (i_ < argc_) - return argv_[i_++]; + { + const char* r (argv_[i_]); + + if (erase_) + { + for (int i (i_ + 1); i < argc_; ++i) + argv_[i - 1] = argv_[i]; + + --argc_; + argv_[argc_] = 0; + } + else + ++i_; + + return r; + } else throw eos_reached (); } @@ -141,7 +156,7 @@ namespace cli skip () { if (i_ < argc_) - i_++; + ++i_; else throw eos_reached (); } @@ -289,14 +304,16 @@ namespace cli // options:: -options (int argc, +options (int& argc, char** argv, + bool erase, ::cli::unknown_mode opt, ::cli::unknown_mode arg) : help_ (), version_ (), output_dir_ (), generate_modifier_ (), + generate_file_scanner_ (), suppress_inline_ (), suppress_usage_ (), long_usage_ (), @@ -322,20 +339,22 @@ options (int argc, guard_prefix_ (), reserved_name_ () { - ::cli::argv_scanner s (argc, argv); + ::cli::argv_scanner s (argc, argv, erase); _parse (s, opt, arg); } options:: options (int start, - int argc, + int& argc, char** argv, + bool erase, ::cli::unknown_mode opt, ::cli::unknown_mode arg) : help_ (), version_ (), output_dir_ (), generate_modifier_ (), + generate_file_scanner_ (), suppress_inline_ (), suppress_usage_ (), long_usage_ (), @@ -361,20 +380,22 @@ options (int start, guard_prefix_ (), reserved_name_ () { - ::cli::argv_scanner s (start, argc, argv); + ::cli::argv_scanner s (start, argc, argv, erase); _parse (s, opt, arg); } options:: -options (int argc, +options (int& argc, char** argv, int& end, + bool erase, ::cli::unknown_mode opt, ::cli::unknown_mode arg) : help_ (), version_ (), output_dir_ (), generate_modifier_ (), + generate_file_scanner_ (), suppress_inline_ (), suppress_usage_ (), long_usage_ (), @@ -400,22 +421,24 @@ options (int argc, guard_prefix_ (), reserved_name_ () { - ::cli::argv_scanner s (argc, argv); + ::cli::argv_scanner s (argc, argv, erase); _parse (s, opt, arg); end = s.end (); } options:: options (int start, - int argc, + int& argc, char** argv, int& end, + bool erase, ::cli::unknown_mode opt, ::cli::unknown_mode arg) : help_ (), version_ (), output_dir_ (), generate_modifier_ (), + generate_file_scanner_ (), suppress_inline_ (), suppress_usage_ (), long_usage_ (), @@ -441,11 +464,48 @@ options (int start, guard_prefix_ (), reserved_name_ () { - ::cli::argv_scanner s (start, argc, argv); + ::cli::argv_scanner s (start, argc, argv, erase); _parse (s, opt, arg); end = s.end (); } +options:: +options (::cli::scanner& s, + ::cli::unknown_mode opt, + ::cli::unknown_mode arg) +: help_ (), + version_ (), + output_dir_ (), + generate_modifier_ (), + generate_file_scanner_ (), + suppress_inline_ (), + suppress_usage_ (), + long_usage_ (), + option_length_ (0), + generate_cxx_ (), + generate_man_ (), + generate_html_ (), + man_prologue_ (), + man_epilogue_ (), + html_prologue_ (), + html_epilogue_ (), + class__ (), + stdout_ (), + hxx_suffix_ (".hxx"), + ixx_suffix_ (".ixx"), + cxx_suffix_ (".cxx"), + man_suffix_ (".1"), + html_suffix_ (".html"), + option_prefix_ ("-"), + option_separator_ ("--"), + include_with_brackets_ (), + include_prefix_ (), + guard_prefix_ (), + reserved_name_ () +{ + _parse (s, opt, arg); +} + void options:: print_usage (::std::ostream& os) { @@ -459,6 +519,8 @@ print_usage (::std::ostream& os) os << "--generate-modifier Generate option value modifiers in addition to" << ::std::endl << " accessors." << ::std::endl; + os << "--generate-file-scanner Generate the 'argv_file_scanner' implementation." << ::std::endl; + os << "--suppress-inline Generate all functions non-inline." << ::std::endl; os << "--suppress-usage Suppress the generation of the usage printing code." << ::std::endl; @@ -547,6 +609,8 @@ struct _cli_options_map_init &::cli::thunk< options, std::string, &options::output_dir_ >; _cli_options_map_["--generate-modifier"] = &::cli::thunk< options, bool, &options::generate_modifier_ >; + _cli_options_map_["--generate-file-scanner"] = + &::cli::thunk< options, bool, &options::generate_file_scanner_ >; _cli_options_map_["--suppress-inline"] = &::cli::thunk< options, bool, &options::suppress_inline_ >; _cli_options_map_["--suppress-usage"] = diff --git a/cli/options.hxx b/cli/options.hxx index 2be8e91..54143b9 100644 --- a/cli/options.hxx +++ b/cli/options.hxx @@ -166,8 +166,8 @@ namespace cli class argv_scanner: public scanner { public: - argv_scanner (int argc, char** argv); - argv_scanner (int start, int argc, char** argv); + argv_scanner (int& argc, char** argv, bool erase = false); + argv_scanner (int start, int& argc, char** argv, bool erase = false); int end () const; @@ -184,9 +184,11 @@ namespace cli virtual void skip (); - private:int i_; - int argc_; + private: + int i_; + int& argc_; char** argv_; + bool erase_; }; } @@ -202,27 +204,35 @@ class options { public: - options (int argc, + options (int& argc, char** argv, + bool erase = false, ::cli::unknown_mode option = ::cli::unknown_mode::fail, ::cli::unknown_mode argument = ::cli::unknown_mode::stop); options (int start, - int argc, + int& argc, char** argv, + bool erase = false, ::cli::unknown_mode option = ::cli::unknown_mode::fail, ::cli::unknown_mode argument = ::cli::unknown_mode::stop); - options (int argc, + options (int& argc, char** argv, int& end, + bool erase = false, ::cli::unknown_mode option = ::cli::unknown_mode::fail, ::cli::unknown_mode argument = ::cli::unknown_mode::stop); options (int start, - int argc, + int& argc, char** argv, int& end, + bool erase = false, + ::cli::unknown_mode option = ::cli::unknown_mode::fail, + ::cli::unknown_mode argument = ::cli::unknown_mode::stop); + + options (::cli::scanner&, ::cli::unknown_mode option = ::cli::unknown_mode::fail, ::cli::unknown_mode argument = ::cli::unknown_mode::stop); @@ -242,6 +252,9 @@ class options generate_modifier () const; const bool& + generate_file_scanner () const; + + const bool& suppress_inline () const; const bool& @@ -329,6 +342,7 @@ class options bool version_; std::string output_dir_; bool generate_modifier_; + bool generate_file_scanner_; bool suppress_inline_; bool suppress_usage_; bool long_usage_; diff --git a/cli/options.ixx b/cli/options.ixx index 6fcc80f..74d7b15 100644 --- a/cli/options.ixx +++ b/cli/options.ixx @@ -87,14 +87,14 @@ namespace cli // argv_scanner // inline argv_scanner:: - argv_scanner (int argc, char** argv) - : i_ (1), argc_ (argc), argv_ (argv) + argv_scanner (int& argc, char** argv, bool erase) + : i_ (1), argc_ (argc), argv_ (argv), erase_ (erase) { } inline argv_scanner:: - argv_scanner (int start, int argc, char** argv) - : i_ (start), argc_ (argc), argv_ (argv) + argv_scanner (int start, int& argc, char** argv, bool erase) + : i_ (start), argc_ (argc), argv_ (argv), erase_ (erase) { } @@ -133,6 +133,12 @@ generate_modifier () const } inline const bool& options:: +generate_file_scanner () const +{ + return this->generate_file_scanner_; +} + +inline const bool& options:: suppress_inline () const { return this->suppress_inline_; diff --git a/cli/runtime-header.cxx b/cli/runtime-header.cxx index cf3ebd3..5da33c0 100644 --- a/cli/runtime-header.cxx +++ b/cli/runtime-header.cxx @@ -12,6 +12,9 @@ generate_runtime_header (context& ctx) { ostream& os (ctx.os); + if (ctx.options.generate_file_scanner ()) + os << "#include " << endl; + os << "#include " << endl << "#include " << endl << "#include " << endl @@ -155,6 +158,30 @@ generate_runtime_header (context& ctx) << "what () const throw ();" << "};"; + if (ctx.options.generate_file_scanner ()) + { + os << "class file_io_failure: public exception" + << "{" + << "public:" << endl + << "virtual" << endl + << "~file_io_failure () throw ();" + << endl + << "file_io_failure (const std::string& file);" + << endl + << "const std::string&" << endl + << "file () const;" + << endl + << "virtual void" << endl + << "print (std::ostream&) const;" + << endl + << "virtual const char*" << endl + << "what () const throw ();" + << endl + << "private:" << endl + << "std::string file_;" + << "};"; + } + // scanner // os << "class scanner" @@ -181,8 +208,8 @@ generate_runtime_header (context& ctx) os << "class argv_scanner: public scanner" << "{" << "public:" << endl - << "argv_scanner (int argc, char** argv);" - << "argv_scanner (int start, int argc, char** argv);" + << "argv_scanner (int& argc, char** argv, bool erase = false);" + << "argv_scanner (int start, int& argc, char** argv, bool erase = false);" << endl << "int" << endl << "end () const;" @@ -199,11 +226,58 @@ generate_runtime_header (context& ctx) << "virtual void" << endl << "skip ();" << endl - << "private:" + << "private:" << endl << "int i_;" - << "int argc_;" + << "int& argc_;" << "char** argv_;" + << "bool erase_;" << "};"; + // argv_file_scanner + // + if (ctx.options.generate_file_scanner ()) + { + os << "class argv_file_scanner: public argv_scanner" + << "{" + << "public:" << endl + << "argv_file_scanner (int& argc," << endl + << "char** argv," << endl + << "const std::string& file_option," << endl + << "bool erase = false);" + << endl + << "argv_file_scanner (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "const std::string& file_option," << endl + << "bool erase = false);" + << endl + << "virtual bool" << endl + << "more ();" + << endl + << "virtual const char*" << endl + << "peek ();" + << endl + << "virtual const char*" << endl + << "next ();" + << endl + << "virtual void" << endl + << "skip ();" + << endl + << "private:" << endl + << "void" << endl + << "load (const char* file);" + << endl + << "typedef argv_scanner base;" + << endl + << "const std::string option_;" + << "std::string hold_;" + << "std::deque args_;"; + + if (!ctx.opt_sep.empty ()) + os << "bool skip_;"; + + os << "};"; + } + os << "}"; // namespace cli } diff --git a/cli/runtime-inline.cxx b/cli/runtime-inline.cxx index d4ce3ec..c99739c 100644 --- a/cli/runtime-inline.cxx +++ b/cli/runtime-inline.cxx @@ -103,18 +103,36 @@ generate_runtime_inline (context& ctx) << "return value_;" << "}"; + if (ctx.options.generate_file_scanner ()) + { + os << "// file_io_failure" << endl + << "//" << endl + + << inl << "file_io_failure::" << endl + << "file_io_failure (const std::string& file)" << endl + << ": file_ (file)" + << "{" + << "}" + + << inl << "const std::string& file_io_failure::" << endl + << "file () const" + << "{" + << "return file_;" + << "}"; + } + os << "// argv_scanner" << endl << "//" << endl; os << inl << "argv_scanner::" << endl - << "argv_scanner (int argc, char** argv)" << endl - << ": i_ (1), argc_ (argc), argv_ (argv)" + << "argv_scanner (int& argc, char** argv, bool erase)" << endl + << ": i_ (1), argc_ (argc), argv_ (argv), erase_ (erase)" << "{" << "}"; os << inl << "argv_scanner::" << endl - << "argv_scanner (int start, int argc, char** argv)" << endl - << ": i_ (start), argc_ (argc), argv_ (argv)" + << "argv_scanner (int start, int& argc, char** argv, bool erase)" << endl + << ": i_ (start), argc_ (argc), argv_ (argv), erase_ (erase)" << "{" << "}"; @@ -124,5 +142,42 @@ generate_runtime_inline (context& ctx) << "return i_;" << "}"; + // argv_file_scanner + // + if (ctx.options.generate_file_scanner ()) + { + bool sep (!ctx.opt_sep.empty ()); + + os << "// argv_file_scanner" << endl + << "//" << endl; + + os << inl << "argv_file_scanner::" << endl + << "argv_file_scanner (int& argc," << endl + << "char** argv," << endl + << "const std::string& option," << endl + << "bool erase)" << endl + << ": argv_scanner (argc, argv, erase)," << endl + << " option_ (option)"; + if (sep) + os << "," << endl + << " skip_ (false)"; + os << "{" + << "}"; + + os << inl << "argv_file_scanner::" << endl + << "argv_file_scanner (int start," << endl + << "int& argc," << endl + << "char** argv," << endl + << "const std::string& option," << endl + << "bool erase)" << endl + << ": argv_scanner (start, argc, argv, erase)," << endl + << " option_ (option)"; + if (sep) + os << "," << endl + << " skip_ (false)"; + os << "{" + << "}"; + } + os << "}"; // namespace cli } diff --git a/cli/runtime-source.cxx b/cli/runtime-source.cxx index 3003b6b..323b604 100644 --- a/cli/runtime-source.cxx +++ b/cli/runtime-source.cxx @@ -17,8 +17,13 @@ generate_runtime_source (context& ctx) << "#include " << endl << "#include " << endl << "#include " << endl - << "#include " << endl - << endl; + << "#include " << endl; + + if (ctx.options.generate_file_scanner ()) + os << "#include " << endl + << "#include " << endl; + + os << endl; os << "namespace cli" << "{"; @@ -31,11 +36,13 @@ generate_runtime_source (context& ctx) << "~unknown_option () throw ()" << "{" << "}" + << "void unknown_option::" << endl << "print (std::ostream& os) const" << "{" << "os << \"unknown option '\" << option () << \"'\";" << "}" + << "const char* unknown_option::" << endl << "what () const throw ()" << "{" @@ -50,11 +57,13 @@ generate_runtime_source (context& ctx) << "~unknown_argument () throw ()" << "{" << "}" + << "void unknown_argument::" << endl << "print (std::ostream& os) const" << "{" << "os << \"unknown argument '\" << argument () << \"'\";" << "}" + << "const char* unknown_argument::" << endl << "what () const throw ()" << "{" @@ -69,11 +78,13 @@ generate_runtime_source (context& ctx) << "~missing_value () throw ()" << "{" << "}" + << "void missing_value::" << endl << "print (std::ostream& os) const" << "{" << "os << \"missing value for option '\" << option () << \"'\";" << "}" + << "const char* missing_value::" << endl << "what () const throw ()" << "{" @@ -88,12 +99,14 @@ generate_runtime_source (context& ctx) << "~invalid_value () throw ()" << "{" << "}" + << "void invalid_value::" << endl << "print (std::ostream& os) const" << "{" << "os << \"invalid value '\" << value () << \"' for option '\"" << endl << " << option () << \"'\";" << "}" + << "const char* invalid_value::" << endl << "what () const throw ()" << "{" @@ -109,12 +122,37 @@ generate_runtime_source (context& ctx) << "{" << "os << what ();" << "}" + << "const char* eos_reached::" << endl << "what () const throw ()" << "{" << "return \"end of argument stream reached\";" << "}"; + // file_io_failure + // + if (ctx.options.generate_file_scanner ()) + { + os << "// file_io_failure" << endl + << "//" << endl + << "file_io_failure::" << endl + << "~file_io_failure () throw ()" + << "{" + << "}" + + << "void file_io_failure::" << endl + << "print (std::ostream& os) const" + << "{" + << "os << \"unable to open file '\" << file () << \"' or read failure\";" + << "}" + + << "const char* file_io_failure::" << endl + << "what () const throw ()" + << "{" + << "return \"unable to open file or read failure\";" + << "}"; + } + // scanner // os << "// scanner" << endl @@ -147,8 +185,23 @@ generate_runtime_source (context& ctx) << "const char* argv_scanner::" << endl << "next ()" << "{" - << "if (i_ < argc_)" << endl - << "return argv_[i_++];" + << "if (i_ < argc_)" + << "{" + << "const char* r (argv_[i_]);" + << endl + << "if (erase_)" + << "{" + << "for (int i (i_ + 1); i < argc_; ++i)" << endl + << "argv_[i - 1] = argv_[i];" + << endl + << "--argc_;" + << "argv_[argc_] = 0;" + << "}" + << "else" << endl + << "++i_;" + << endl + << "return r;" + << "}" << "else" << endl << "throw eos_reached ();" << "}" @@ -157,11 +210,176 @@ generate_runtime_source (context& ctx) << "skip ()" << "{" << "if (i_ < argc_)" << endl - << "i_++;" + << "++i_;" << "else" << endl << "throw eos_reached ();" << "}"; + // argv_file_scanner + // + if (ctx.options.generate_file_scanner ()) + { + bool sep (!ctx.opt_sep.empty ()); + + os << "// argv_file_scanner" << endl + << "//" << endl + + << "bool argv_file_scanner::" << endl + << "more ()" + << "{" + << "if (!args_.empty ())" << endl + << "return true;" + << endl + << "while (base::more ())" + << "{" + << "// See if the next argument is the file option." << endl + << "//" << endl + << "const char* a (base::peek ());" + << endl + << "if (" << (sep ? "!skip_ && " : "") << "a == option_)" + << "{" + << "base::next ();" + << endl + << "if (!base::more ())" << endl + << "throw missing_value (option_);" + << endl + << "load (base::next ());" + << endl + << "if (!args_.empty ())" << endl + << "return true;" + << "}" + << "else" + << "{"; + if (sep) + os << "if (!skip_)" << endl + << "skip_ = (std::strcmp (a, \"" << ctx.opt_sep << "\") == 0);" + << endl; + os << "return true;" + << "}" + << "}" // while + << "return false;" + << "}" + + << "const char* argv_file_scanner::" << endl + << "peek ()" + << "{" + << "if (!more ())" << endl + << "throw eos_reached ();" + << endl + << "return args_.empty () ? base::peek () : args_.front ().c_str ();" + << "}" + + << "const char* argv_file_scanner::" << endl + << "next ()" + << "{" + << "if (!more ())" << endl + << "throw eos_reached ();" + << endl + << "if (args_.empty ())" << endl + << "return base::next ();" + << "else" + << "{" + << "hold_.swap (args_.front ());" + << "args_.pop_front ();" + << "return hold_.c_str ();" + << "}" + << "}" + + << "void argv_file_scanner::" << endl + << "skip ()" + << "{" + << "if (!more ())" << endl + << "throw eos_reached ();" + << endl + << "if (args_.empty ())" << endl + << "return base::skip ();" + << "else" << endl + << "args_.pop_front ();" + << "}" + + << "void argv_file_scanner::" << endl + << "load (const char* file)" + << "{" + << "using namespace std;" + << endl + << "ifstream is (file);" + << endl + << "if (!is.is_open ())" << endl + << "throw file_io_failure (file);" + << endl + << "while (!is.eof ())" + << "{" + << "string line;" + << "getline (is, line);" + << endl + << "if (is.fail () && !is.eof ())" << endl + << "throw file_io_failure (file);" + << endl + << "string::size_type n (line.size ());" + << endl + << "// Trim the line from leading and trailing whitespaces." << endl + << "//" << endl + << "if (n != 0)" + << "{" + << "const char* f (line.c_str ());" + << "const char* l (f + n);" + << endl + << "const char* of (f);" + << "while (f < l && (*f == ' ' || *f == '\\t' || *f == '\\r'))" << endl + << "++f;" + << endl + << "--l;" + << endl + << "const char* ol (l);" + << "while (l > f && (*l == ' ' || *l == '\\t' || *l == '\\r'))" << endl + << "--l;" + << endl + << "if (f != of || l != ol)" << endl + << "line = f <= l ? string (f, l - f + 1) : string ();" + << "}" + << "// Ignore empty lines, those that start with #." << endl + << "//" << endl + << "if (line.empty () || line[0] == '#')" << endl + << "continue;" + << endl + << "string::size_type p (line.find (' '));" + << endl + << "if (p == string::npos)" + << "{"; + if (sep) + os << "if (!skip_)" << endl + << "skip_ = (line == \"" << ctx.opt_sep << "\");" + << endl; + os << "args_.push_back (line);" + << "}" + << "else" + << "{" + << "string s1 (line, 0, p);" + << endl + << "// Skip leading whitespaces in the argument." << endl + << "//" << endl + << "n = line.size ();" + << "for (++p; p < n; ++p)" + << "{" + << "char c (line[p]);" + << endl + << "if (c != ' ' && c != '\\t' && c != '\\r')" << endl + << "break;" + << "}" + << "string s2 (line, p);" + << endl + << "if (" << (sep ? "!skip_ && " : "") << "s1 == option_)" << endl + << "load (s2.c_str ());" + << "else" + << "{" + << "args_.push_back (s1);" + << "args_.push_back (s2);" + << "}" + << "}" + << "}" // while + << "}"; + } + // parser class template & its specializations // os << "template " << endl diff --git a/cli/source.cxx b/cli/source.cxx index 5f0b199..1c0b890 100644 --- a/cli/source.cxx +++ b/cli/source.cxx @@ -24,7 +24,11 @@ namespace os << "," << endl << " "; else + { + os << endl + << ": "; comma_ = true; + } os << emember (o); @@ -356,75 +360,88 @@ namespace string um ("::cli::unknown_mode"); os << name << "::" << endl - << name << " (int argc," << endl + << name << " (int& argc," << endl << "char** argv," << endl + << "bool erase," << endl << um << " opt," << endl - << um << " arg)" << endl - << ": "; + << um << " arg)"; { option_init init (*this); traversal::names names_init (init); names (c, names_init); } os << "{" - << "::cli::argv_scanner s (argc, argv);" + << "::cli::argv_scanner s (argc, argv, erase);" << "_parse (s, opt, arg);" << "}"; os << name << "::" << endl << name << " (int start," << endl - << "int argc," << endl + << "int& argc," << endl << "char** argv," << endl + << "bool erase," << endl << um << " opt," << endl - << um << " arg)" << endl - << ": "; + << um << " arg)"; { option_init init (*this); traversal::names names_init (init); names (c, names_init); } os << "{" - << "::cli::argv_scanner s (start, argc, argv);" + << "::cli::argv_scanner s (start, argc, argv, erase);" << "_parse (s, opt, arg);" << "}"; os << name << "::" << endl - << name << " (int argc," << endl + << name << " (int& argc," << endl << "char** argv," << endl << "int& end," << endl + << "bool erase," << endl << um << " opt," << endl - << um << " arg)" << endl - << ": "; + << um << " arg)"; { option_init init (*this); traversal::names names_init (init); names (c, names_init); } os << "{" - << "::cli::argv_scanner s (argc, argv);" + << "::cli::argv_scanner s (argc, argv, erase);" << "_parse (s, opt, arg);" << "end = s.end ();" << "}"; os << name << "::" << endl << name << " (int start," << endl - << "int argc," << endl + << "int& argc," << endl << "char** argv," << endl << "int& end," << endl + << "bool erase," << endl << um << " opt," << endl - << um << " arg)" << endl - << ": "; + << um << " arg)"; { option_init init (*this); traversal::names names_init (init); names (c, names_init); } os << "{" - << "::cli::argv_scanner s (start, argc, argv);" + << "::cli::argv_scanner s (start, argc, argv, erase);" << "_parse (s, opt, arg);" << "end = s.end ();" << "}"; + os << name << "::" << endl + << name << " (::cli::scanner& s," << endl + << um << " opt," << endl + << um << " arg)"; + { + option_init init (*this); + traversal::names names_init (init); + names (c, names_init); + } + os << "{" + << "_parse (s, opt, arg);" + << "}"; + // usage // if (usage) diff --git a/doc/cli.1 b/doc/cli.1 index 7c89ace..0a2021f 100644 --- a/doc/cli.1 +++ b/doc/cli.1 @@ -73,6 +73,11 @@ Write the generated files to \fIdir\fP instead of the current directory\. .IP "\fB--generate-modifier\fP" Generate option value modifiers in addition to accessors\. +.IP "\fB--generate-file-scanner\fP" +Generate the argv_file_scanner implementation\. This scanner is capable of +reading command line arguments from the argv array as well as files +specified with command line options\. + .IP "\fB--suppress-inline\fP" Generate all functions non-inline\. By default simple functions are made inline\. This option suppresses creation of the inline file\. diff --git a/doc/cli.xhtml b/doc/cli.xhtml index e431f97..3389c77 100644 --- a/doc/cli.xhtml +++ b/doc/cli.xhtml @@ -96,6 +96,11 @@
--generate-modifier
Generate option value modifiers in addition to accessors.
+
--generate-file-scanner
+
Generate the argv_file_scanner implementation. This scanner is + capable of reading command line arguments from the argv array + as well as files specified with command line options.
+
--suppress-inline
Generate all functions non-inline. By default simple functions are made inline. This option suppresses creation of the inline file.
diff --git a/doc/guide/index.xhtml b/doc/guide/index.xhtml index 89f56e7..0b43209 100644 --- a/doc/guide/index.xhtml +++ b/doc/guide/index.xhtml @@ -561,27 +561,35 @@ class options class options { public: - options (int argc, + options (int& argc, char** argv, + bool erase = false, cli::unknown_mode opt_mode = cli::unknown_mode::fail, cli::unknown_mode arg_mode = cli::unknown_mode::stop); options (int start, - int argc, + int& argc, char** argv, + bool erase = false, cli::unknown_mode opt_mode = cli::unknown_mode::fail, cli::unknown_mode arg_mode = cli::unknown_mode::stop); - options (int argc, + options (int& argc, char** argv, int& end, + bool erase = false, cli::unknown_mode opt_mode = cli::unknown_mode::fail, cli::unknown_mode arg_mode = cli::unknown_mode::stop); options (int start, - int argc, + int& argc, char** argv, int& end, + bool erase = false, + cli::unknown_mode opt_mode = cli::unknown_mode::fail, + cli::unknown_mode arg_mode = cli::unknown_mode::stop); + + options (cli::scanner&, cli::unknown_mode opt_mode = cli::unknown_mode::fail, cli::unknown_mode arg_mode = cli::unknown_mode::stop); @@ -621,7 +629,10 @@ public: from position 1, skipping the executable name in argv[0]. The end argument is used to return the position in the arguments array where the parsing of options stopped. This is the - position of the first program argument, if any.

+ position of the first program argument, if any. If the erase + argument is true, then the recognized options and their + values are removed from the argv array and the + argc count is updated accordingly.

The opt_mode and arg_mode arguments specify the parser behavior when it encounters an unknown option @@ -657,8 +668,96 @@ namespace cli exception (described blow) on encountering an unknown option or argument, respectively.

-

The parsing constructor (those with the argc/argv arguments) - can throw the following exceptions: cli::unknown_option, +

Instead of the argc/argv arguments, the last overloaded + constructor accepts the cli::scanner object. It is part + of the generated CLI runtime support code and has the following + abstract interface:

+ +
+namespace cli
+{
+  class scanner
+  {
+  public:
+    virtual bool
+    more () = 0;
+
+    virtual const char*
+    peek () = 0;
+
+    virtual const char*
+    next () = 0;
+
+    virtual void
+    skip () = 0;
+  };
+}
+  
+ +

The CLI runtime also provides two implementations of this interface: + cli::argv_scanner and cli::argv_file_scanner. + The first implementation is a simple scanner for the argv + array (it is used internally by all the other constructors) and has the + following interface:

+ +
+namespace cli
+{
+  class argv_scanner
+  {
+  public:
+    argv_scanner (int& argc, char** argv, bool erase = false);
+    argv_scanner (int start, int& argc, char** argv, bool erase = false);
+
+    int
+    end () const;
+
+    ...
+  };
+}
+  
+ +

The cli::argv_file_scanner implementation provides + support for reading command line arguments from the argv + array as well as files specified with command line options. It is + generated only if explicitly requested with the + --generate-file-scanner CLI compiler option and has + the following interface:

+ +
+namespace cli
+{
+  class argv_file_scanner
+  {
+  public:
+    argv_file_scanner (int& argc,
+                       char** argv,
+                       const std::string& file_option,
+                       bool erase = false);
+
+    argv_file_scanner (int start,
+                       int& argc,
+                       char** argv,
+                       const std::string& file_option,
+                       bool erase = false);
+    ...
+  };
+}
+  
+ +

The file_option argument is used to pass the option name + that should be recognized as specifying the file containing additional + options. Such a file contains a set of options, each appearing on a + separate line optionally followed by space and an argument. Empty lines + and lines starting with # are ignored. The semantics + of providing options in a file is equivalent to providing the same set + of options in the same order on the command line at the point where the + options file is specified, except that shell escaping and quoting is not + required. Multiple files can be specified by including several file + options on the command line or inside other files.

+ +

The parsing constructor (those with the argc/argv or + cli::scanner arguments) can throw the following exceptions: cli::unknown_option, cli::unknown_argument, cli::missing_value, and cli::invalid_value. The first two exceptions are thrown on encountering unknown options and arguments, respectively, as @@ -667,6 +766,16 @@ namespace cli thrown when an option value is invalid, for example, a non-integer value is specified for an option of type int.

+

Furthermore, all scanners (and thus the parsing constructors that + call them) can throw the cli::eos_reached exception + which indicates that one of the peek(), next(), + or skip() functions were called while more() + returns false. Catching this exception normally indicates an + error in an option parser implementation. The argv_file_scanner + class can also throw the cli::file_io_failure exception + which indicates that a file could not be opened or there was a reading + error.

+

All CLI exceptions are derived from the common cli::exception class which implements the polymorphic std::ostream insertion. For example, if you catch the cli::unknown_option @@ -759,6 +868,31 @@ namespace cli virtual const char* what () const throw (); }; + + class eos_reached: public exception + { + public: + virtual void + print (std::ostream&) const; + + virtual const char* + what () const throw (); + }; + + class file_io_failure: public exception + { + public: + file_io_failure (const std::string& file); + + const std::string& + file () const; + + virtual void + print (std::ostream&) const; + + virtual const char* + what () const throw (); + }; } diff --git a/examples/README b/examples/README index d333f11..84ef156 100644 --- a/examples/README +++ b/examples/README @@ -9,3 +9,7 @@ hello features Shows how to use various features of the CLI language. + +file + Shows how to allow the users of your application to supply options in + files in addition to the command line. diff --git a/examples/examples-8.0.sln b/examples/examples-8.0.sln index 8b7e077..aded850 100644 --- a/examples/examples-8.0.sln +++ b/examples/examples-8.0.sln @@ -5,6 +5,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hello", "hello\hello-8.0.vc EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "features", "features\features-8.0.vcproj", "{FA7FD071-4FF6-4EB3-96E0-95366AB2CA6F}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "file", "file\file-8.0.vcproj", "{ECCD3577-1DB2-4F38-9ED7-757433B8D66F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -19,6 +21,10 @@ Global {FA7FD071-4FF6-4EB3-96E0-95366AB2CA6F}.Debug|Win32.Build.0 = Debug|Win32 {FA7FD071-4FF6-4EB3-96E0-95366AB2CA6F}.Release|Win32.ActiveCfg = Release|Win32 {FA7FD071-4FF6-4EB3-96E0-95366AB2CA6F}.Release|Win32.Build.0 = Release|Win32 + {ECCD3577-1DB2-4F38-9ED7-757433B8D66F}.Debug|Win32.ActiveCfg = Debug|Win32 + {ECCD3577-1DB2-4F38-9ED7-757433B8D66F}.Debug|Win32.Build.0 = Debug|Win32 + {ECCD3577-1DB2-4F38-9ED7-757433B8D66F}.Release|Win32.ActiveCfg = Release|Win32 + {ECCD3577-1DB2-4F38-9ED7-757433B8D66F}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/examples/examples-9.0.sln b/examples/examples-9.0.sln index 8a2d2b5..d0c1be5 100644 --- a/examples/examples-9.0.sln +++ b/examples/examples-9.0.sln @@ -5,6 +5,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hello", "hello\hello-9.0.vc EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "features", "features\features-9.0.vcproj", "{A2A8DB0D-FA16-4F85-9775-8ADBDD06AE90}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "file", "file\file-9.0.vcproj", "{ECCD3577-1DB2-4F38-9ED7-757433B8D66F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -19,6 +21,10 @@ Global {A2A8DB0D-FA16-4F85-9775-8ADBDD06AE90}.Debug|Win32.Build.0 = Debug|Win32 {A2A8DB0D-FA16-4F85-9775-8ADBDD06AE90}.Release|Win32.ActiveCfg = Release|Win32 {A2A8DB0D-FA16-4F85-9775-8ADBDD06AE90}.Release|Win32.Build.0 = Release|Win32 + {ECCD3577-1DB2-4F38-9ED7-757433B8D66F}.Debug|Win32.ActiveCfg = Debug|Win32 + {ECCD3577-1DB2-4F38-9ED7-757433B8D66F}.Debug|Win32.Build.0 = Debug|Win32 + {ECCD3577-1DB2-4F38-9ED7-757433B8D66F}.Release|Win32.ActiveCfg = Release|Win32 + {ECCD3577-1DB2-4F38-9ED7-757433B8D66F}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/examples/file/README b/examples/file/README new file mode 100644 index 0000000..289fc64 --- /dev/null +++ b/examples/file/README @@ -0,0 +1,38 @@ +This example shows how to allow the users of your application to supply +options in files in addition to the command line. + +The example consists of the following files: + +options.cli + Command line interface description in the CLI language. + +options.hxx +options.ixx +options.cxx + Command line interface implementation in C++. These files are generated + by the CLI compiler from options.cli using the following command line: + + cli --generate-file-scanner hello.cli + + We use the --generate-file-scanner CLI compiler option to include the + argv_file_scanner scanner implementation which provides support for + reading options from files in addition to the command line. + +driver.cxx + Driver for the example. It first creates the argv_file_scanner object + and indicates that the values for the --options-file option should be + recognized as files containing additional options. It then passes this + scanner object to the option class which parses the command line. The + driver then prints the option values. + +test.ops + Sample options file. + +To run this example you can try the following command line: + +$ ./driver --verbose 2 --val 1 --options-file test.ops --val 4 + +The output will be: + +verbosity: 5 +values: 1 2 3 4 diff --git a/examples/file/driver.cxx b/examples/file/driver.cxx new file mode 100644 index 0000000..df7a67c --- /dev/null +++ b/examples/file/driver.cxx @@ -0,0 +1,36 @@ +// file : examples/file/driver.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include +#include +#include + +#include "options.hxx" + +using namespace std; + +int +main (int argc, char* argv[]) +{ + try + { + cli::argv_file_scanner s (argc, argv, "--options-file"); + options o (s); + + cout << "verbosity: " << o.verbose () << endl + << "values: "; + + copy (o.val ().begin (), + o.val ().end (), + ostream_iterator (cout, " ")); + + cerr << endl; + } + catch (const cli::exception& e) + { + cerr << e << endl; + return 1; + } +} diff --git a/examples/file/file-8.0.vcproj b/examples/file/file-8.0.vcproj new file mode 100644 index 0000000..0b1000b --- /dev/null +++ b/examples/file/file-8.0.vcproj @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/file/file-9.0.vcproj b/examples/file/file-9.0.vcproj new file mode 100644 index 0000000..c2a78e5 --- /dev/null +++ b/examples/file/file-9.0.vcproj @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/file/makefile b/examples/file/makefile new file mode 100644 index 0000000..04c45a7 --- /dev/null +++ b/examples/file/makefile @@ -0,0 +1,74 @@ +# file : examples/file/makefile +# author : Boris Kolpackov +# copyright : Copyright (c) 2009 Code Synthesis Tools CC +# license : MIT; see accompanying LICENSE file + +include $(dir $(lastword $(MAKEFILE_LIST)))../../build/bootstrap.make + +cli := options.cli +cxx := driver.cxx + +obj := $(addprefix $(out_base)/,$(cxx:.cxx=.o) $(cli:.cli=.o)) +dep := $(obj:.o=.o.d) + +driver := $(out_base)/driver +install := $(out_base)/.install +clean := $(out_base)/.clean + +# Build. +# +$(driver): $(obj) + +$(obj) $(dep): cpp_options := -I$(out_base) + +genf := $(cli:.cli=.hxx) $(cli:.cli=.ixx) $(cli:.cli=.cxx) +gen := $(addprefix $(out_base)/,$(genf)) + +$(gen): cli := $(out_root)/cli/cli +$(gen): cli_options := --generate-file-scanner +$(gen): $(out_root)/cli/cli + +$(call include-dep,$(dep),$(obj),$(gen)) + +# Convenience alias for default target. +# +$(out_base)/: $(driver) + +# Install +# +$(install): path := $(subst $(src_root)/,,$(src_base)) +$(install): + $(call install-data,$(src_base)/driver.cxx,$(install_doc_dir)/cli/$(path)/driver.cxx) + $(call install-data,$(src_base)/options.cli,$(install_doc_dir)/cli/$(path)/options.cli) + $(call install-data,$(src_base)/README,$(install_doc_dir)/cli/$(path)/README) + $(call install-data,$(src_base)/test.ops,$(install_doc_dir)/cli/$(path)/test.ops) + +# Clean. +# +$(clean): $(driver).o.clean \ + $(addsuffix .cxx.clean,$(obj)) \ + $(addsuffix .cxx.clean,$(dep)) \ + $(addprefix $(out_base)/,$(cli:.cli=.cxx.cli.clean)) + +# Generated .gitignore. +# +ifeq ($(out_base),$(src_base)) +$(gen): | $(out_base)/.gitignore +$(driver): | $(out_base)/.gitignore + +$(out_base)/.gitignore: files := driver $(genf) +$(clean): $(out_base)/.gitignore.clean + +$(call include,$(bld_root)/git/gitignore.make) +endif + +# How to. +# +$(call include,$(bld_root)/cxx/o-e.make) +$(call include,$(bld_root)/cxx/cxx-o.make) +$(call include,$(bld_root)/cxx/cxx-d.make) +$(call include,$(scf_root)/cli/cli-cxx.make) + +# Dependencies. +# +$(call import,$(src_root)/cli/makefile) diff --git a/examples/file/options.cli b/examples/file/options.cli new file mode 100644 index 0000000..3e6db5a --- /dev/null +++ b/examples/file/options.cli @@ -0,0 +1,7 @@ +include ; + +class options +{ + int --verbose; + std::vector --val; +}; diff --git a/examples/file/test.ops b/examples/file/test.ops new file mode 100644 index 0000000..47097fb --- /dev/null +++ b/examples/file/test.ops @@ -0,0 +1,7 @@ +# Sample options file. Empty lines and lines starting with '#' are +# ignored. +# +--verbose 5 + +--val 2 +--val 3 diff --git a/examples/makefile b/examples/makefile index 135b9bb..9b1de12 100644 --- a/examples/makefile +++ b/examples/makefile @@ -5,7 +5,7 @@ include $(dir $(lastword $(MAKEFILE_LIST)))../build/bootstrap.make -examples := hello features +examples := hello features file default := $(out_base)/ install := $(out_base)/.install diff --git a/tests/ctor/driver.cxx b/tests/ctor/driver.cxx new file mode 100644 index 0000000..c923ec1 --- /dev/null +++ b/tests/ctor/driver.cxx @@ -0,0 +1,69 @@ +// file : tests/ctor/driver.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include "test.hxx" + +int +main (int argc, char* argv[]) +{ + // Test that we can call all the c-tors unambiguously. + // + { + options o1 (argc, argv); + options o2 (argc, argv, + cli::unknown_mode::fail); + options o3 (argc, argv, + cli::unknown_mode::fail, + cli::unknown_mode::stop); + options o4 (argc, argv, true, + cli::unknown_mode::fail, + cli::unknown_mode::stop); + } + + { + options o1 (1, argc, argv); + options o2 (1, argc, argv, + cli::unknown_mode::fail); + options o3 (1, argc, argv, + cli::unknown_mode::fail, + cli::unknown_mode::stop); + options o4 (1, argc, argv, true, + cli::unknown_mode::fail, + cli::unknown_mode::stop); + } + + { + int end; + options o1 (argc, argv, end); + options o2 (argc, argv, end, + cli::unknown_mode::fail); + options o3 (argc, argv, end, + cli::unknown_mode::fail, + cli::unknown_mode::stop); + options o4 (argc, argv, end, true, + cli::unknown_mode::fail, + cli::unknown_mode::stop); + } + + { + int end; + options o1 (1, argc, argv, end); + options o2 (1, argc, argv, end, + cli::unknown_mode::fail); + options o3 (1, argc, argv, end, + cli::unknown_mode::fail, + cli::unknown_mode::stop); + options o4 (1, argc, argv, end, true, + cli::unknown_mode::fail, + cli::unknown_mode::stop); + } + + { + cli::argv_scanner s (argc, argv); + options o1 (s); + options o2 (s, cli::unknown_mode::fail); + options o3 (s, cli::unknown_mode::fail, cli::unknown_mode::stop); + } +} diff --git a/tests/ctor/makefile b/tests/ctor/makefile new file mode 100644 index 0000000..93d832a --- /dev/null +++ b/tests/ctor/makefile @@ -0,0 +1,73 @@ +# file : tests/ctor/makefile +# author : Boris Kolpackov +# copyright : Copyright (c) 2009 Code Synthesis Tools CC +# license : MIT; see accompanying LICENSE file + +include $(dir $(lastword $(MAKEFILE_LIST)))../../build/bootstrap.make + +cxx_tun := driver.cxx +cli_tun := test.cli + +# +# +cxx_obj := $(addprefix $(out_base)/,$(cxx_tun:.cxx=.o) $(cli_tun:.cli=.o)) +cxx_od := $(cxx_obj:.o=.o.d) + +driver := $(out_base)/driver +test := $(out_base)/.test +clean := $(out_base)/.clean + +# Build. +# +$(driver): $(cxx_obj) +$(cxx_obj) $(cxx_od): cpp_options := -I$(out_base) + +genf := $(cli_tun:.cli=.hxx) $(cli_tun:.cli=.ixx) $(cli_tun:.cli=.cxx) +gen := $(addprefix $(out_base)/,$(genf)) + +$(gen): $(out_root)/cli/cli +$(gen): cli := $(out_root)/cli/cli +$(gen): cli_options := + +$(call include-dep,$(cxx_od),$(cxx_obj),$(gen)) + +# Alias for default target. +# +$(out_base)/: $(driver) + +# Test. +# +$(test): driver := $(driver) +$(test): $(driver) + $(call message,test $$1,$$1,$(driver)) + +# Clean. +# +$(clean): \ + $(driver).o.clean \ + $(addsuffix .cxx.clean,$(cxx_obj)) \ + $(addsuffix .cxx.clean,$(cxx_od)) \ + $(addprefix $(out_base)/,$(cli_tun:.cli=.cxx.cli.clean)) + +# Generated .gitignore. +# +ifeq ($(out_base),$(src_base)) +$(driver): | $(out_base)/.gitignore + +$(out_base)/.gitignore: files := driver $(genf) +$(clean): $(out_base)/.gitignore.clean + +$(call include,$(bld_root)/git/gitignore.make) +endif + +# How to. +# +$(call include,$(bld_root)/cxx/o-e.make) +$(call include,$(bld_root)/cxx/cxx-o.make) +$(call include,$(bld_root)/cxx/cxx-d.make) +$(call include,$(scf_root)/cli/cli-cxx.make) + +# Dependencies. +# +$(call import,$(src_root)/cli/makefile) + diff --git a/tests/ctor/test.cli b/tests/ctor/test.cli new file mode 100644 index 0000000..f85ad98 --- /dev/null +++ b/tests/ctor/test.cli @@ -0,0 +1,9 @@ +// file : tests/ctor/test.cli +// author : Boris Kolpackov +// copyright : Copyright (c) 2009 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +class options +{ + bool --help; +}; diff --git a/tests/erase/driver.cxx b/tests/erase/driver.cxx new file mode 100644 index 0000000..ddb6b8d --- /dev/null +++ b/tests/erase/driver.cxx @@ -0,0 +1,35 @@ +// file : tests/erase/driver.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +// Test argument erasing. +// + +#include +#include + +#include "test.hxx" + +using namespace std; + +int +main (int argc, char* argv[]) +{ + options o (argc, argv, true, + cli::unknown_mode::skip, + cli::unknown_mode::skip); + + assert (o.a ()); + assert (o.b () == 123); + + // We should have 'foo bar --arg -- -b 234'. + // + assert (argc == 7); + assert (argv[1] == string ("foo")); + assert (argv[2] == string ("bar")); + assert (argv[3] == string ("--arg")); + assert (argv[4] == string ("--")); + assert (argv[5] == string ("-b")); + assert (argv[6] == string ("234")); +} diff --git a/tests/erase/makefile b/tests/erase/makefile new file mode 100644 index 0000000..5a8f49a --- /dev/null +++ b/tests/erase/makefile @@ -0,0 +1,73 @@ +# file : tests/erase/makefile +# author : Boris Kolpackov +# copyright : Copyright (c) 2009 Code Synthesis Tools CC +# license : MIT; see accompanying LICENSE file + +include $(dir $(lastword $(MAKEFILE_LIST)))../../build/bootstrap.make + +cxx_tun := driver.cxx +cli_tun := test.cli + +# +# +cxx_obj := $(addprefix $(out_base)/,$(cxx_tun:.cxx=.o) $(cli_tun:.cli=.o)) +cxx_od := $(cxx_obj:.o=.o.d) + +driver := $(out_base)/driver +test := $(out_base)/.test +clean := $(out_base)/.clean + +# Build. +# +$(driver): $(cxx_obj) +$(cxx_obj) $(cxx_od): cpp_options := -I$(out_base) + +genf := $(cli_tun:.cli=.hxx) $(cli_tun:.cli=.ixx) $(cli_tun:.cli=.cxx) +gen := $(addprefix $(out_base)/,$(genf)) + +$(gen): $(out_root)/cli/cli +$(gen): cli := $(out_root)/cli/cli +$(gen): cli_options := + +$(call include-dep,$(cxx_od),$(cxx_obj),$(gen)) + +# Alias for default target. +# +$(out_base)/: $(driver) + +# Test. +# +$(test): driver := $(driver) +$(test): $(driver) + $(call message,test $$1,$$1 foo -a bar -b 123 --arg -- -b 234,$(driver)) + +# Clean. +# +$(clean): \ + $(driver).o.clean \ + $(addsuffix .cxx.clean,$(cxx_obj)) \ + $(addsuffix .cxx.clean,$(cxx_od)) \ + $(addprefix $(out_base)/,$(cli_tun:.cli=.cxx.cli.clean)) + +# Generated .gitignore. +# +ifeq ($(out_base),$(src_base)) +$(driver): | $(out_base)/.gitignore + +$(out_base)/.gitignore: files := driver $(genf) +$(clean): $(out_base)/.gitignore.clean + +$(call include,$(bld_root)/git/gitignore.make) +endif + +# How to. +# +$(call include,$(bld_root)/cxx/o-e.make) +$(call include,$(bld_root)/cxx/cxx-o.make) +$(call include,$(bld_root)/cxx/cxx-d.make) +$(call include,$(scf_root)/cli/cli-cxx.make) + +# Dependencies. +# +$(call import,$(src_root)/cli/makefile) + diff --git a/tests/erase/test.cli b/tests/erase/test.cli new file mode 100644 index 0000000..57820c0 --- /dev/null +++ b/tests/erase/test.cli @@ -0,0 +1,10 @@ +// file : tests/erase/test.cli +// author : Boris Kolpackov +// copyright : Copyright (c) 2009 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +class options +{ + bool -a; + int -b; +}; diff --git a/tests/file/base.ops b/tests/file/base.ops new file mode 100644 index 0000000..45d7696 --- /dev/null +++ b/tests/file/base.ops @@ -0,0 +1,2 @@ +-a 21 +-b 21 diff --git a/tests/file/driver.cxx b/tests/file/driver.cxx new file mode 100644 index 0000000..e0aec6e --- /dev/null +++ b/tests/file/driver.cxx @@ -0,0 +1,29 @@ +// file : tests/file/driver.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +// Test argv_file_scanner. +// + +#include + +#include "test.hxx" + +using namespace std; + +int +main (int argc, char* argv[]) +{ + try + { + cli::argv_file_scanner s (argc, argv, "--file"); + + while (s.more ()) + cout << s.next () << endl; + } + catch (const cli::exception& e) + { + cout << e << endl; + } +} diff --git a/tests/file/empty.ops b/tests/file/empty.ops new file mode 100644 index 0000000..ed080fb --- /dev/null +++ b/tests/file/empty.ops @@ -0,0 +1,3 @@ +# Empty options file. +# + diff --git a/tests/file/makefile b/tests/file/makefile new file mode 100644 index 0000000..56e95c6 --- /dev/null +++ b/tests/file/makefile @@ -0,0 +1,96 @@ +# file : tests/file/makefile +# author : Boris Kolpackov +# copyright : Copyright (c) 2009 Code Synthesis Tools CC +# license : MIT; see accompanying LICENSE file + +include $(dir $(lastword $(MAKEFILE_LIST)))../../build/bootstrap.make + +cxx_tun := driver.cxx +cli_tun := test.cli + +tests := 000 001 002 003 + +# +# +cxx_obj := $(addprefix $(out_base)/,$(cxx_tun:.cxx=.o) $(cli_tun:.cli=.o)) +cxx_od := $(cxx_obj:.o=.o.d) + +driver := $(out_base)/driver +test := $(out_base)/.test +clean := $(out_base)/.clean + +# Build. +# +$(driver): $(cxx_obj) +$(cxx_obj) $(cxx_od): cpp_options := -I$(out_base) + +genf := $(cli_tun:.cli=.hxx) $(cli_tun:.cli=.ixx) $(cli_tun:.cli=.cxx) +gen := $(addprefix $(out_base)/,$(genf)) + +$(gen): $(out_root)/cli/cli +$(gen): cli := $(out_root)/cli/cli +$(gen): cli_options := --generate-file-scanner + +$(call include-dep,$(cxx_od),$(cxx_obj),$(gen)) + +# Alias for default target. +# +$(out_base)/: $(driver) + +# Test. +# +test_targets := $(addprefix $(out_base)/.test-,$(tests)) + +$(test): $(test_targets) +$(test_targets): driver := $(driver) + +.PHONY: $(out_base)/.test-% + +$(out_base)/.test-000: $(driver) $(src_base)/test-000.ops + $(call message,test $(out_base)/000,$(driver) -a 1 \ +--file $(src_base)/empty.ops -b 1 --file $(src_base)/base.ops \ +--file $(src_base)/test-000.ops b | diff -u $(src_base)/test-000.std -) + +$(out_base)/.test-001: $(driver) $(src_base)/test-001.ops + $(call message,test $(out_base)/001,$(driver) -a 1 -- --file \ +$(src_base)/test-001.ops b | diff -u $(src_base)/test-001.std -) + +$(out_base)/.test-002: $(driver) $(src_base)/test-002.ops + $(call message,test $(out_base)/002,$(driver) -a 1 --file \ +$(src_base)/test-002.ops --file $(src_base)/empty.ops b | \ +diff -u $(src_base)/test-002.std -) + +$(out_base)/.test-003: $(driver) + $(call message,test $(out_base)/003,$(driver) -a 1 --file \ +$(src_base)/base.ops --file test-003.ops b | diff -u $(src_base)/test-003.std -) + +# Clean. +# +$(clean): \ + $(driver).o.clean \ + $(addsuffix .cxx.clean,$(cxx_obj)) \ + $(addsuffix .cxx.clean,$(cxx_od)) \ + $(addprefix $(out_base)/,$(cli_tun:.cli=.cxx.cli.clean)) + +# Generated .gitignore. +# +ifeq ($(out_base),$(src_base)) +$(driver): | $(out_base)/.gitignore + +$(out_base)/.gitignore: files := driver $(genf) +$(clean): $(out_base)/.gitignore.clean + +$(call include,$(bld_root)/git/gitignore.make) +endif + +# How to. +# +$(call include,$(bld_root)/cxx/o-e.make) +$(call include,$(bld_root)/cxx/cxx-o.make) +$(call include,$(bld_root)/cxx/cxx-d.make) +$(call include,$(scf_root)/cli/cli-cxx.make) + +# Dependencies. +# +$(call import,$(src_root)/cli/makefile) + diff --git a/tests/file/test-000.ops b/tests/file/test-000.ops new file mode 100644 index 0000000..c0462e2 --- /dev/null +++ b/tests/file/test-000.ops @@ -0,0 +1,6 @@ +-a 11 +-b 11 + -a 12 + + -b 12 +a diff --git a/tests/file/test-000.std b/tests/file/test-000.std new file mode 100644 index 0000000..392b83d --- /dev/null +++ b/tests/file/test-000.std @@ -0,0 +1,18 @@ +-a +1 +-b +1 +-a +21 +-b +21 +-a +11 +-b +11 +-a +12 +-b +12 +a +b diff --git a/tests/file/test-001.ops b/tests/file/test-001.ops new file mode 100644 index 0000000..ed080fb --- /dev/null +++ b/tests/file/test-001.ops @@ -0,0 +1,3 @@ +# Empty options file. +# + diff --git a/tests/file/test-001.std b/tests/file/test-001.std new file mode 100644 index 0000000..36fabf3 --- /dev/null +++ b/tests/file/test-001.std @@ -0,0 +1,6 @@ +-a +1 +-- +--file +/home/boris/work/cli/cli/tests/file/test-001.ops +b diff --git a/tests/file/test-002.ops b/tests/file/test-002.ops new file mode 100644 index 0000000..5c728a0 --- /dev/null +++ b/tests/file/test-002.ops @@ -0,0 +1,7 @@ +-a 11 +-b 11 +-- +--file base.ops +-a 12 +-b 12 +a diff --git a/tests/file/test-002.std b/tests/file/test-002.std new file mode 100644 index 0000000..0336afe --- /dev/null +++ b/tests/file/test-002.std @@ -0,0 +1,17 @@ +-a +1 +-a +11 +-b +11 +-- +--file +base.ops +-a +12 +-b +12 +a +--file +/home/boris/work/cli/cli/tests/file/empty.ops +b diff --git a/tests/file/test-003.std b/tests/file/test-003.std new file mode 100644 index 0000000..6e9a6ae --- /dev/null +++ b/tests/file/test-003.std @@ -0,0 +1,7 @@ +-a +1 +-a +21 +-b +21 +unable to open file 'test-003.ops' or read failure diff --git a/tests/file/test.cli b/tests/file/test.cli new file mode 100644 index 0000000..4bd40d0 --- /dev/null +++ b/tests/file/test.cli @@ -0,0 +1,8 @@ +// file : tests/file/test.cli +// author : Boris Kolpackov +// copyright : Copyright (c) 2009 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +class options +{ +}; diff --git a/tests/makefile b/tests/makefile index 422b93b..5eef36e 100644 --- a/tests/makefile +++ b/tests/makefile @@ -5,7 +5,7 @@ include $(dir $(lastword $(MAKEFILE_LIST)))../build/bootstrap.make -tests := lexer parser +tests := ctor erase file lexer parser default := $(out_base)/ test := $(out_base)/.test -- cgit v1.1