---
src/python/matahari/shellmode.py | 105 ++++++++++++++++
src/python/matahari/shelltypes.py | 243 +++++++++++++++++++++++++++++++++++++
2 files changed, 345 insertions(+), 3 deletions(-)
diff --git a/src/python/matahari/shellmode.py b/src/python/matahari/shellmode.py
index fc3e844..10741df 100644
--- a/src/python/matahari/shellmode.py
+++ b/src/python/matahari/shellmode.py
@@ -6,6 +6,9 @@ from interpreter import *
import shelltypes as types
+QMF_ACCESS_MODES = (READ_CREATE, READ_WRITE, READ_ONLY) = range(1, 4)
+
+
class RootMode(Mode):
"""The default, root, shell mode."""
@@ -24,6 +27,9 @@ class RootMode(Mode):
types.Class(self.state, pkg_state))
self += class_wrapper(self.package_class)
+ def writelist(self, objects):
+ self.write(*[str(o) for o in objects])
+
def getFilteredMode(self):
return FilteredMode(self.state)
@@ -33,12 +39,12 @@ class RootMode(Mode):
@Command('show', 'hosts')
def show_hosts(self, *kws):
"""Show the list of connected hosts."""
- self.write(*[str(h) for h in self.state.list_hosts()])
+ self.writelist(self.state.list_hosts())
@Command('show', 'agents')
def show_agents(self, *kws):
"""Show the list of connected agents."""
- self.write(*[str(a) for a in self.state.agents()])
+ self.writelist(self.state.agents())
def select(self, kw_select, kw_host, *arg_hosts):
""" Select a set of hosts to restrict commands
to."""
@@ -72,18 +78,111 @@ class FilteredMode(RootMode):
@Command('show', 'selection')
def show_selection(self, *kws):
"""Show the current host selection."""
- self.write(*[str(h) for h in self.state.hosts])
+ self.writelist(self.state.hosts)
def prompt(self):
return '(host)'
+class CallMethodHandler(object):
+ """CLI Handler for a method call"""
+
+ def __new__(cls, mode, method):
+ handler = super(CallMethodHandler, cls).__new__(cls, mode, method)
+ CallMethodHandler.__init__(handler, mode, method)
+ wrapper = Command('call', handler._method.name, *handler._args)
+ return wrapper(handler)
+
+ def __init__(self, mode, method):
+ self._mode = mode
+ self._method = method
+ self._args = [types.QMFProperty(a) for a in self._method.arguments
+ if 'I' in a.dir]
+ self.__name__ = method.name
+ self.__doc__ = self.help()
+
+ def help(self):
+ lines = []
+ if self._method.desc is not None:
+ lines.append(self._method.desc)
+ if self._args:
+ lines.append('Parameters:')
+ lines.extend(a.help() for a in self._args)
+ return '\n'.join(lines)
+
+ def __call__(self, kw_call, methodname, *args):
+ """Call a method on the selected objects"""
+ qmf_method = getattr(self._mode.state.objects, methodname)
+ self._mode.writelist(qmf_method(*args))
+
+class GetPropertyHandler(object):
+ """CLI handler for getting an object property or
statistic"""
+
+ def __new__(cls, mode, prop, is_stat=False):
+ handler = super(GetPropertyHandler, cls).__new__(cls, mode,
+ prop, is_stat)
+ GetPropertyHandler.__init__(handler, mode, prop, is_stat)
+ wrapper = Command('get', repr(prop))
+ return wrapper(handler)
+
+ def __init__(self, mode, prop, is_stat=False):
+ self._mode = mode
+ self._prop = prop
+ self._is_stat = is_stat
+ self.__name__ = 'get_property'
+ self.__doc__ = self.help()
+
+ def __call__(self, kw_get, propname):
+ self._mode.writelist(getattr(self._mode.state.objects, propname))
+
+ def help(self):
+ doc = "Get the value of the %s %s for the selected objects"
+ desc = self._prop.desc and '\n\n' + self._prop.desc or ''
+ return (doc % (self._prop.name,
+ self._is_stat and 'statistic' or 'property')) +
desc
+
+class SetPropertyHandler(object):
+ """CLI handler for setting an object property"""
+
+ def __new__(cls, mode, prop):
+ handler = super(SetPropertyHandler, cls).__new__(cls, mode, prop)
+ SetPropertyHandler.__init__(handler, mode, prop)
+ wrapper = Command('set', repr(prop), handler._arg)
+ return wrapper(handler)
+
+ def __init__(self, mode, prop):
+ self._mode = mode
+ self._prop = prop
+ self._arg = types.QMFProperty(prop)
+ self.__name__ = 'set_property'
+ self.__doc__ = self.help()
+
+ def __call__(self, kw_set, propname, value):
+ setattr(self._mode.state.objects, propname, value)
+
+ def help(self):
+ doc = "Set the value of the %s property for the selected objects"
+ return (doc % (str(self._arg),)) + '\nDetails:\n' + self._arg.help()
+
+
class ClassMode(RootMode):
"""Shell mode filtered by QMF class."""
def __init__(self, state):
RootMode.__init__(self, state)
+ for prop in self.state.objects._properties():
+ self += GetPropertyHandler(self, prop)
+
+ if int(prop.access) == READ_WRITE:
+ self += SetPropertyHandler(self, prop)
+
+ for stat in self.state.objects._statistics():
+ self += GetPropertyHandler(self, stat, True)
+
+ for method in self.state.objects._methods():
+ self += CallMethodHandler(self, method)
+
def getFilteredMode(self):
return FilteredClassMode(self.state)
diff --git a/src/python/matahari/shelltypes.py b/src/python/matahari/shelltypes.py
index e6f2b7b..e2d6de1 100644
--- a/src/python/matahari/shelltypes.py
+++ b/src/python/matahari/shelltypes.py
@@ -1,3 +1,4 @@
+import uuid
class Host(object):
def __init__(self, state):
@@ -67,3 +68,245 @@ class Class(object):
def complete(self, param):
classnames = self.state.list_classnames(self.pkginfo.get())
return [c + ' ' for c in classnames if c.startswith(param)]
+
+
+QMF_TYPES = (U8, U16, U32, U64,
+ _UNUSED,
+ SSTR, LSTR,
+ ABSTIME, DELTATIME,
+ REF,
+ BOOL,
+ FLOAT, DOUBLE,
+ UUID,
+ FTABLE,
+ S8, S16, S32, S64,
+ OBJECT, LIST, ARRAY) = range(1, 23)
+
+class QMFProperty(object):
+ """CLI verification class for a QMF Property
argument"""
+
+ def __new__(cls, arg):
+ """Create a CLI argument of the correct type from a
SchemaArg."""
+ if cls == QMFProperty:
+ return _qmfPropertyTypes.get(arg.type, String)(arg)
+ else:
+ return super(QMFProperty, cls).__new__(cls, arg)
+
+ def __init__(self, arg):
+ self.arg = arg
+ self.__name__ = str(self)
+
+ def __repr__(self):
+ return self.__class__.__name__.lower()
+
+ def __str__(self):
+ return self.arg.name
+
+ def help(self):
+ return ('%9s %-18s %s' % (repr(self),
+ str(self),
+ self.arg.desc or '')).rstrip()
+
+ def default(self):
+ return self.arg.default
+
+class String(QMFProperty):
+ """A QMF String property argument"""
+ def __repr__(self):
+ if self.arg.type not in _qmfPropertyTypes:
+ return str(self)
+ return self.arg.type == SSTR and 'sstr' or 'lstr'
+
+ def __call__(self, param):
+ maxlen = self.arg.maxlen or (self.arg.type == SSTR and 255) or 65535
+ if len(param) > maxlen:
+ raise ValueError('Parameter is too long')
+ return param
+
+class Bool(QMFProperty):
+ """A QMF Boolean property argument"""
+
+ TRUTHY = ('true', 't', '1', 'y', 'yes')
+ FALSEY = ('false', 'f', '0', 'n', 'no')
+
+ def __call__(self, param):
+ lc = param.lower()
+ if lc in self.TRUTHY:
+ return True
+ if lc in self.FALSEY:
+ return False
+
+ try:
+ value = eval(param, {}, {})
+ except (NameError, SyntaxError):
+ raise ValueError('"%s" is not a boolean' % (param,))
+
+ if not isinstance(value, (int, bool)):
+ raise ValueError('"%s" is not a boolean' % (param,))
+
+ return bool(value)
+
+ def complete(self, param):
+ if not param:
+ return map(lambda l: l[0].capitalize(), (self.TRUTHY, self.FALSEY))
+ lc = param.lower()
+ matches = []
+ for v in self.TRUTHY + self.FALSEY:
+ if v == lc:
+ return [param + ' ']
+ if v.startswith(lc):
+ return [param + v[len(param):] + ' ']
+ return []
+
+class Int(QMFProperty):
+ """A QMF Integer property argument"""
+
+ NAMES = {U8: 'u8', U16: 'u16', U32: 'u32', U64:
'u64',
+ S8: 's8', S16: 's16', S32: 's32', S64:
's64'}
+ MINIMUM = {U8: 0, U16: 0,
+ U32: 0, U64: 0,
+ S8: -(1 << 7), S16: -(1 << 15),
+ S32: -(1 << 31), S64: -(1 << 63)}
+ MAXIMUM = {U8: (1 << 8) - 1, U16: (1 << 16) - 1,
+ U32: (1 << 32) - 1, U64: (1 << 64) - 1,
+ S8: (1 << 7) - 1, S16: (1 << 15) - 1,
+ S32: (1 << 31) - 1, S64: (1 << 63) - 1}
+
+ def __str__(self):
+ if self.arg.min is not None or self.arg.max is not None:
+ return '<%d-%d>' % (self._min(), self._max())
+
+ return QMFProperty.__str__(self)
+
+ def __repr__(self):
+ try:
+ return self.NAMES[self.arg.type]
+ except KeyError:
+ return QMFProperty.__repr__(self)
+
+ def _min(self):
+ """Get the minimum allowed value"""
+ if self.arg.min is not None:
+ return self.arg.min
+ try:
+ return self.MINIMUM[self.arg.type]
+ except KeyError:
+ return -(1 << 31)
+
+ def _max(self):
+ """Get the maximum allowed value"""
+ if self.arg.max is not None:
+ return self.arg.max
+ try:
+ return self.MAXIMUM[self.arg.type]
+ except KeyError:
+ return (1 << 31) - 1
+
+ def __call__(self, param):
+ value = int(param)
+ if value < self._min():
+ raise ValueError('Value %d underflows minimum of range (%d)' %
+ (value, self._min()))
+ if value > self._max():
+ raise ValueError('Value %d overflows maximum of range (%d)' %
+ (value, self._max()))
+ return value
+
+class Float(QMFProperty):
+ """A QMF Floating Point property argument"""
+
+ def __repr__(self):
+ return self.arg.type == FLOAT and 'float' or 'double'
+
+ def __call__(self, param):
+ return float(param)
+
+class Uuid(QMFProperty):
+ """A QMF UUID property argument"""
+
+ LENGTH = 32
+
+ def __call__(self, param):
+ return uuid.UUID(param)
+
+ def complete(self, param):
+ raw = param.replace('-', '')
+ try:
+ val = int(param, 16)
+ except ValueError:
+ return []
+ if len(raw) in (8, 12, 16, 20):
+ return [param + '-']
+ if len(raw) == self.LENGTH:
+ return [param + ' ']
+ return ['']
+
+class List(QMFProperty):
+ """A QMF List property argument"""
+
+ def __call__(self, param):
+ try:
+ l = eval(param, {}, {})
+ except (NameError, SyntaxError):
+ raise ValueError('"%s" is not a valid list' % (param,))
+ if not isinstance(l, list):
+ raise ValueError('"%s" is not a list' % (param,))
+ return l
+
+ def complete(self, param):
+ if not param:
+ return ['[']
+ if not param.startswith('['):
+ return []
+ if param.endswith(']'):
+ try:
+ self(param)
+ except ValueError:
+ return []
+ return [param + ' ']
+ return ['']
+
+class Map(QMFProperty):
+ """A QMF Map property argument"""
+
+ def __call__(self, param):
+ try:
+ l = eval(param, {}, {})
+ except (NameError, SyntaxError):
+ raise ValueError('"%s" is not a valid map' % (param,))
+ if not isinstance(l, dict):
+ raise ValueError('"%s" is not a map' % (param,))
+ return dict((unicode(k), v) for k, v in l.items())
+
+ def complete(self, param):
+ if not param:
+ return ['{']
+ if not param.startswith('{'):
+ return []
+ if param.endswith('}'):
+ try:
+ self(param)
+ except ValueError:
+ return []
+ return [param + ' ']
+ return ['']
+
+_qmfPropertyTypes = {
+ U8: Int,
+ U16: Int,
+ U32: Int,
+ U64: Int,
+ SSTR: String,
+ LSTR: String,
+ BOOL: Bool,
+ FLOAT: Float,
+ DOUBLE: Float,
+ UUID: Uuid,
+ FTABLE: Map,
+ S8: Int,
+ S16: Int,
+ S32: Int,
+ S64: Int,
+ LIST: List,
+ ARRAY: List,
+}