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