summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2019-07-27 11:38:53 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2019-07-27 16:34:32 +0200
commitdca38b27afc25d329fd7a7241095b40e2a1ecae2 (patch)
treec656421398fd818d68f9e07130f96d7eeeaa6490
parentc5979a2814c9211e0e3c8ae7232ea66f171d54d0 (diff)
Add support for option merging (--generate-merge)
-rw-r--r--NEWS22
-rw-r--r--cli/context.cxx21
-rw-r--r--cli/context.hxx8
-rw-r--r--cli/header.cxx34
-rw-r--r--cli/inline.cxx6
-rw-r--r--cli/name-processor.cxx4
-rw-r--r--cli/options.cli23
-rw-r--r--cli/options.cxx10
-rw-r--r--cli/options.hxx10
-rw-r--r--cli/options.ixx18
-rw-r--r--cli/runtime-source.cxx263
-rw-r--r--cli/source.cxx102
-rw-r--r--doc/cli.1109
-rw-r--r--doc/cli.xhtml103
-rw-r--r--tests/merge/buildfile10
-rw-r--r--tests/merge/driver.cxx56
-rw-r--r--tests/merge/test.cli19
17 files changed, 610 insertions, 208 deletions
diff --git a/NEWS b/NEWS
index e353f36..a1fe25e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,9 @@
Version 1.2.0
+ * New option, --generate-merge, triggers the generation of the merge()
+ function which can be used to merge several already parsed options class
+ instances, for example, to implement option appending/overriding.
+
* New option, --generate-specifier, triggers the generation of functions
for determining whether the option was specified on the command line.
@@ -18,11 +22,11 @@ Version 1.2.0
Version 1.1.0
- * Support for option documentation. Option documentation is used to print
+ * 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 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
@@ -36,7 +40,7 @@ Version 1.1.0
--html-epilogue
--man-suffix
--html-suffix
- --class
+ --class
--stdout
The CLI compiler usage, HTML documentation, and man page are auto-generated
@@ -49,15 +53,15 @@ Version 1.1.0
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
+ * 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
+ 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
+ * 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
+ 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.
diff --git a/cli/context.cxx b/cli/context.cxx
index 3715efa..061129e 100644
--- a/cli/context.cxx
+++ b/cli/context.cxx
@@ -106,8 +106,11 @@ context (ostream& os_,
unit (unit_),
options (ops),
ot (ot_),
- modifier (options.generate_modifier ()),
- specifier (options.generate_specifier ()),
+ gen_modifier (options.generate_modifier ()),
+ gen_specifier (options.generate_specifier () ||
+ options.generate_merge ()),
+ gen_parse (options.generate_parse ()),
+ gen_merge (options.generate_merge ()),
inl (data_->inl_),
opt_prefix (options.option_prefix ()),
opt_sep (options.option_separator ()),
@@ -122,13 +125,13 @@ context (ostream& os_,
tocs (data_->tocs_)
{
if (options.suppress_usage ())
- usage = ut_none;
+ gen_usage = ut_none;
else
{
if (options.long_usage ())
- usage = options.short_usage () ? ut_both : ut_long;
+ gen_usage = options.short_usage () ? ut_both : ut_long;
else
- usage = ut_short;
+ gen_usage = ut_short;
}
if (!options.suppress_inline ())
@@ -168,9 +171,11 @@ context (context& c)
unit (c.unit),
options (c.options),
ot (c.ot),
- modifier (c.modifier),
- specifier (c.specifier),
- usage (c.usage),
+ gen_modifier (c.gen_modifier),
+ gen_specifier (c.gen_specifier),
+ gen_parse (c.gen_parse),
+ gen_merge (c.gen_merge),
+ gen_usage (c.gen_usage),
inl (c.inl),
opt_prefix (c.opt_prefix),
opt_sep (c.opt_sep),
diff --git a/cli/context.hxx b/cli/context.hxx
index a076c17..14cdb7c 100644
--- a/cli/context.hxx
+++ b/cli/context.hxx
@@ -83,9 +83,11 @@ public:
output_type ot;
- bool modifier;
- bool specifier;
- usage_type usage;
+ bool gen_modifier;
+ bool gen_specifier;
+ bool gen_parse;
+ bool gen_merge;
+ usage_type gen_usage;
string const& inl;
string const& opt_prefix;
diff --git a/cli/header.cxx b/cli/header.cxx
index dc0ea9e..a1ab963 100644
--- a/cli/header.cxx
+++ b/cli/header.cxx
@@ -25,7 +25,7 @@ namespace
<< name << " () const;"
<< endl;
- if (modifier)
+ if (gen_modifier)
{
os << type << "&" << endl
<< name << " ();"
@@ -36,7 +36,7 @@ namespace
<< endl;
}
- if (specifier && type != "bool")
+ if (gen_specifier && type != "bool")
{
string spec (especifier (o));
@@ -44,7 +44,7 @@ namespace
<< spec << " () const;"
<< endl;
- if (modifier)
+ if (gen_modifier)
os << "void" << endl
<< spec << " (bool);"
<< endl;
@@ -66,7 +66,7 @@ namespace
os << type << " " << member << ";";
- if (specifier && type != "bool")
+ if (gen_specifier && type != "bool")
os << "bool " << especifier_member (o) << ";";
}
};
@@ -137,7 +137,7 @@ namespace
// Are we generating parsing constructors or parse() functions?
//
string n;
- if (options.generate_parse ())
+ if (gen_parse)
{
os << "// Return true if anything has been parsed." << endl
<< "//" << endl;
@@ -185,16 +185,30 @@ namespace
<< endl;
}
+
+ // Note that we are generating public merge() function even for abstract
+ // classes; theoretically, one may want to merge options only starting
+ // form a specific point in the inheritance hierarchy (e.g., only common
+ // options or some such).
+ //
+ if (gen_merge)
+ os << "// Merge options from the specified instance appending/overriding" << endl
+ << "// them as if they appeared after options in this instance." << endl
+ << "//" << endl
+ << "void" << endl
+ << "merge (const " << name << "&);"
+ << endl;
+
//
//
- os << "// Option accessors" << (modifier ? " and modifiers." : ".") << endl
+ os << "// Option accessors" << (gen_modifier ? " and modifiers." : ".") << endl
<< "//" << endl;
names (c, names_option_);
// Usage.
//
- if (usage != ut_none)
+ if (gen_usage != ut_none)
{
string up (cli + "::usage_para");
string const& ost (options.ostream_type ());
@@ -207,7 +221,7 @@ namespace
<< up << " = " << up << "::none);"
<< endl;
- if (usage == ut_both)
+ if (gen_usage == ut_both)
os << "static " << up << endl
<< "print_long_usage (" << ost << "&," << endl
<< up << " = " << up << "::none);"
@@ -343,7 +357,7 @@ generate_header (context& ctx)
// Entire page usage.
//
- if (ctx.usage != ut_none && ctx.options.page_usage_specified ())
+ if (ctx.gen_usage != ut_none && ctx.options.page_usage_specified ())
{
os << "// Print page usage information." << endl
<< "//" << endl;
@@ -359,7 +373,7 @@ generate_header (context& ctx)
<< up << " = " << up << "::none);"
<< endl;
- if (ctx.usage == ut_both)
+ if (ctx.gen_usage == ut_both)
os << up << endl
<< n << "long_usage (" << ost << "&," << endl
<< up << " = " << up << "::none);"
diff --git a/cli/inline.cxx b/cli/inline.cxx
index 9dc5440..4bc0182 100644
--- a/cli/inline.cxx
+++ b/cli/inline.cxx
@@ -26,7 +26,7 @@ namespace
<< "return this->" << emember (o) << ";"
<< "}";
- if (modifier)
+ if (gen_modifier)
{
os << inl << type << "& " << scope << "::" << endl
<< name << " ()"
@@ -41,7 +41,7 @@ namespace
<< "}";
}
- if (specifier && type != "bool")
+ if (gen_specifier && type != "bool")
{
string spec (especifier (o));
@@ -51,7 +51,7 @@ namespace
<< "return this->" << especifier_member (o) << ";"
<< "}";
- if (modifier)
+ if (gen_modifier)
os << inl << "void " << scope << "::" << endl
<< spec << "(bool x)"
<< "{"
diff --git a/cli/name-processor.cxx b/cli/name-processor.cxx
index c0f7179..d533a5b 100644
--- a/cli/name-processor.cxx
+++ b/cli/name-processor.cxx
@@ -85,7 +85,7 @@ namespace
virtual void
traverse (type& o)
{
- if (specifier && o.type ().name () != "bool")
+ if (gen_specifier && o.type ().name () != "bool")
{
semantics::context& oc (o.context ());
string const& base (oc.get<string> ("name"));
@@ -111,7 +111,7 @@ namespace
string const& base (oc.get<string> ("name"));
oc.set ("member", find_name (base + "_", set_));
- if (specifier && o.type ().name () != "bool")
+ if (gen_specifier && o.type ().name () != "bool")
{
string const& base (oc.get<string> ("specifier"));
oc.set ("specifier-member", find_name (base + "_", set_));
diff --git a/cli/options.cli b/cli/options.cli
index 631f52f..da9a52a 100644
--- a/cli/options.cli
+++ b/cli/options.cli
@@ -51,9 +51,18 @@ class options
bool --generate-parse
{
- "Generate \cb{parse()} functions instead of parsing constructors. This
- is primarily useful for being able to parse into an already initialized
- options class instance, for example, to implement merging/overriding."
+ "Generate \cb{parse()} functions instead of parsing constructors. This is
+ primarily useful for being able to parse into an already initialized
+ options class instance, for example, to implement option
+ appending/overriding."
+ };
+
+ bool --generate-merge
+ {
+ "Generate \cb{merge()} functions. This is primarily useful for being able
+ to merge several already parsed options class instances, for example, to
+ implement option appending/overriding. Note that this option forces
+ \cb{--generate-specifier}."
};
bool --generate-description
@@ -63,20 +72,20 @@ class options
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
+ "Generate the \cb{argv_file_scanner} implementation. This scanner is
+ capable of reading command line arguments from the \cb{argv} array as
well as files specified with command line options."
};
bool --generate-vector-scanner
{
- "Generate the \c{vector_scanner} implementation. This scanner is capable
+ "Generate the \cb{vector_scanner} implementation. This scanner is capable
of reading command line arguments from \cb{vector<string>}."
};
bool --generate-group-scanner
{
- "Generate the \c{group_scanner} implementation. This scanner supports
+ "Generate the \cb{group_scanner} implementation. This scanner supports
grouping of arguments (usually options) to apply only to a certain
argument.
diff --git a/cli/options.cxx b/cli/options.cxx
index bb102e0..2e69cc9 100644
--- a/cli/options.cxx
+++ b/cli/options.cxx
@@ -612,6 +612,7 @@ options ()
generate_modifier_ (),
generate_specifier_ (),
generate_parse_ (),
+ generate_merge_ (),
generate_description_ (),
generate_file_scanner_ (),
generate_vector_scanner_ (),
@@ -750,6 +751,7 @@ options (int& argc,
generate_modifier_ (),
generate_specifier_ (),
generate_parse_ (),
+ generate_merge_ (),
generate_description_ (),
generate_file_scanner_ (),
generate_vector_scanner_ (),
@@ -891,6 +893,7 @@ options (int start,
generate_modifier_ (),
generate_specifier_ (),
generate_parse_ (),
+ generate_merge_ (),
generate_description_ (),
generate_file_scanner_ (),
generate_vector_scanner_ (),
@@ -1032,6 +1035,7 @@ options (int& argc,
generate_modifier_ (),
generate_specifier_ (),
generate_parse_ (),
+ generate_merge_ (),
generate_description_ (),
generate_file_scanner_ (),
generate_vector_scanner_ (),
@@ -1175,6 +1179,7 @@ options (int start,
generate_modifier_ (),
generate_specifier_ (),
generate_parse_ (),
+ generate_merge_ (),
generate_description_ (),
generate_file_scanner_ (),
generate_vector_scanner_ (),
@@ -1314,6 +1319,7 @@ options (::cli::scanner& s,
generate_modifier_ (),
generate_specifier_ (),
generate_parse_ (),
+ generate_merge_ (),
generate_description_ (),
generate_file_scanner_ (),
generate_vector_scanner_ (),
@@ -1466,6 +1472,8 @@ print_usage (::std::ostream& os, ::cli::usage_para p)
os << "--generate-parse Generate parse() functions instead of parsing" << ::std::endl
<< " constructors." << ::std::endl;
+ os << "--generate-merge Generate merge() functions." << ::std::endl;
+
os << "--generate-description Generate the option description list that can be" << ::std::endl
<< " examined at runtime." << ::std::endl;
@@ -1715,6 +1723,8 @@ struct _cli_options_map_init
&::cli::thunk< options, bool, &options::generate_specifier_ >;
_cli_options_map_["--generate-parse"] =
&::cli::thunk< options, bool, &options::generate_parse_ >;
+ _cli_options_map_["--generate-merge"] =
+ &::cli::thunk< options, bool, &options::generate_merge_ >;
_cli_options_map_["--generate-description"] =
&::cli::thunk< options, bool, &options::generate_description_ >;
_cli_options_map_["--generate-file-scanner"] =
diff --git a/cli/options.hxx b/cli/options.hxx
index dce59bd..9f5124f 100644
--- a/cli/options.hxx
+++ b/cli/options.hxx
@@ -500,6 +500,15 @@ class options
generate_parse (const bool&);
const bool&
+ generate_merge () const;
+
+ bool&
+ generate_merge ();
+
+ void
+ generate_merge (const bool&);
+
+ const bool&
generate_description () const;
bool&
@@ -1450,6 +1459,7 @@ class options
bool generate_modifier_;
bool generate_specifier_;
bool generate_parse_;
+ bool generate_merge_;
bool generate_description_;
bool generate_file_scanner_;
bool generate_vector_scanner_;
diff --git a/cli/options.ixx b/cli/options.ixx
index 4c5cfb7..ba5fff2 100644
--- a/cli/options.ixx
+++ b/cli/options.ixx
@@ -407,6 +407,24 @@ generate_parse(const bool& x)
}
inline const bool& options::
+generate_merge () const
+{
+ return this->generate_merge_;
+}
+
+inline bool& options::
+generate_merge ()
+{
+ return this->generate_merge_;
+}
+
+inline void options::
+generate_merge(const bool& x)
+{
+ this->generate_merge_ = x;
+}
+
+inline const bool& options::
generate_description () const
{
return this->generate_description_;
diff --git a/cli/runtime-source.cxx b/cli/runtime-source.cxx
index 778bed4..54a085f 100644
--- a/cli/runtime-source.cxx
+++ b/cli/runtime-source.cxx
@@ -802,166 +802,203 @@ generate_runtime_source (context& ctx, bool complete)
// generated header file, we always generate the following templates
// in the source file.
//
- bool sp (ctx.specifier);
+ bool sp (ctx.gen_specifier);
+ bool gen_merge (ctx.gen_merge);
// parser class template & its specializations
//
os << "template <typename X>" << endl
<< "struct parser"
- << "{"
- << "static void" << endl
- << "parse (X& x, " << (sp ? "bool& xs, " : "") << "scanner& s)"
- << "{"
- << "using namespace std;"
- << endl
- << "const char* o (s.next ());"
- << endl
- << "if (s.more ())"
- << "{"
- << "string v (s.next ());"
- << "istringstream is (v);"
- << "if (!(is >> x && " <<
- "is.peek () == istringstream::traits_type::eof ()))" << endl
- << "throw invalid_value (o, v);"
- << "}"
- << "else" << endl
- << "throw missing_value (o);";
-
+ << "{";
+
+ os << "static void" << endl
+ << "parse (X& x, " << (sp ? "bool& xs, " : "") << "scanner& s)"
+ << "{"
+ << "using namespace std;"
+ << endl
+ << "const char* o (s.next ());"
+ << "if (s.more ())"
+ << "{"
+ << "string v (s.next ());"
+ << "istringstream is (v);"
+ << "if (!(is >> x && is.peek () == istringstream::traits_type::eof ()))" << endl
+ << "throw invalid_value (o, v);"
+ << "}"
+ << "else" << endl
+ << "throw missing_value (o);";
if (sp)
os << endl
- << "xs = true;";
+ << "xs = true;";
+ os << "}";
- os << "}"
- << "};";
+ if (gen_merge)
+ os << "static void" << endl
+ << "merge (X& b, const X& a)"
+ << "{"
+ << "b = a;"
+ << "}";
+
+ os << "};";
// parser<bool>
//
os << "template <>" << endl
<< "struct parser<bool>"
- << "{"
- << "static void" << endl
- << "parse (bool& x, scanner& s)"
- << "{"
- << "s.next ();"
- << "x = true;"
- << "}"
- << "};";
+ << "{";
+
+ os << "static void" << endl
+ << "parse (bool& x, scanner& s)"
+ << "{"
+ << "s.next ();"
+ << "x = true;"
+ << "}";
+
+ if (gen_merge)
+ os << "static void" << endl
+ << "merge (bool& b, const bool&)"
+ << "{"
+ << "b = true;" // We wouldn't be here if a is false.
+ << "}";
+
+ os << "};";
// parser<string>
//
os << "template <>" << endl
<< "struct parser<std::string>"
- << "{"
- << "static void" << endl
- << "parse (std::string& x, " << (sp ? "bool& xs, " : "") << "scanner& s)"
- << "{"
- << "const char* o (s.next ());"
- << endl
- << "if (s.more ())" << endl
- << "x = s.next ();"
- << "else" << endl
- << "throw missing_value (o);";
-
+ << "{";
+
+ os << "static void" << endl
+ << "parse (std::string& x, " << (sp ? "bool& xs, " : "") << "scanner& s)"
+ << "{"
+ << "const char* o (s.next ());"
+ << endl
+ << "if (s.more ())" << endl
+ << "x = s.next ();"
+ << "else" << endl
+ << "throw missing_value (o);";
if (sp)
- os << endl
+ os << endl
<< "xs = true;";
+ os << "}";
- os << "}"
- << "};";
+ if (gen_merge)
+ os << "static void" << endl
+ << "merge (std::string& b, const std::string& a)"
+ << "{"
+ << "b = a;"
+ << "}";
+
+ os << "};";
// parser<std::vector<X>>
//
os << "template <typename X>" << endl
<< "struct parser<std::vector<X> >"
- << "{"
- << "static void" << endl
- << "parse (std::vector<X>& c, " << (sp ? "bool& xs, " : "") <<
- "scanner& s)"
- << "{"
- << "X x;";
+ << "{";
+ os << "static void" << endl
+ << "parse (std::vector<X>& c, " << (sp ? "bool& xs, " : "") << "scanner& s)"
+ << "{"
+ << "X x;";
if (sp)
- os << "bool dummy;";
-
- os << "parser<X>::parse (x, " << (sp ? "dummy, " : "") << "s);"
- << "c.push_back (x);";
-
+ os << "bool dummy;";
+ os << "parser<X>::parse (x, " << (sp ? "dummy, " : "") << "s);"
+ << "c.push_back (x);";
if (sp)
- os << "xs = true;";
+ os << "xs = true;";
+ os << "}";
+
+ if (gen_merge)
+ os << "static void" << endl
+ << "merge (std::vector<X>& b, const std::vector<X>& a)"
+ << "{"
+ << "b.insert (b.end (), a.begin (), a.end ());"
+ << "}";
- os << "}"
- << "};";
+ os << "};";
// parser<std::set<X>>
//
os << "template <typename X>" << endl
<< "struct parser<std::set<X> >"
- << "{"
- << "static void" << endl
- << "parse (std::set<X>& c, " << (sp ? "bool& xs, " : "") << "scanner& s)"
- << "{"
- << "X x;";
+ << "{";
+ os << "static void" << endl
+ << "parse (std::set<X>& c, " << (sp ? "bool& xs, " : "") << "scanner& s)"
+ << "{"
+ << "X x;";
if (sp)
- os << "bool dummy;";
-
- os << "parser<X>::parse (x, " << (sp ? "dummy, " : "") << "s);"
- << "c.insert (x);";
-
+ os << "bool dummy;";
+ os << "parser<X>::parse (x, " << (sp ? "dummy, " : "") << "s);"
+ << "c.insert (x);";
if (sp)
- os << "xs = true;";
+ os << "xs = true;";
+ os << "}";
+
+ if (gen_merge)
+ os << "static void" << endl
+ << "merge (std::set<X>& b, const std::set<X>& a)"
+ << "{"
+ << "b.insert (a.begin (), a.end ());"
+ << "}";
- os << "}"
- << "};";
+ os << "};";
// parser<std::map<K,V>>
//
os << "template <typename K, typename V>" << endl
<< "struct parser<std::map<K, V> >"
- << "{"
- << "static void" << endl
- << "parse (std::map<K, V>& m, " << (sp ? "bool& xs, " : "") <<
- "scanner& s)"
- << "{"
- << "const char* o (s.next ());"
- << endl
- << "if (s.more ())"
- << "{"
- << "std::string ov (s.next ());"
- << "std::string::size_type p = ov.find ('=');"
- << endl
- << "K k = K ();"
- << "V v = V ();"
- << "std::string kstr (ov, 0, p);"
- << "std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ()));"
- << endl
- << "int ac (2);"
- << "char* av[] = {const_cast<char*> (o), 0};";
+ << "{";
+
+ os << "static void" << endl
+ << "parse (std::map<K, V>& m, " << (sp ? "bool& xs, " : "") << "scanner& s)"
+ << "{"
+ << "const char* o (s.next ());"
+ << endl
+ << "if (s.more ())"
+ << "{"
+ << "std::string ov (s.next ());"
+ << "std::string::size_type p = ov.find ('=');"
+ << endl
+ << "K k = K ();"
+ << "V v = V ();"
+ << "std::string kstr (ov, 0, p);"
+ << "std::string vstr (ov, (p != std::string::npos ? p + 1 : ov.size ()));"
+ << endl
+ << "int ac (2);"
+ << "char* av[] = {const_cast<char*> (o), 0};";
if (sp)
- os << "bool dummy;";
- os << "if (!kstr.empty ())"
- << "{"
- << "av[1] = const_cast<char*> (kstr.c_str ());"
- << "argv_scanner s (0, ac, av);"
- << "parser<K>::parse (k, " << (sp ? "dummy, " : "") << "s);"
- << "}"
- << "if (!vstr.empty ())"
- << "{"
- << "av[1] = const_cast<char*> (vstr.c_str ());"
- << "argv_scanner s (0, ac, av);"
- << "parser<V>::parse (v, " << (sp ? "dummy, " : "") << "s);"
- << "}"
- << "m[k] = v;"
- << "}"
- << "else" << endl
- << "throw missing_value (o);";
-
+ os << "bool dummy;";
+ os << "if (!kstr.empty ())"
+ << "{"
+ << "av[1] = const_cast<char*> (kstr.c_str ());"
+ << "argv_scanner s (0, ac, av);"
+ << "parser<K>::parse (k, " << (sp ? "dummy, " : "") << "s);"
+ << "}"
+ << "if (!vstr.empty ())"
+ << "{"
+ << "av[1] = const_cast<char*> (vstr.c_str ());"
+ << "argv_scanner s (0, ac, av);"
+ << "parser<V>::parse (v, " << (sp ? "dummy, " : "") << "s);"
+ << "}"
+ << "m[k] = v;"
+ << "}"
+ << "else" << endl
+ << "throw missing_value (o);";
if (sp)
os << endl
- << "xs = true;";
+ << "xs = true;";
+ os << "}";
+
+ if (gen_merge)
+ os << "static void" << endl
+ << "merge (std::map<K, V>& b, const std::map<K, V>& a)"
+ << "{"
+ << "b.insert (a.begin (), a.end ());"
+ << "}";
- os << "}"
- << "};";
+ os << "};";
// Parser thunk.
//
@@ -972,7 +1009,7 @@ generate_runtime_source (context& ctx, bool complete)
<< "parser<T>::parse (x.*M, s);"
<< "}";
- if (ctx.specifier)
+ if (ctx.gen_specifier)
os << "template <typename X, typename T, T X::*M, bool X::*S>" << endl
<< "void" << endl
<< "thunk (X& x, scanner& s)"
diff --git a/cli/source.cxx b/cli/source.cxx
index 3d0b7cc..2333faf 100644
--- a/cli/source.cxx
+++ b/cli/source.cxx
@@ -58,7 +58,7 @@ namespace
else
os << " ()";
- if (specifier && o.type ().name () != "bool")
+ if (gen_specifier && o.type ().name () != "bool")
os << "," << endl
<< " " << especifier_member (o) << " (false)";
}
@@ -67,6 +67,29 @@ namespace
bool comma_;
};
+ struct option_merge: traversal::option, context
+ {
+ option_merge (context& c) : context (c) {}
+
+ virtual void
+ traverse (type& o)
+ {
+ string type (o.type ().name ());
+ bool b (type == "bool");
+
+ string member (emember (o));
+ string spec_member (b ? member : especifier_member (o));
+
+ os << "if (a." << spec_member << ")"
+ << "{"
+ << cli << "::parser< " << type << ">::merge (" << endl
+ << "this->" << member << ", a." << member << ");";
+ if (!b)
+ os << "this->" << spec_member << " = true;";
+ os << "}";
+ }
+ };
+
//
//
struct option_map: traversal::option, context
@@ -91,7 +114,7 @@ namespace
<< "&" << cli << "::thunk< " << scope << ", " << type << ", " <<
"&" << scope << "::" << member;
- if (specifier && type != "bool")
+ if (gen_specifier && type != "bool")
os << "," << endl
<< " &" << scope << "::" << especifier_member (o);
@@ -243,7 +266,7 @@ namespace
size_t n (ds.size ());
string d;
- if (usage == ut_both && usage_ == ut_long)
+ if (gen_usage == ut_both && usage_ == ut_long)
{
d = n > 2 // Have both short and long?
? ds[2] // Then use long.
@@ -436,7 +459,7 @@ namespace
if (doc.size () > i) // Have at least one.
{
- if (usage == ut_both && usage_ == ut_long)
+ if (gen_usage == ut_both && usage_ == ut_long)
{
d = doc.size () > i + 1 // Have both short and long?
? doc[i + 1] // Then use long.
@@ -444,10 +467,10 @@ namespace
}
else // Short or long.
{
- d = doc.size () > i + 1 // Have both short and long?
- ? doc[i] // Then use short,
- : (usage == ut_long // Otherwise, if asked for long,
- ? doc[i] // Then use long,
+ d = doc.size () > i + 1 // Have both short and long?
+ ? doc[i] // Then use short,
+ : (gen_usage == ut_long // Otherwise, if asked for long,
+ ? doc[i] // Then use long,
: first_sentence (doc[i])); // Else first sentence of long.
}
}
@@ -496,6 +519,22 @@ namespace
//
//
+ struct base_merge: traversal::class_, context
+ {
+ base_merge (context& c): context (c) {}
+
+ virtual void
+ traverse (type& c)
+ {
+ os << "// " << escape (c.name ()) << " base" << endl
+ << "//" << endl
+ << fq_name (c) << "::merge (a);"
+ << endl;
+ }
+ };
+
+ //
+ //
struct base_desc: traversal::class_, context
{
base_desc (context& c): context (c) {}
@@ -524,7 +563,7 @@ namespace
const char* t (
(cd == cd_default
- ? usage != ut_both || usage_ == ut_short
+ ? gen_usage != ut_both || usage_ == ut_short
: cd == cd_short) ? "" : "long_");
os << "// " << escape (c.name ()) << " base" << endl
@@ -544,12 +583,16 @@ namespace
class_ (context& c)
: context (c),
base_parse_ (c),
+ base_merge_ (c),
base_desc_ (c),
+ option_merge_ (c),
option_map_ (c),
option_desc_ (c)
{
inherits_base_parse_ >> base_parse_;
+ inherits_base_merge_ >> base_merge_;
inherits_base_desc_ >> base_desc_;
+ names_option_merge_ >> option_merge_;
names_option_map_ >> option_map_;
names_option_desc_ >> option_desc_;
}
@@ -583,7 +626,7 @@ namespace
if (!abst)
{
- bool p (options.generate_parse ());
+ bool p (gen_parse);
string n, res, ret;
if (p)
@@ -684,9 +727,30 @@ namespace
<< "}";
}
+ // merge()
+ //
+ if (gen_merge)
+ {
+ os << "void " << name << "::" << endl
+ << "merge (const " << name << "& a)"
+ << "{"
+ << "CLI_POTENTIALLY_UNUSED (a);"
+ << endl;
+
+ // First merge all our bases.
+ //
+ inherits (c, inherits_base_merge_);
+
+ // Then our options.
+ //
+ names (c, names_option_merge_);
+
+ os << "}";
+ }
+
// Usage.
//
- if (usage != ut_none)
+ if (gen_usage != ut_none)
{
bool b (hb && !options.exclude_base ());
@@ -775,7 +839,7 @@ namespace
<< "CLI_POTENTIALLY_UNUSED (os);"
<< endl;
{
- usage_type u (usage == ut_both ? ut_short : usage);
+ usage_type u (gen_usage == ut_both ? ut_short : gen_usage);
base_usage bu (*this, u);
traversal::inherits i (bu);
@@ -808,7 +872,7 @@ namespace
// Long version.
//
- if (usage == ut_both)
+ if (gen_usage == ut_both)
{
os << up << " " << name << "::" << endl
<< "print_long_usage (" << ost << "& os, " << up << " p)"
@@ -1136,9 +1200,15 @@ namespace
base_parse base_parse_;
traversal::inherits inherits_base_parse_;
+ base_merge base_merge_;
+ traversal::inherits inherits_base_merge_;
+
base_desc base_desc_;
traversal::inherits inherits_base_desc_;
+ option_merge option_merge_;
+ traversal::names names_option_merge_;
+
option_map option_map_;
traversal::names names_option_map_;
@@ -1163,7 +1233,7 @@ namespace
const char* t (
(cd == cd_default || cd == cd_exclude_base
- ? usage != ut_both || usage_ == ut_short
+ ? gen_usage != ut_both || usage_ == ut_short
: cd == cd_short) ? "" : "long_");
string p (
@@ -1210,12 +1280,12 @@ generate_source (context& ctx)
// Entire page usage.
//
- if (ctx.usage != ut_none && ctx.options.page_usage_specified ())
+ if (ctx.gen_usage != ut_none && ctx.options.page_usage_specified ())
{
const string& qn (ctx.options.page_usage ());
string n (ctx.escape (ctx.substitute (ctx.ns_open (qn, false))));
- usage u (ctx.usage);
+ usage u (ctx.gen_usage);
string up (ctx.cli + "::usage_para");
string const& ost (ctx.options.ostream_type ());
diff --git a/doc/cli.1 b/doc/cli.1
index 94456d7..ab46e49 100644
--- a/doc/cli.1
+++ b/doc/cli.1
@@ -76,16 +76,68 @@ command line\.
.IP "\fB--generate-parse\fR"
Generate \fBparse()\fR functions instead of parsing constructors\. This is
primarily useful for being able to parse into an already initialized options
-class instance, for example, to implement merging/overriding\.
+class instance, for example, to implement option appending/overriding\.
+.IP "\fB--generate-merge\fR"
+Generate \fBmerge()\fR functions\. This is primarily useful for being able to
+merge several already parsed options class instances, for example, to
+implement option appending/overriding\. Note that this option forces
+\fB--generate-specifier\fR\.
.IP "\fB--generate-description\fR"
Generate the option description list that can be examined at runtime\.
.IP "\fB--generate-file-scanner\fR"
-Generate the argv_file_scanner\fR implementation\. This scanner is capable of
-reading command line arguments from the argv\fR array as well as files
+Generate the \fBargv_file_scanner\fR implementation\. This scanner is capable
+of reading command line arguments from the \fBargv\fR array as well as files
specified with command line options\.
.IP "\fB--generate-vector-scanner\fR"
-Generate the vector_scanner\fR implementation\. This scanner is capable of
+Generate the \fBvector_scanner\fR implementation\. This scanner is capable of
reading command line arguments from \fBvector<string>\fR\.
+.IP "\fB--generate-group-scanner\fR"
+Generate the \fBgroup_scanner\fR implementation\. This scanner supports
+grouping of arguments (usually options) to apply only to a certain argument\.
+
+Groups can be specified before (leading) and/or after (trailing) the argument
+they apply to\. A leading group starts with '\fB{\fR' and ends with '\fB}+\fR'
+while a trailing group starts with '\fB+{\fR' and ends with '\fB}\fR'\. For
+example:
+
+.nf
+{ --foo --bar }+ arg # 'arg' with '--foo' '--bar'
+arg +{ fox=1 baz=2 } # 'arg' with 'fox=1' 'baz=2'
+.fi
+
+Multiple leading and/or trailing groups can be specified for the same
+argument\. For example:
+
+.nf
+{ -f }+ { -b }+ arg +{ f=1 } +{ b=2 } # 'arg' with '-f' 'b' 'f=1' 'b=2'
+.fi
+
+Note that the group applies to a single argument only\. For example:
+
+.nf
+{ --foo }+ arg1 arg2 +{ --bar } # 'arg1' with '--foo' and
+ # 'arg2' with '--bar'
+.fi
+
+The group separators ('\fB{\fR', '\fB}+'\fR, etc) must be separate command
+line arguments\. In particular, they must not be adjacent either to the
+arguments inside the group nor to the argument they apply to\. All such cases
+will be treated as ordinary arguments\. For example:
+
+.nf
+{--foo}+ arg # '{--foo}+' \.\.\.
+arg+{ --foo } # 'arg+{' \.\.\.
+.fi
+
+If one of the group separators needs to be specified as an argument verbatim,
+then it must be escaped with '\fB\e\fR'\. For example:
+
+.nf
+} # error: unexpected group separator
+}x # '}x'
+\\} # '}'
+{ \\}+ }+ arg # 'arg' with '}+'
+.fi
.IP "\fB--suppress-inline\fR"
Generate all functions non-inline\. By default simple functions are made
inline\. This option suppresses creation of the inline file\.
@@ -165,12 +217,13 @@ default, base classes are included first\.
Specify the documentation \fIkind\fR that should be used for the options class
\fIname\fR\. The \fIname\fR value should be a fully-qualified class name, for
example, \fBapp::options\fR\. The \fIkind\fR value can be \fBshort\fR,
-\fBlong\fR, or \fBexclude\fR\. If the value is \fBexclude\fR, then the class
-documentation is excluded from usage and man/HTML/text output\. For usage, the
-\fBshort\fR and \fBlong\fR values determine which usage function will be
-called when the class is used as base or as part of the page usage (see
-\fB--page-usage\fR)\. For man/HTML/text, these values determine which
-documentation strings are used in the output\.
+\fBlong\fR, \fBexclude\fR, or \fBexclude-base\fR\. If the value is
+\fBexclude\fR, then the class documentation is excluded from usage and
+man/HTML/text output\. If it is \fBexclude-base\fR, then it is only excluded
+when used as a base\. For usage, the \fBshort\fR and \fBlong\fR values
+determine which usage function will be called when the class is used as base
+or as part of the page usage (see \fB--page-usage\fR)\. For man/HTML/text,
+these values determine which documentation strings are used in the output\.
.IP "\fB--class\fR \fIname\fR"
Generate the man page, HTML, or text documentation only for the options class
\fIname\fR\. The \fIname\fR value should be a fully-qualified options class
@@ -185,7 +238,7 @@ variables can be substituted in prologues and epilogues (see
\fB--*-prologue*\fR and \fB--*-epilogue*\fR options) using the
\fB$\fR\fIname\fR\fB$\fR expansion syntax (use \fB$$\fR to escape expansion)\.
They can also be defined in \fB\.cli\fR files using the
-"\e\fIname\fR=\fIval\fR"\fR syntax\.
+\&"\e\fIname\fR=\fIval\fR"\fR syntax\.
.IP "\fB--link-regex\fR \fIregex\fR"
Add \fIregex\fR to the list of regular expressions used to transform link
targets in the generated documentation\. The argument to this option is a
@@ -203,8 +256,8 @@ Trace the process of applying regular expressions specified with the
expressions don't do what you expected them to do\.
.IP "\fB--html-heading-map\fR \fIc\fR=\fIh\fR"
Map CLI heading \fIc\fR (valid values: '\fBH\fR', '\fB0\fR', '\fB1\fR',
-'\fBh\fR', and '\fB2\fR') to HTML heading \fIh\fR (for example, '\fBh1\fR',
-'\fBh2\fR', etc)\.
+\&'\fBh\fR', and '\fB2\fR') to HTML heading \fIh\fR (for example, '\fBh1\fR',
+\&'\fBh2\fR', etc)\.
.IP "\fB--omit-link-check\fR"
Don't check that local fragment link references (\el{#ref \.\.\.}) resolve to
ids\.
@@ -294,6 +347,20 @@ Use \fIsep\fR instead of the default '\fB--\fR' as an optional separator
between options and arguments\. All the command line arguments that are parsed
after this separator are treated as program arguments\. Set the option
separator to the empty value if you don't want this functionality\.
+.IP "\fB--keep-separator\fR"
+Leave the option separator in the scanner\. This is primarily useful for
+incremental option parsing\.
+.IP "\fB--no-combined-flags\fR"
+Disable support for combining multiple single-character flags into a single
+argument (the \fB-xyz\fR form that is equivalent to \fB-x\fR \fB-y\fR
+\fB-z\fR)\. An argument is considered a combination of flags if it starts with
+a single option prefix (\fB--option-prefix\fR) and only contains letters and
+digits\. Note that an option with a value may not be part of such a
+combination, not even if it is specified last\.
+.IP "\fB--no-combined-values\fR"
+Disable support for combining an option and its value into a single argument
+with the assignment sign (the \fIoption\fR\fB=\fR\fIvalue\fR\fR form)\. This
+functionality requires a non-empty option prefix (\fB--option-prefix\fR)\.
.IP "\fB--include-with-brackets\fR"
Use angle brackets (\fB<>\fR) instead of quotes (\fB""\fR) in the generated
\fB#include\fR directives\.
@@ -308,14 +375,14 @@ Add \fIname\fR with an optional \fIrep\fR 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\fR \fIfile\fR"
-Read additional options from \fIfile\fR with each option appearing on a
-separate line optionally followed by space and an option value\. Empty lines
-and lines starting with \fB#\fR are ignored\. Option values can be enclosed in
-double (\fB"\fR) or single (\fB'\fR) quotes to preserve leading and trailing
-whitespaces as well as to specify empty values\. If the value itself contains
-trailing or leading quotes, enclose it with an extra pair of quotes, for
-example \fB'"x"'\fR\. Non-leading and non-trailing quotes are interpreted as
-being part of the option value\.
+Read additional options from \fIfile\fR\. Each option should appear on a
+separate line optionally followed by space or equal sign (\fB=\fR) and an
+option value\. Empty lines and lines starting with \fB#\fR are ignored\.
+Option values can be enclosed in double (\fB"\fR) or single (\fB'\fR) quotes
+to preserve leading and trailing whitespaces as well as to specify empty
+values\. If the value itself contains trailing or leading quotes, enclose it
+with an extra pair of quotes, for example \fB'"x"'\fR\. Non-leading and
+non-trailing quotes are interpreted as being part of the option value\.
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
diff --git a/doc/cli.xhtml b/doc/cli.xhtml
index 3bc4e57..bc3aa5e 100644
--- a/doc/cli.xhtml
+++ b/doc/cli.xhtml
@@ -101,23 +101,71 @@
<dd>Generate <code><b>parse()</b></code> functions instead of parsing
constructors. This is primarily useful for being able to parse into an
already initialized options class instance, for example, to implement
- merging/overriding.</dd>
+ option appending/overriding.</dd>
+
+ <dt><code><b>--generate-merge</b></code></dt>
+ <dd>Generate <code><b>merge()</b></code> functions. This is primarily
+ useful for being able to merge several already parsed options class
+ instances, for example, to implement option appending/overriding. Note
+ that this option forces <code><b>--generate-specifier</b></code>.</dd>
<dt><code><b>--generate-description</b></code></dt>
<dd>Generate the option description list that can be examined at
runtime.</dd>
<dt><code><b>--generate-file-scanner</b></code></dt>
- <dd>Generate the <code>argv_file_scanner</code> implementation. This
- scanner is capable of reading command line arguments from the
- <code>argv</code> array as well as files specified with command line
- options.</dd>
+ <dd>Generate the <code><b>argv_file_scanner</b></code> implementation.
+ This scanner is capable of reading command line arguments from the
+ <code><b>argv</b></code> array as well as files specified with command
+ line options.</dd>
<dt><code><b>--generate-vector-scanner</b></code></dt>
- <dd>Generate the <code>vector_scanner</code> implementation. This scanner
- is capable of reading command line arguments from
+ <dd>Generate the <code><b>vector_scanner</b></code> implementation. This
+ scanner is capable of reading command line arguments from
<code><b>vector&lt;string></b></code>.</dd>
+ <dt><code><b>--generate-group-scanner</b></code></dt>
+ <dd>Generate the <code><b>group_scanner</b></code> implementation. This
+ scanner supports grouping of arguments (usually options) to apply only to
+ a certain argument.
+
+ <p>Groups can be specified before (leading) and/or after (trailing) the
+ argument they apply to. A leading group starts with
+ '<code><b>{</b></code>' and ends with '<code><b>}+</b></code>' while a
+ trailing group starts with '<code><b>+{</b></code>' and ends with
+ '<code><b>}</b></code>'. For example:</p>
+
+ <pre>{ --foo --bar }+ arg # 'arg' with '--foo' '--bar'
+arg +{ fox=1 baz=2 } # 'arg' with 'fox=1' 'baz=2'</pre>
+
+ <p>Multiple leading and/or trailing groups can be specified for the same
+ argument. For example:</p>
+
+ <pre>{ -f }+ { -b }+ arg +{ f=1 } +{ b=2 } # 'arg' with '-f' 'b' 'f=1' 'b=2'</pre>
+
+ <p>Note that the group applies to a single argument only. For example:</p>
+
+ <pre>{ --foo }+ arg1 arg2 +{ --bar } # 'arg1' with '--foo' and
+ # 'arg2' with '--bar'</pre>
+
+ <p>The group separators ('<code><b>{</b></code>',
+ '<code><b>}+'</b></code>, etc) must be separate command line arguments. In
+ particular, they must not be adjacent either to the arguments inside the
+ group nor to the argument they apply to. All such cases will be treated as
+ ordinary arguments. For example:</p>
+
+ <pre>{--foo}+ arg # '{--foo}+' ...
+arg+{ --foo } # 'arg+{' ...</pre>
+
+ <p>If one of the group separators needs to be specified as an argument
+ verbatim, then it must be escaped with '<code><b>\</b></code>'. For
+ example:</p>
+
+ <pre>} # error: unexpected group separator
+}x # '}x'
+\} # '}'
+{ \}+ }+ arg # 'arg' with '}+'</pre></dd>
+
<dt><code><b>--suppress-inline</b></code></dt>
<dd>Generate all functions non-inline. By default simple functions are
made inline. This option suppresses creation of the inline file.</dd>
@@ -224,10 +272,12 @@
for the options class <code><i>name</i></code>. The
<code><i>name</i></code> value should be a fully-qualified class name, for
example, <code><b>app::options</b></code>. The <code><i>kind</i></code>
- value can be <code><b>short</b></code>, <code><b>long</b></code>, or
- <code><b>exclude</b></code>. If the value is <code><b>exclude</b></code>,
- then the class documentation is excluded from usage and man/HTML/text
- output. For usage, the <code><b>short</b></code> and
+ value can be <code><b>short</b></code>, <code><b>long</b></code>,
+ <code><b>exclude</b></code>, or <code><b>exclude-base</b></code>. If the
+ value is <code><b>exclude</b></code>, then the class documentation is
+ excluded from usage and man/HTML/text output. If it is
+ <code><b>exclude-base</b></code>, then it is only excluded when used as a
+ base. For usage, the <code><b>short</b></code> and
<code><b>long</b></code> values determine which usage function will be
called when the class is used as base or as part of the page usage (see
<code><b>--page-usage</b></code>). For man/HTML/text, these values
@@ -433,6 +483,26 @@
separator are treated as program arguments. Set the option separator to
the empty value if you don't want this functionality.</dd>
+ <dt><code><b>--keep-separator</b></code></dt>
+ <dd>Leave the option separator in the scanner. This is primarily useful
+ for incremental option parsing.</dd>
+
+ <dt><code><b>--no-combined-flags</b></code></dt>
+ <dd>Disable support for combining multiple single-character flags into a
+ single argument (the <code><b>-xyz</b></code> form that is equivalent to
+ <code><b>-x</b></code> <code><b>-y</b></code> <code><b>-z</b></code>). An
+ argument is considered a combination of flags if it starts with a single
+ option prefix (<code><b>--option-prefix</b></code>) and only contains
+ letters and digits. Note that an option with a value may not be part of
+ such a combination, not even if it is specified last.</dd>
+
+ <dt><code><b>--no-combined-values</b></code></dt>
+ <dd>Disable support for combining an option and its value into a single
+ argument with the assignment sign (the
+ <code><i>option</i><b>=</b><i>value</i></code> form). This functionality
+ requires a non-empty option prefix
+ (<code><b>--option-prefix</b></code>).</dd>
+
<dt><code><b>--include-with-brackets</b></code></dt>
<dd>Use angle brackets (<code><b>&lt;></b></code>) instead of quotes
(<code><b>""</b></code>) in the generated <code><b>#include</b></code>
@@ -454,11 +524,12 @@
already in this list.</dd>
<dt><code><b>--options-file</b></code> <code><i>file</i></code></dt>
- <dd>Read additional options from <code><i>file</i></code> 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. Option values can be enclosed in double (<code><b>"</b></code>)
- or single (<code><b>'</b></code>) quotes to preserve leading and trailing
+ <dd>Read additional options from <code><i>file</i></code>. Each option
+ should appear on a separate line optionally followed by space or equal
+ sign (<code><b>=</b></code>) and an option value. Empty lines and lines
+ starting with <code><b>#</b></code> are ignored. Option values can be
+ enclosed in double (<code><b>"</b></code>) or single
+ (<code><b>'</b></code>) quotes to preserve leading and trailing
whitespaces as well as to specify empty values. If the value itself
contains trailing or leading quotes, enclose it with an extra pair of
quotes, for example <code><b>'"x"'</b></code>. Non-leading and
diff --git a/tests/merge/buildfile b/tests/merge/buildfile
new file mode 100644
index 0000000..c1ef0a1
--- /dev/null
+++ b/tests/merge/buildfile
@@ -0,0 +1,10 @@
+# file : tests/merge/buildfile
+# copyright : Copyright (c) 2009-2019 Code Synthesis Tools CC
+# license : MIT; see accompanying LICENSE file
+
+exe{driver}: {hxx cxx}{* -test} cli.cxx{test}
+
+cxx.poptions =+ "-I$out_base"
+
+cli.cxx{test}: cli{test}
+cli.options = --generate-merge
diff --git a/tests/merge/driver.cxx b/tests/merge/driver.cxx
new file mode 100644
index 0000000..a0791e6
--- /dev/null
+++ b/tests/merge/driver.cxx
@@ -0,0 +1,56 @@
+// file : tests/merge/driver.cxx
+// author : Boris Kolpackov <boris@codesynthesis.com>
+// copyright : Copyright (c) 2009-2019 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+// Test parsed options merging.
+//
+
+#include <string>
+#include <cassert>
+
+#include "test.hxx"
+
+using namespace std;
+
+template <typename T, int N1, int N2>
+static T
+merge (const char* (&av1)[N1], const char* (&av2)[N2])
+{
+ int ac1 (N1);
+ int ac2 (N2);
+ T o1 (ac1, const_cast<char**> (av1));
+ T o2 (ac2, const_cast<char**> (av2));
+ o1.merge (o2);
+ return o1;
+}
+
+int
+main ()
+{
+ // Basics.
+ //
+ {
+ const char* a1[] = {"", "-i=123", "-v=1", "-v=2"};
+ const char* a2[] = {"", "-b", "-i=456", "-s=xyz", "-v=3", "-v=4"};
+ derived r (merge<derived> (a1, a2));
+
+ assert (r.b ());
+ assert (r.i_specified () && r.i () == 456);
+ assert (r.s_specified () && r.s () == "xyz");
+ assert (r.v_specified () && r.v ().size () == 4 &&
+ r.v ()[0] == 1 &&
+ r.v ()[1] == 2 &&
+ r.v ()[2] == 3 &&
+ r.v ()[3] == 4);
+ }
+
+ // Default value does not override.
+ //
+ {
+ const char* a1[] = {"", "-i=456"};
+ const char* a2[] = {"" };
+ derived r (merge<derived> (a1, a2));
+ assert (r.i_specified () && r.i () == 456);
+ }
+}
diff --git a/tests/merge/test.cli b/tests/merge/test.cli
new file mode 100644
index 0000000..64da534
--- /dev/null
+++ b/tests/merge/test.cli
@@ -0,0 +1,19 @@
+// file : tests/merge/test.cli
+// author : Boris Kolpackov <boris@codesynthesis.com>
+// copyright : Copyright (c) 2009-2019 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+include <string>;
+include <vector>;
+
+class base
+{
+ bool -b;
+ int -i = -1;
+ std::string -s;
+};
+
+class derived: base
+{
+ std::vector<int> -v;
+};