From a84693938be8260657ef54adc9fbb1376a3ed68d Mon Sep 17 00:00:00 2001 From: Iustin Pop Date: Mon, 13 Oct 2008 12:17:12 +0000 Subject: [PATCH] Add two new options types for CLI usage MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit For the new 2.0-style command line options, we need to parse strings of the type: ident:key=val[,...] and key=val[,...] This patch adds two new option builders for these two, which return (ident, {key=val,}) and {key=val,} for the above two formats. It also handles specially constructs of type “key” (val is set to True) and “no_key” (val is set to False, and the ‘no_’ prefix is stripped). Reviewed-by: imsnah --- lib/cli.py | 83 ++++++++++++++++++++++++++++++++++++++++++- test/ganeti.cli_unittest.py | 18 +++++++++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/lib/cli.py b/lib/cli.py index 9f2b9bc..c10dd78 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -42,7 +42,8 @@ from optparse import (OptionParser, make_option, TitledHelpFormatter, __all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain", "SubmitOpCode", "GetClient", - "cli_option", "GenerateTable", "AskUser", + "cli_option", "ikv_option", "keyval_option", + "GenerateTable", "AskUser", "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE", "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT", "SUBMIT_OPT", "ListTags", "AddTags", "RemoveTags", "TAG_SRC_OPT", @@ -221,8 +222,88 @@ class CliOption(Option): TYPE_CHECKER["unit"] = check_unit +def _SplitKeyVal(opt, data): + """Convert a KeyVal string into a dict. + + This function will convert a key=val[,...] string into a dict. Empty + values will be converted specially: keys which have the prefix 'no_' + will have the value=False and the prefix stripped, the others will + have value=True. + + @type opt: string + @param opt: a string holding the option name for which we process the + data, used in building error messages + @type data: string + @param data: a string of the format key=val,key=val,... + @rtype: dict + @return: {key=val, key=val} + @raises errors.ParameterError: if there are duplicate keys + + """ + NO_PREFIX = "no_" + kv_dict = {} + for elem in data.split(","): + if "=" in elem: + key, val = elem.split("=", 1) + else: + if elem.startswith(NO_PREFIX): + key, val = elem[len(NO_PREFIX):], False + else: + key, val = elem, True + if key in kv_dict: + raise errors.ParameterError("Duplicate key '%s' in option %s" % + (key, opt)) + kv_dict[key] = val + return kv_dict + + +def check_ident_key_val(option, opt, value): + """Custom parser for the IdentKeyVal option type. + + """ + if ":" not in value: + retval = (value, {}) + else: + ident, rest = value.split(":", 1) + kv_dict = _SplitKeyVal(opt, rest) + retval = (ident, kv_dict) + return retval + + +class IdentKeyValOption(Option): + """Custom option class for ident:key=val,key=val options. + + This will store the parsed values as a tuple (ident, {key: val}). As + such, multiple uses of this option via action=append is possible. + + """ + TYPES = Option.TYPES + ("identkeyval",) + TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER) + TYPE_CHECKER["identkeyval"] = check_ident_key_val + + +def check_key_val(option, opt, value): + """Custom parser for the KeyVal option type. + + """ + return _SplitKeyVal(opt, value) + + +class KeyValOption(Option): + """Custom option class for key=val,key=val options. + + This will store the parsed values as a dict {key: val}. + + """ + TYPES = Option.TYPES + ("keyval",) + TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER) + TYPE_CHECKER["keyval"] = check_key_val + + # optparse.py sets make_option, so we do it for our own option class, too cli_option = CliOption +ikv_option = IdentKeyValOption +keyval_option = KeyValOption def _ParseArgs(argv, commands, aliases): diff --git a/test/ganeti.cli_unittest.py b/test/ganeti.cli_unittest.py index fb0d820..9987727 100755 --- a/test/ganeti.cli_unittest.py +++ b/test/ganeti.cli_unittest.py @@ -27,7 +27,7 @@ import ganeti import testutils from ganeti import constants from ganeti import cli -from ganeti.errors import OpPrereqError +from ganeti.errors import OpPrereqError, ParameterError class TestParseTimespec(unittest.TestCase): """Testing case for ParseTimespec""" @@ -59,5 +59,21 @@ class TestParseTimespec(unittest.TestCase): self.failUnlessRaises(OpPrereqError, cli.ParseTimespec, value) +class TestSplitKeyVal(unittest.TestCase): + """Testing case for cli._SplitKeyVal""" + DATA = "a=b,c,no_d" + RESULT = {"a": "b", "c": True, "d": False} + + def testSplitKeyVal(self): + """Test splitting""" + self.failUnlessEqual(cli._SplitKeyVal("option", self.DATA), self.RESULT) + + def testDuplicateParam(self): + """Test duplicate parameters""" + for data in ("a=1,a=2", "a,no_a"): + self.failUnlessRaises(ParameterError, cli._SplitKeyVal, + "option", data) + + if __name__ == '__main__': unittest.main() -- 1.7.10.4