From 74dfffa9df361e35a5910f1cf5b1734571bbef91 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 10 Dec 2009 10:50:23 +0200 Subject: Allows additional options to be provided in files (--options-file) Implemented using the new argv_file_scanner scanner implementation. --- cli/cli.cxx | 12 ++-- cli/makefile | 2 +- cli/options.cli | 16 +++++ cli/options.cxx | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- cli/options.hxx | 64 ++++++++++++++++++ cli/options.ixx | 45 +++++++++++++ 6 files changed, 328 insertions(+), 11 deletions(-) (limited to 'cli') diff --git a/cli/cli.cxx b/cli/cli.cxx index 2b909cd..64a79f1 100644 --- a/cli/cli.cxx +++ b/cli/cli.cxx @@ -20,11 +20,12 @@ int main (int argc, char* argv[]) { ostream& e (cerr); + const char* file (0); try { - int end; - options ops (argc, argv, end); + cli::argv_file_scanner scan (argc, argv, "--options-file"); + options ops (scan); // Handle --version // @@ -53,7 +54,7 @@ main (int argc, char* argv[]) return 0; } - if (end == argc) + if (!scan.more ()) { e << "error: no input file specified" << endl << "info: try '" << argv[0] << " --help' for more information" << endl; @@ -61,7 +62,8 @@ main (int argc, char* argv[]) return 1; } - semantics::path path (argv[end]); + file = scan.next (); + semantics::path path (file); ifstream ifs (path.string ().c_str ()); if (!ifs.is_open ()) @@ -91,7 +93,7 @@ main (int argc, char* argv[]) } catch (std::ios_base::failure const&) { - e << argv[1] << ": error: read failure" << endl; + e << file << ": error: read failure" << endl; return 1; } catch (parser::invalid_input const&) diff --git a/cli/makefile b/cli/makefile index 0ebba36..7320630 100644 --- a/cli/makefile +++ b/cli/makefile @@ -64,7 +64,7 @@ genf := $(cli_tun:.cli=.hxx) $(cli_tun:.cli=.ixx) $(cli_tun:.cli=.cxx) gen := $(addprefix $(out_base)/,$(genf)) $(gen): cli := $(out_root)/cli/cli -$(gen): cli_options := --guard-prefix CLI +$(gen): cli_options := --generate-file-scanner --guard-prefix CLI $(call include-dep,$(cxx_od),$(cxx_obj),$(gen)) diff --git a/cli/options.cli b/cli/options.cli index afd0f98..4cce297 100644 --- a/cli/options.cli +++ b/cli/options.cli @@ -198,4 +198,20 @@ class options that should not be used as identifiers. If provided, the replacement name is used instead. All C++ keywords are already in this list." }; + + // This is a "fake" option in that it is actually handled by + // argv_file_scanner. We have it here to get the documentation. + // + std::string --options-file + { + "", + "Read additional options from with each option appearing on a + separate line optionally followed by space and an option value. Empty + lines and lines starting with \cb{#} 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 + \cb{--options-file} option is specified except that shell escaping and + quoting is not required. Repeat this option to specify more than one + options files." + }; }; diff --git a/cli/options.cxx b/cli/options.cxx index 6d4ee87..4e8b67d 100644 --- a/cli/options.cxx +++ b/cli/options.cxx @@ -10,6 +10,8 @@ #include #include #include +#include +#include namespace cli { @@ -104,6 +106,25 @@ namespace cli return "end of argument stream reached"; } + // file_io_failure + // + file_io_failure:: + ~file_io_failure () throw () + { + } + + void file_io_failure:: + print (std::ostream& os) const + { + os << "unable to open file '" << file () << "' or read failure"; + } + + const char* file_io_failure:: + what () const throw () + { + return "unable to open file or read failure"; + } + // scanner // scanner:: @@ -161,6 +182,164 @@ namespace cli throw eos_reached (); } + // argv_file_scanner + // + bool argv_file_scanner:: + more () + { + if (!args_.empty ()) + return true; + + while (base::more ()) + { + // See if the next argument is the file option. + // + const char* a (base::peek ()); + + if (!skip_ && a == option_) + { + base::next (); + + if (!base::more ()) + throw missing_value (option_); + + load (base::next ()); + + if (!args_.empty ()) + return true; + } + else + { + if (!skip_) + skip_ = (std::strcmp (a, "--") == 0); + + return true; + } + } + + return false; + } + + const char* argv_file_scanner:: + peek () + { + if (!more ()) + throw eos_reached (); + + return args_.empty () ? base::peek () : args_.front ().c_str (); + } + + const char* argv_file_scanner:: + next () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::next (); + else + { + hold_.swap (args_.front ()); + args_.pop_front (); + return hold_.c_str (); + } + } + + void argv_file_scanner:: + skip () + { + if (!more ()) + throw eos_reached (); + + if (args_.empty ()) + return base::skip (); + else + args_.pop_front (); + } + + void argv_file_scanner:: + load (const char* file) + { + using namespace std; + + ifstream is (file); + + if (!is.is_open ()) + throw file_io_failure (file); + + while (!is.eof ()) + { + string line; + getline (is, line); + + if (is.fail () && !is.eof ()) + throw file_io_failure (file); + + string::size_type n (line.size ()); + + // Trim the line from leading and trailing whitespaces. + // + if (n != 0) + { + const char* f (line.c_str ()); + const char* l (f + n); + + const char* of (f); + while (f < l && (*f == ' ' || *f == '\t' || *f == '\r')) + ++f; + + --l; + + const char* ol (l); + while (l > f && (*l == ' ' || *l == '\t' || *l == '\r')) + --l; + + if (f != of || l != ol) + line = f <= l ? string (f, l - f + 1) : string (); + } + + // Ignore empty lines, those that start with #. + // + if (line.empty () || line[0] == '#') + continue; + + string::size_type p (line.find (' ')); + + if (p == string::npos) + { + if (!skip_) + skip_ = (line == "--"); + + args_.push_back (line); + } + else + { + string s1 (line, 0, p); + + // Skip leading whitespaces in the argument. + // + n = line.size (); + for (++p; p < n; ++p) + { + char c (line[p]); + + if (c != ' ' && c != '\t' && c != '\r') + break; + } + + string s2 (line, p); + + if (!skip_ && s1 == option_) + load (s2.c_str ()); + else + { + args_.push_back (s1); + args_.push_back (s2); + } + } + } + } + template struct parser { @@ -337,7 +516,8 @@ options (int& argc, include_with_brackets_ (), include_prefix_ (), guard_prefix_ (), - reserved_name_ () + reserved_name_ (), + options_file_ () { ::cli::argv_scanner s (argc, argv, erase); _parse (s, opt, arg); @@ -378,7 +558,8 @@ options (int start, include_with_brackets_ (), include_prefix_ (), guard_prefix_ (), - reserved_name_ () + reserved_name_ (), + options_file_ () { ::cli::argv_scanner s (start, argc, argv, erase); _parse (s, opt, arg); @@ -419,7 +600,8 @@ options (int& argc, include_with_brackets_ (), include_prefix_ (), guard_prefix_ (), - reserved_name_ () + reserved_name_ (), + options_file_ () { ::cli::argv_scanner s (argc, argv, erase); _parse (s, opt, arg); @@ -462,7 +644,8 @@ options (int start, include_with_brackets_ (), include_prefix_ (), guard_prefix_ (), - reserved_name_ () + reserved_name_ (), + options_file_ () { ::cli::argv_scanner s (start, argc, argv, erase); _parse (s, opt, arg); @@ -501,7 +684,8 @@ options (::cli::scanner& s, include_with_brackets_ (), include_prefix_ (), guard_prefix_ (), - reserved_name_ () + reserved_name_ (), + options_file_ () { _parse (s, opt, arg); } @@ -587,6 +771,10 @@ print_usage (::std::ostream& os) os << "--reserved-name = Add with an optional replacement to" << ::std::endl << " the list of names that should not be used as" << ::std::endl << " identifiers." << ::std::endl; + + os << "--options-file Read additional options from with each" << ::std::endl + << " option appearing on a separate line optionally" << ::std::endl + << " followed by space and an option value." << ::std::endl; } typedef @@ -659,6 +847,8 @@ struct _cli_options_map_init &::cli::thunk< options, std::string, &options::guard_prefix_ >; _cli_options_map_["--reserved-name"] = &::cli::thunk< options, std::map, &options::reserved_name_ >; + _cli_options_map_["--options-file"] = + &::cli::thunk< options, std::string, &options::options_file_ >; } } _cli_options_map_init_; diff --git a/cli/options.hxx b/cli/options.hxx index 54143b9..1c7456c 100644 --- a/cli/options.hxx +++ b/cli/options.hxx @@ -5,6 +5,7 @@ #ifndef CLI_OPTIONS_HXX #define CLI_OPTIONS_HXX +#include #include #include #include @@ -144,6 +145,27 @@ namespace cli what () const throw (); }; + class file_io_failure: public exception + { + public: + virtual + ~file_io_failure () throw (); + + file_io_failure (const std::string& file); + + const std::string& + file () const; + + virtual void + print (std::ostream&) const; + + virtual const char* + what () const throw (); + + private: + std::string file_; + }; + class scanner { public: @@ -190,6 +212,44 @@ namespace cli char** argv_; bool erase_; }; + + class argv_file_scanner: public argv_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); + + virtual bool + more (); + + virtual const char* + peek (); + + virtual const char* + next (); + + virtual void + skip (); + + private: + void + load (const char* file); + + typedef argv_scanner base; + + const std::string option_; + std::string hold_; + std::deque args_; + bool skip_; + }; } #include @@ -326,6 +386,9 @@ class options const std::map& reserved_name () const; + const std::string& + options_file () const; + // Print usage information. // static void @@ -367,6 +430,7 @@ class options std::string include_prefix_; std::string guard_prefix_; std::map reserved_name_; + std::string options_file_; }; #include "options.ixx" diff --git a/cli/options.ixx b/cli/options.ixx index 74d7b15..cfaf2ac 100644 --- a/cli/options.ixx +++ b/cli/options.ixx @@ -84,6 +84,20 @@ namespace cli return value_; } + // file_io_failure + // + inline file_io_failure:: + file_io_failure (const std::string& file) + : file_ (file) + { + } + + inline const std::string& file_io_failure:: + file () const + { + return file_; + } + // argv_scanner // inline argv_scanner:: @@ -103,6 +117,31 @@ namespace cli { return i_; } + + // argv_file_scanner + // + inline argv_file_scanner:: + argv_file_scanner (int& argc, + char** argv, + const std::string& option, + bool erase) + : argv_scanner (argc, argv, erase), + option_ (option), + skip_ (false) + { + } + + inline argv_file_scanner:: + argv_file_scanner (int start, + int& argc, + char** argv, + const std::string& option, + bool erase) + : argv_scanner (start, argc, argv, erase), + option_ (option), + skip_ (false) + { + } } // options @@ -282,3 +321,9 @@ reserved_name () const return this->reserved_name_; } +inline const std::string& options:: +options_file () const +{ + return this->options_file_; +} + -- cgit v1.1