Revision 726ae450

b/lib/cli.py
563 563
    raise OptionValueError("option %s: %s" % (opt, err))
564 564

  
565 565

  
566
def _SplitKeyVal(opt, data):
566
def _SplitKeyVal(opt, data, parse_prefixes):
567 567
  """Convert a KeyVal string into a dict.
568 568

  
569 569
  This function will convert a key=val[,...] string into a dict. Empty
570 570
  values will be converted specially: keys which have the prefix 'no_'
571
  will have the value=False and the prefix stripped, the others will
571
  will have the value=False and the prefix stripped, keys with the prefix
572
  "-" will have value=None and the prefix stripped, and the others will
572 573
  have value=True.
573 574

  
574 575
  @type opt: string
......
576 577
      data, used in building error messages
577 578
  @type data: string
578 579
  @param data: a string of the format key=val,key=val,...
580
  @type parse_prefixes: bool
581
  @param parse_prefixes: whether to handle prefixes specially
579 582
  @rtype: dict
580 583
  @return: {key=val, key=val}
581 584
  @raises errors.ParameterError: if there are duplicate keys
......
586 589
    for elem in utils.UnescapeAndSplit(data, sep=","):
587 590
      if "=" in elem:
588 591
        key, val = elem.split("=", 1)
589
      else:
592
      elif parse_prefixes:
590 593
        if elem.startswith(NO_PREFIX):
591 594
          key, val = elem[len(NO_PREFIX):], False
592 595
        elif elem.startswith(UN_PREFIX):
593 596
          key, val = elem[len(UN_PREFIX):], None
594 597
        else:
595 598
          key, val = elem, True
599
      else:
600
        raise errors.ParameterError("Missing value for key '%s' in option %s" %
601
                                    (elem, opt))
596 602
      if key in kv_dict:
597 603
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
598 604
                                    (key, opt))
......
600 606
  return kv_dict
601 607

  
602 608

  
603
def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
604
  """Custom parser for ident:key=val,key=val options.
609
def _SplitIdentKeyVal(opt, value, parse_prefixes):
610
  """Helper function to parse "ident:key=val,key=val" options.
605 611

  
606
  This will store the parsed values as a tuple (ident, {key: val}). As such,
607
  multiple uses of this option via action=append is possible.
612
  @type opt: string
613
  @param opt: option name, used in error messages
614
  @type value: string
615
  @param value: expected to be in the format "ident:key=val,key=val,..."
616
  @type parse_prefixes: bool
617
  @param parse_prefixes: whether to handle prefixes specially (see
618
      L{_SplitKeyVal})
619
  @rtype: tuple
620
  @return: (ident, {key=val, key=val})
621
  @raises errors.ParameterError: in case of duplicates or other parsing errors
608 622

  
609 623
  """
610 624
  if ":" not in value:
......
612 626
  else:
613 627
    ident, rest = value.split(":", 1)
614 628

  
615
  if ident.startswith(NO_PREFIX):
629
  if parse_prefixes and ident.startswith(NO_PREFIX):
616 630
    if rest:
617 631
      msg = "Cannot pass options when removing parameter groups: %s" % value
618 632
      raise errors.ParameterError(msg)
619 633
    retval = (ident[len(NO_PREFIX):], False)
620
  elif (ident.startswith(UN_PREFIX) and
621
        (len(ident) <= len(UN_PREFIX) or
622
         not ident[len(UN_PREFIX)][0].isdigit())):
634
  elif (parse_prefixes and ident.startswith(UN_PREFIX) and
635
        (len(ident) <= len(UN_PREFIX) or not ident[len(UN_PREFIX)].isdigit())):
623 636
    if rest:
624 637
      msg = "Cannot pass options when removing parameter groups: %s" % value
625 638
      raise errors.ParameterError(msg)
626 639
    retval = (ident[len(UN_PREFIX):], None)
627 640
  else:
628
    kv_dict = _SplitKeyVal(opt, rest)
641
    kv_dict = _SplitKeyVal(opt, rest, parse_prefixes)
629 642
    retval = (ident, kv_dict)
630 643
  return retval
631 644

  
632 645

  
646
def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
647
  """Custom parser for ident:key=val,key=val options.
648

  
649
  This will store the parsed values as a tuple (ident, {key: val}). As such,
650
  multiple uses of this option via action=append is possible.
651

  
652
  """
653
  return _SplitIdentKeyVal(opt, value, True)
654

  
655

  
633 656
def check_key_val(option, opt, value):  # pylint: disable=W0613
634 657
  """Custom parser class for key=val,key=val options.
635 658

  
636 659
  This will store the parsed values as a dict {key: val}.
637 660

  
638 661
  """
639
  return _SplitKeyVal(opt, value)
662
  return _SplitKeyVal(opt, value, True)
663

  
664

  
665
def _SplitListKeyVal(opt, value):
666
  retval = {}
667
  for elem in value.split("/"):
668
    if not elem:
669
      raise errors.ParameterError("Empty section in option '%s'" % opt)
670
    (ident, valdict) = _SplitIdentKeyVal(opt, elem, False)
671
    if ident in retval:
672
      msg = ("Duplicated parameter '%s' in parsing %s: %s" %
673
             (ident, opt, elem))
674
      raise errors.ParameterError(msg)
675
    retval[ident] = valdict
676
  return retval
677

  
678

  
679
def check_list_ident_key_val(_, opt, value):
680
  """Custom parser for "ident:key=val,key=val/ident:key=val" options.
681

  
682
  @rtype: list of dictionary
683
  @return: {ident: {key: val, key: val}, ident: {key: val}}
684

  
685
  """
686
  return _SplitListKeyVal(opt, value)
640 687

  
641 688

  
642 689
def check_bool(option, opt, value): # pylint: disable=W0613
......
711 758
    "completion_suggest",
712 759
    ]
713 760
  TYPES = Option.TYPES + (
761
    "listidentkeyval",
714 762
    "identkeyval",
715 763
    "keyval",
716 764
    "unit",
......
719 767
    "maybefloat",
720 768
    )
721 769
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
770
  TYPE_CHECKER["listidentkeyval"] = check_list_ident_key_val
722 771
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
723 772
  TYPE_CHECKER["keyval"] = check_key_val
724 773
  TYPE_CHECKER["unit"] = check_unit
b/test/py/ganeti.cli_unittest.py
71 71
  """Testing case for cli._SplitKeyVal"""
72 72
  DATA = "a=b,c,no_d,-e"
73 73
  RESULT = {"a": "b", "c": True, "d": False, "e": None}
74
  RESULT_NOPREFIX = {"a": "b", "c": {}, "no_d": {}, "-e": {}}
74 75

  
75 76
  def testSplitKeyVal(self):
76 77
    """Test splitting"""
77
    self.failUnlessEqual(cli._SplitKeyVal("option", self.DATA), self.RESULT)
78
    self.failUnlessEqual(cli._SplitKeyVal("option", self.DATA, True),
79
                         self.RESULT)
78 80

  
79 81
  def testDuplicateParam(self):
80 82
    """Test duplicate parameters"""
81 83
    for data in ("a=1,a=2", "a,no_a"):
82 84
      self.failUnlessRaises(ParameterError, cli._SplitKeyVal,
83
                            "option", data)
85
                            "option", data, True)
84 86

  
85 87
  def testEmptyData(self):
86 88
    """Test how we handle splitting an empty string"""
87
    self.failUnlessEqual(cli._SplitKeyVal("option", ""), {})
89
    self.failUnlessEqual(cli._SplitKeyVal("option", "", True), {})
88 90

  
89 91

  
90 92
class TestIdentKeyVal(unittest.TestCase):
......
101 103
    self.assertEqual(cikv("no_bar"), ("bar", False))
102 104
    self.assertRaises(ParameterError, cikv, "no_bar:foo")
103 105
    self.assertRaises(ParameterError, cikv, "no_bar:foo=baz")
106
    self.assertRaises(ParameterError, cikv, "bar:foo=baz,foo=baz")
104 107
    self.assertEqual(cikv("-foo"), ("foo", None))
105 108
    self.assertRaises(ParameterError, cikv, "-foo:a=c")
106 109

  
......
115 118
    for i in ["-:", "-"]:
116 119
      self.assertEqual(cikv(i), ("", None))
117 120

  
121
  @staticmethod
122
  def _csikv(value):
123
    return cli._SplitIdentKeyVal("opt", value, False)
124

  
125
  def testIdentKeyValNoPrefix(self):
126
    """Test identkeyval without prefixes"""
127
    test_cases = [
128
      ("foo:bar", None),
129
      ("foo:no_bar", None),
130
      ("foo:bar=baz,bar=baz", None),
131
      ("foo",
132
       ("foo", {})),
133
      ("foo:bar=baz",
134
       ("foo", {"bar": "baz"})),
135
      ("no_foo:-1=baz,no_op=3",
136
       ("no_foo", {"-1": "baz", "no_op": "3"})),
137
      ]
138
    for (arg, res) in test_cases:
139
      if res is None:
140
        self.assertRaises(ParameterError, self._csikv, arg)
141
      else:
142
        self.assertEqual(self._csikv(arg), res)
143

  
144

  
145
class TestListIdentKeyVal(unittest.TestCase):
146
  """Test for cli.check_list_ident_key_val()"""
147

  
148
  @staticmethod
149
  def _clikv(value):
150
    return cli.check_list_ident_key_val("option", "opt", value)
151

  
152
  def testListIdentKeyVal(self):
153
    test_cases = [
154
      ("",
155
       None),
156
      ("foo",
157
       {"foo": {}}),
158
      ("foo:bar=baz",
159
       {"foo": {"bar": "baz"}}),
160
      ("foo:bar=baz/foo:bat=bad",
161
       None),
162
      ("foo:abc=42/bar:def=11",
163
       {"foo": {"abc": "42"},
164
        "bar": {"def": "11"}}),
165
      ("foo:abc=42/bar:def=11,ghi=07",
166
       {"foo": {"abc": "42"},
167
        "bar": {"def": "11", "ghi": "07"}}),
168
      ]
169
    for (arg, res) in test_cases:
170
      if res is None:
171
        self.assertRaises(ParameterError, self._clikv, arg)
172
      else:
173
        self.assertEqual(res, self._clikv(arg))
174

  
118 175

  
119 176
class TestToStream(unittest.TestCase):
120 177
  """Test the ToStream functions"""

Also available in: Unified diff