summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2009-12-10 10:50:23 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2009-12-10 10:50:23 +0200
commit74dfffa9df361e35a5910f1cf5b1734571bbef91 (patch)
tree72c4751da28efd20e2ddcf374842f359fad67faf
parent2dc2da5488ac32da8c6ff7cd0eeb5e1beb38c92f (diff)
Allows additional options to be provided in files (--options-file)
Implemented using the new argv_file_scanner scanner implementation.
-rw-r--r--NEWS3
-rw-r--r--cli/cli.cxx12
-rw-r--r--cli/makefile2
-rw-r--r--cli/options.cli16
-rw-r--r--cli/options.cxx200
-rw-r--r--cli/options.hxx64
-rw-r--r--cli/options.ixx45
-rw-r--r--doc/cli.19
-rw-r--r--doc/cli.xhtml10
9 files changed, 350 insertions, 11 deletions
diff --git a/NEWS b/NEWS
index 789c8bb..e491819 100644
--- a/NEWS
+++ b/NEWS
@@ -43,6 +43,9 @@ Version 1.1.0
3.1, "Option Class Definition" in the Getting Started Guide as well as
the 'file' example.
+ * New option, --options-file, allows additional CLI command line options
+ to be provided in files (implemented using argv_file_scanner).
+
Version 1.0.0
* First public release.
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
+ {
+ "<file>",
+ "Read additional options from <file> 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 <vector>
#include <ostream>
#include <sstream>
+#include <cstring>
+#include <fstream>
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 <typename X>
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 <name>=<rep> Add <name> with an optional <rep> replacement to" << ::std::endl
<< " the list of names that should not be used as" << ::std::endl
<< " identifiers." << ::std::endl;
+
+ os << "--options-file <file> Read additional options from <file> 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<std::string, std::string>, &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 <deque>
#include <iosfwd>
#include <string>
#include <exception>
@@ -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<std::string> args_;
+ bool skip_;
+ };
}
#include <map>
@@ -326,6 +386,9 @@ class options
const std::map<std::string, std::string>&
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<std::string, std::string> 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_;
+}
+
diff --git a/doc/cli.1 b/doc/cli.1
index 0a2021f..8daec4e 100644
--- a/doc/cli.1
+++ b/doc/cli.1
@@ -178,6 +178,15 @@ Add \fIname\fP with an optional \fIrep\fP replacement to the list of names
that should not be used as identifiers\. If provided, the replacement name
is used instead\. All C++ keywords are already in this list\.
+.IP "\fB--options-file\fP \fIfile\fP"
+Read additional options from \fIfile\fP with each option appearing on a
+separate line optionally followed by space and an option value\. Empty lines
+and lines starting with \fB#\fP 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 \fB--options-file\fP
+option is specified except that shell escaping and quoting is not required\.
+Repeat this option to specify more than one options files\.
+
.\"
.\" DIAGNOSTICS
.\"
diff --git a/doc/cli.xhtml b/doc/cli.xhtml
index 3389c77..3a09c96 100644
--- a/doc/cli.xhtml
+++ b/doc/cli.xhtml
@@ -204,6 +204,16 @@
that should not be used as identifiers. If provided, the replacement name is
used instead. All C++ keywords are already in this list.</dd>
+ <dt><code><b>--options-file</b></code> <i>file</i></dt>
+ <dd>Read additional options from <i>file</i> with each option appearing on a
+ separate line optionally followed by space and an option value. Empty lines
+ and lines starting with <code><b>#</b></code> 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
+ <code><b>--options-file</b></code> option is specified except that shell
+ escaping and quoting is not required. Repeat this option to specify more
+ than one options files.</dd>
+
</dl>
<h1>DIAGNOSTICS</h1>