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