8104b243b9071d9258f4fd408e755bfc648a5731
[sfa.git] / sfatables / commands / moo.py
1 import os, time
2
3
4 class Command:
5     commandline_options = []
6     help = "Add a new rule"
7
8     def __init__(self):
9         if (len(commandline_options!=2)):
10             raise Exception("Internal error: each command must supply 2 command line options")
11
12                 
13     def __call__(self, option, opt_str, value, parser, *args, **kwargs):
14         return True
15
16         
17
18     def help(self, indent = "  "):
19         """
20         Text documentation for the method.
21         """
22
23         (min_args, max_args, defaults) = self.args()
24
25         text = "%s(%s) -> %s\n\n" % (self.name, ", ".join(max_args), xmlrpc_type(self.returns))
26
27         text += "Description:\n\n"
28         lines = [indent + line.strip() for line in self.__doc__.strip().split("\n")]
29         text += "\n".join(lines) + "\n\n"
30
31         def param_text(name, param, indent, step):
32             """
33             Format a method parameter.
34             """
35
36             text = indent
37
38             # Print parameter name
39             if name:
40                 param_offset = 32
41                 text += name.ljust(param_offset - len(indent))
42             else:
43                 param_offset = len(indent)
44
45             # Print parameter type
46             param_type = python_type(param)
47             text += xmlrpc_type(param_type) + "\n"
48
49             # Print parameter documentation right below type
50             if isinstance(param, Parameter):
51                 wrapper = textwrap.TextWrapper(width = 70,
52                                                initial_indent = " " * param_offset,
53                                                subsequent_indent = " " * param_offset)
54                 text += "\n".join(wrapper.wrap(param.doc)) + "\n"
55                 param = param.type
56
57             text += "\n"
58
59             # Indent struct fields and mixed types
60             if isinstance(param, dict):
61                 for name, subparam in param.items():
62                     text += param_text(name, subparam, indent + step, step)
63             elif isinstance(param, Mixed):
64                 for subparam in param:
65                     text += param_text(name, subparam, indent + step, step)
66             elif isinstance(param, (list, tuple, set)):
67                 for subparam in param:
68                     text += param_text("", subparam, indent + step, step)
69
70             return text
71
72         text += "Parameters:\n\n"
73         for name, param in zip(max_args, self.accepts):
74             text += param_text(name, param, indent, indent)
75
76         text += "Returns:\n\n"
77         text += param_text("", self.returns, indent, indent)
78
79         return text
80
81     def args(self):
82         """
83         Returns a tuple:
84
85         ((arg1_name, arg2_name, ...),
86          (arg1_name, arg2_name, ..., optional1_name, optional2_name, ...),
87          (None, None, ..., optional1_default, optional2_default, ...))
88
89         That represents the minimum and maximum sets of arguments that
90         this function accepts and the defaults for the optional arguments.
91         """
92         
93         # Inspect call. Remove self from the argument list.
94         max_args = self.call.__code__.co_varnames[1:self.call.__code__.co_argcount]
95         defaults = self.call.__defaults__
96         if defaults is None:
97             defaults = ()
98
99         min_args = max_args[0:len(max_args) - len(defaults)]
100         defaults = tuple([None for arg in min_args]) + defaults
101         
102         return (min_args, max_args, defaults)
103
104     def type_check(self, name, value, expected, args):
105         """
106         Checks the type of the named value against the expected type,
107         which may be a Python type, a typed value, a Parameter, a
108         Mixed type, or a list or dictionary of possibly mixed types,
109         values, Parameters, or Mixed types.
110         
111         Extraneous members of lists must be of the same type as the
112         last specified type. For example, if the expected argument
113         type is [int, bool], then [1, False] and [14, True, False,
114         True] are valid, but [1], [False, 1] and [14, True, 1] are
115         not.
116
117         Extraneous members of dictionaries are ignored.
118         """
119
120         # If any of a number of types is acceptable
121         if isinstance(expected, Mixed):
122             for item in expected:
123                 try:
124                     self.type_check(name, value, item, args)
125                     return
126                 except SfaInvalidArgument as fault:
127                     pass
128             raise fault
129
130         # If an authentication structure is expected, save it and
131         # authenticate after basic type checking is done.
132         #if isinstance(expected, Auth):
133         #    auth = expected
134         #else:
135         #    auth = None
136
137         # Get actual expected type from within the Parameter structure
138         if isinstance(expected, Parameter):
139             min = expected.min
140             max = expected.max
141             nullok = expected.nullok
142             expected = expected.type
143         else:
144             min = None
145             max = None
146             nullok = False
147
148         expected_type = python_type(expected)
149
150         # If value can be NULL
151         if value is None and nullok:
152             return
153
154         # Strings are a special case. Accept either unicode or str
155         # types if a string is expected.
156         if issubclass(expected_type, str) and isinstance(value, str):
157             pass
158
159         # Integers and long integers are also special types. Accept
160         # either int or long types if an int or long is expected.
161         elif expected_type in (IntType, LongType) and isinstance(value, (IntType, LongType)):
162             pass
163
164         elif not isinstance(value, expected_type):
165             raise SfaInvalidArgument("expected %s, got %s" % \
166                                      (xmlrpc_type(expected_type),
167                                       xmlrpc_type(type(value))),
168                                      name)
169
170         # If a minimum or maximum (length, value) has been specified
171         if issubclass(expected_type, str):
172             if min is not None and \
173                len(value.encode(self.api.encoding)) < min:
174                 raise SfaInvalidArgument("%s must be at least %d bytes long" % (name, min))
175             if max is not None and \
176                len(value.encode(self.api.encoding)) > max:
177                 raise SfaInvalidArgument("%s must be at most %d bytes long" % (name, max))
178         elif expected_type in (list, tuple, set):
179             if min is not None and len(value) < min:
180                 raise SfaInvalidArgument("%s must contain at least %d items" % (name, min))
181             if max is not None and len(value) > max:
182                 raise SfaInvalidArgument("%s must contain at most %d items" % (name, max))
183         else:
184             if min is not None and value < min:
185                 raise SfaInvalidArgument("%s must be > %s" % (name, str(min)))
186             if max is not None and value > max:
187                 raise SfaInvalidArgument("%s must be < %s" % (name, str(max)))
188
189         # If a list with particular types of items is expected
190         if isinstance(expected, (list, tuple, set)):
191             for i in range(len(value)):
192                 if i >= len(expected):
193                     j = len(expected) - 1
194                 else:
195                     j = i
196                 self.type_check(name + "[]", value[i], expected[j], args)
197
198         # If a struct with particular (or required) types of items is
199         # expected.
200         elif isinstance(expected, dict):
201             for key in list(value.keys()):
202                 if key in expected:
203                     self.type_check(name + "['%s']" % key, value[key], expected[key], args)
204             for key, subparam in expected.items():
205                 if isinstance(subparam, Parameter) and \
206                    subparam.optional is not None and \
207                    not subparam.optional and key not in list(value.keys()):
208                     raise SfaInvalidArgument("'%s' not specified" % key, name)
209
210         #if auth is not None:
211         #    auth.check(self, *args)