1420035836
This is required to support things like the '--hint' option for 'openstack server create', which allows you to specify arguments multiple times. Change-Id: If73cab759fa09bddf1ff519923c5972c3b2052b1 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
285 lines
11 KiB
Python
285 lines
11 KiB
Python
# Copyright 2013 OpenStack Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
#
|
|
|
|
"""argparse Custom Actions"""
|
|
|
|
import argparse
|
|
|
|
from osc_lib.i18n import _
|
|
|
|
|
|
class KeyValueAction(argparse.Action):
|
|
"""A custom action to parse arguments as key=value pairs
|
|
|
|
Ensures that ``dest`` is a dict and values are strings.
|
|
"""
|
|
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
# Make sure we have an empty dict rather than None
|
|
if getattr(namespace, self.dest, None) is None:
|
|
setattr(namespace, self.dest, {})
|
|
|
|
# Add value if an assignment else remove it
|
|
if '=' in values:
|
|
values_list = values.split('=', 1)
|
|
# NOTE(qtang): Prevent null key setting in property
|
|
if '' == values_list[0]:
|
|
msg = _("Property key must be specified: %s")
|
|
raise argparse.ArgumentTypeError(msg % str(values))
|
|
else:
|
|
getattr(namespace, self.dest, {}).update([values_list])
|
|
else:
|
|
msg = _("Expected 'key=value' type, but got: %s")
|
|
raise argparse.ArgumentTypeError(msg % str(values))
|
|
|
|
|
|
class KeyValueAppendAction(argparse.Action):
|
|
"""A custom action to parse arguments as key=value pairs
|
|
|
|
Ensures that ``dest`` is a dict and values are lists of strings.
|
|
"""
|
|
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
# Make sure we have an empty dict rather than None
|
|
if getattr(namespace, self.dest, None) is None:
|
|
setattr(namespace, self.dest, {})
|
|
|
|
# Add value if an assignment else remove it
|
|
if '=' in values:
|
|
key, value = values.split('=', 1)
|
|
# NOTE(qtang): Prevent null key setting in property
|
|
if '' == key:
|
|
msg = _("Property key must be specified: %s")
|
|
raise argparse.ArgumentTypeError(msg % str(values))
|
|
|
|
dest = getattr(namespace, self.dest)
|
|
if key in dest:
|
|
dest[key].append(value)
|
|
else:
|
|
dest[key] = [value]
|
|
else:
|
|
msg = _("Expected 'key=value' type, but got: %s")
|
|
raise argparse.ArgumentTypeError(msg % str(values))
|
|
|
|
|
|
class MultiKeyValueAction(argparse.Action):
|
|
"""A custom action to parse arguments as key1=value1,key2=value2 pairs
|
|
|
|
Ensure that ``dest`` is a list. The list will finally contain multiple
|
|
dicts, with key=value pairs in them.
|
|
|
|
NOTE: The arguments string should be a comma separated key-value pairs.
|
|
And comma(',') and equal('=') may not be used in the key or value.
|
|
"""
|
|
|
|
def __init__(self, option_strings, dest, nargs=None,
|
|
required_keys=None, optional_keys=None, **kwargs):
|
|
"""Initialize the action object, and parse customized options
|
|
|
|
Required keys and optional keys can be specified when initializing
|
|
the action to enable the key validation. If none of them specified,
|
|
the key validation will be skipped.
|
|
|
|
:param required_keys: a list of required keys
|
|
:param optional_keys: a list of optional keys
|
|
"""
|
|
if nargs:
|
|
msg = _("Parameter 'nargs' is not allowed, but got %s")
|
|
raise ValueError(msg % nargs)
|
|
|
|
super(MultiKeyValueAction, self).__init__(option_strings,
|
|
dest, **kwargs)
|
|
|
|
# required_keys: A list of keys that is required. None by default.
|
|
if required_keys and not isinstance(required_keys, list):
|
|
msg = _("'required_keys' must be a list")
|
|
raise TypeError(msg)
|
|
self.required_keys = set(required_keys or [])
|
|
|
|
# optional_keys: A list of keys that is optional. None by default.
|
|
if optional_keys and not isinstance(optional_keys, list):
|
|
msg = _("'optional_keys' must be a list")
|
|
raise TypeError(msg)
|
|
self.optional_keys = set(optional_keys or [])
|
|
|
|
def __call__(self, parser, namespace, values, metavar=None):
|
|
# Make sure we have an empty list rather than None
|
|
if getattr(namespace, self.dest, None) is None:
|
|
setattr(namespace, self.dest, [])
|
|
|
|
params = {}
|
|
for kv in values.split(','):
|
|
# Add value if an assignment else raise ArgumentTypeError
|
|
if '=' in kv:
|
|
kv_list = kv.split('=', 1)
|
|
# NOTE(qtang): Prevent null key setting in property
|
|
if '' == kv_list[0]:
|
|
msg = _("Each property key must be specified: %s")
|
|
raise argparse.ArgumentTypeError(msg % str(kv))
|
|
else:
|
|
params.update([kv_list])
|
|
else:
|
|
msg = _(
|
|
"Expected comma separated 'key=value' pairs, but got: %s"
|
|
)
|
|
raise argparse.ArgumentTypeError(msg % str(kv))
|
|
|
|
# Check key validation
|
|
valid_keys = self.required_keys | self.optional_keys
|
|
if valid_keys:
|
|
invalid_keys = [k for k in params if k not in valid_keys]
|
|
if invalid_keys:
|
|
msg = _(
|
|
"Invalid keys %(invalid_keys)s specified.\n"
|
|
"Valid keys are: %(valid_keys)s"
|
|
)
|
|
raise argparse.ArgumentTypeError(msg % {
|
|
'invalid_keys': ', '.join(invalid_keys),
|
|
'valid_keys': ', '.join(valid_keys),
|
|
})
|
|
|
|
if self.required_keys:
|
|
missing_keys = [k for k in self.required_keys if k not in params]
|
|
if missing_keys:
|
|
msg = _(
|
|
"Missing required keys %(missing_keys)s.\n"
|
|
"Required keys are: %(required_keys)s"
|
|
)
|
|
raise argparse.ArgumentTypeError(msg % {
|
|
'missing_keys': ', '.join(missing_keys),
|
|
'required_keys': ', '.join(self.required_keys),
|
|
})
|
|
|
|
# Update the dest dict
|
|
getattr(namespace, self.dest, []).append(params)
|
|
|
|
|
|
class MultiKeyValueCommaAction(MultiKeyValueAction):
|
|
"""Custom action to parse arguments from a set of key=value pair
|
|
|
|
Ensures that ``dest`` is a dict.
|
|
Parses dict by separating comma separated string into individual values
|
|
Ex. key1=val1,val2,key2=val3 => {"key1": "val1,val2", "key2": "val3"}
|
|
"""
|
|
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
"""Overwrite the __call__ function of MultiKeyValueAction
|
|
|
|
This is done to handle scenarios where we may have comma seperated
|
|
data as a single value.
|
|
"""
|
|
# Make sure we have an empty list rather than None
|
|
if getattr(namespace, self.dest, None) is None:
|
|
setattr(namespace, self.dest, [])
|
|
|
|
params = {}
|
|
key = ''
|
|
for kv in values.split(','):
|
|
# Add value if an assignment else raise ArgumentTypeError
|
|
if '=' in kv:
|
|
kv_list = kv.split('=', 1)
|
|
# NOTE(qtang): Prevent null key setting in property
|
|
if '' == kv_list[0]:
|
|
msg = _("A key must be specified before '=': %s")
|
|
raise argparse.ArgumentTypeError(msg % str(kv))
|
|
else:
|
|
params.update([kv_list])
|
|
key = kv_list[0]
|
|
else:
|
|
# If the ',' split does not have key=value pair, then it
|
|
# means the current value is a part of the previous
|
|
# key=value pair, so append it.
|
|
try:
|
|
params[key] = "%s,%s" % (params[key], kv)
|
|
except KeyError:
|
|
msg = _("A key=value pair is required: %s")
|
|
raise argparse.ArgumentTypeError(msg % str(kv))
|
|
|
|
# Check key validation
|
|
valid_keys = self.required_keys | self.optional_keys
|
|
if valid_keys:
|
|
invalid_keys = [k for k in params if k not in valid_keys]
|
|
if invalid_keys:
|
|
msg = _(
|
|
"Invalid keys %(invalid_keys)s specified.\n"
|
|
"Valid keys are: %(valid_keys)s"
|
|
)
|
|
raise argparse.ArgumentTypeError(msg % {
|
|
'invalid_keys': ', '.join(invalid_keys),
|
|
'valid_keys': ', '.join(valid_keys),
|
|
})
|
|
|
|
if self.required_keys:
|
|
missing_keys = [k for k in self.required_keys if k not in params]
|
|
if missing_keys:
|
|
msg = _(
|
|
"Missing required keys %(missing_keys)s.\n"
|
|
"Required keys are: %(required_keys)s"
|
|
)
|
|
raise argparse.ArgumentTypeError(msg % {
|
|
'missing_keys': ', '.join(missing_keys),
|
|
'required_keys': ', '.join(self.required_keys),
|
|
})
|
|
|
|
# Update the dest dict
|
|
getattr(namespace, self.dest, []).append(params)
|
|
|
|
|
|
class RangeAction(argparse.Action):
|
|
"""A custom action to parse a single value or a range of values
|
|
|
|
Parses single integer values or a range of integer values delimited
|
|
by a colon and returns a tuple of integers:
|
|
'4' sets ``dest`` to (4, 4)
|
|
'6:9' sets ``dest`` to (6, 9)
|
|
"""
|
|
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
range = values.split(':')
|
|
if len(range) == 0:
|
|
# Nothing passed, return a zero default
|
|
setattr(namespace, self.dest, (0, 0))
|
|
elif len(range) == 1:
|
|
# Only a single value is present
|
|
setattr(namespace, self.dest, (int(range[0]), int(range[0])))
|
|
elif len(range) == 2:
|
|
# Range of two values
|
|
if int(range[0]) <= int(range[1]):
|
|
setattr(namespace, self.dest, (int(range[0]), int(range[1])))
|
|
else:
|
|
msg = _("Invalid range, %(min)s is not less than %(max)s")
|
|
raise argparse.ArgumentError(self, msg % {
|
|
'min': range[0],
|
|
'max': range[1],
|
|
})
|
|
else:
|
|
# Too many values
|
|
msg = _("Invalid range, too many values")
|
|
raise argparse.ArgumentError(self, msg)
|
|
|
|
|
|
class NonNegativeAction(argparse.Action):
|
|
"""A custom action to check whether the value is non-negative or not
|
|
|
|
Ensures the value is >= 0.
|
|
"""
|
|
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
if int(values) >= 0:
|
|
setattr(namespace, self.dest, values)
|
|
else:
|
|
msg = _("%s expected a non-negative integer")
|
|
raise argparse.ArgumentTypeError(msg % str(option_string))
|