]>
Commit | Line | Data |
---|---|---|
c2d936a4 BP |
1 | #! /usr/bin/python |
2 | ||
be51cd41 | 3 | import getopt |
c2d936a4 BP |
4 | import sys |
5 | import os.path | |
6 | import re | |
be51cd41 BP |
7 | import xml.dom.minidom |
8 | import build.nroff | |
c2d936a4 BP |
9 | |
10 | OFP_ACTION_ALIGN = 8 | |
11 | ||
12 | # Map from OpenFlow version number to version ID used in ofp_header. | |
13 | version_map = {"1.0": 0x01, | |
14 | "1.1": 0x02, | |
15 | "1.2": 0x03, | |
16 | "1.3": 0x04, | |
17 | "1.4": 0x05, | |
18 | "1.5": 0x06} | |
4ab66562 | 19 | version_reverse_map = dict((v, k) for (k, v) in version_map.items()) |
c2d936a4 BP |
20 | |
21 | # Map from vendor name to the length of the action header. | |
22 | vendor_map = {"OF": (0x00000000, 4), | |
914624f8 | 23 | "ONF": (0x4f4e4600, 10), |
c2d936a4 BP |
24 | "NX": (0x00002320, 10)} |
25 | ||
26 | # Basic types used in action arguments. | |
27 | types = {} | |
28 | types['uint8_t'] = {"size": 1, "align": 1, "ntoh": None, "hton": None} | |
29 | types['ovs_be16'] = {"size": 2, "align": 2, "ntoh": "ntohs", "hton": "htons"} | |
30 | types['ovs_be32'] = {"size": 4, "align": 4, "ntoh": "ntohl", "hton": "htonl"} | |
31 | types['ovs_be64'] = {"size": 8, "align": 8, "ntoh": "ntohll", "hton": "htonll"} | |
32 | types['uint16_t'] = {"size": 2, "align": 2, "ntoh": None, "hton": None} | |
33 | types['uint32_t'] = {"size": 4, "align": 4, "ntoh": None, "hton": None} | |
34 | types['uint64_t'] = {"size": 8, "align": 8, "ntoh": None, "hton": None} | |
35 | ||
36 | line = "" | |
37 | ||
38 | arg_structs = set() | |
39 | ||
40 | def round_up(x, y): | |
3fa5aa42 | 41 | return int((x + (y - 1)) / y) * y |
c2d936a4 BP |
42 | |
43 | def open_file(fn): | |
44 | global file_name | |
45 | global input_file | |
46 | global line_number | |
47 | file_name = fn | |
48 | input_file = open(file_name) | |
49 | line_number = 0 | |
50 | ||
51 | def get_line(): | |
52 | global input_file | |
53 | global line | |
54 | global line_number | |
55 | line = input_file.readline() | |
56 | line_number += 1 | |
57 | if line == "": | |
58 | fatal("unexpected end of input") | |
59 | return line | |
60 | ||
61 | n_errors = 0 | |
62 | def error(msg): | |
63 | global n_errors | |
64 | sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg)) | |
65 | n_errors += 1 | |
66 | ||
67 | def fatal(msg): | |
68 | error(msg) | |
69 | sys.exit(1) | |
70 | ||
71 | def usage(): | |
72 | argv0 = os.path.basename(sys.argv[0]) | |
d34a1cc0 | 73 | print('''\ |
c2d936a4 | 74 | %(argv0)s, for extracting OpenFlow action data |
be51cd41 BP |
75 | usage: %(argv0)s [prototypes | definitions] OFP-ACTIONS.c |
76 | usage: %(argv0)s ovs-actions OVS-ACTIONS.XML | |
c2d936a4 | 77 | |
be51cd41 | 78 | Commands: |
c2d936a4 | 79 | |
be51cd41 BP |
80 | prototypes OFP-ACTIONS.C |
81 | Reads ofp-actions.c and prints a set of prototypes to #include early in | |
82 | ofp-actions.c. | |
83 | ||
84 | definitions OFP-ACTIONS.C | |
85 | Reads ofp-actions.c and prints a set of definitions to #include late in | |
86 | ofp-actions.c. | |
87 | ||
88 | ovs-actions OVS-ACTIONS.XML | |
89 | Reads ovs-actions.xml and prints documentation in troff format.\ | |
c2d936a4 BP |
90 | ''' % {"argv0": argv0}) |
91 | sys.exit(0) | |
92 | ||
93 | def extract_ofp_actions(fn, definitions): | |
94 | error_types = {} | |
95 | ||
96 | comments = [] | |
97 | names = [] | |
98 | domain = {} | |
99 | for code, size in vendor_map.values(): | |
100 | domain[code] = {} | |
101 | enums = {} | |
102 | ||
103 | n_errors = 0 | |
104 | ||
105 | open_file(fn) | |
106 | ||
107 | while True: | |
108 | get_line() | |
109 | if re.match('enum ofp_raw_action_type {', line): | |
110 | break | |
111 | ||
112 | while True: | |
113 | get_line() | |
114 | if line.startswith('/*') or not line or line.isspace(): | |
115 | continue | |
116 | elif re.match('}', line): | |
117 | break | |
118 | ||
119 | if not line.lstrip().startswith('/*'): | |
120 | fatal("unexpected syntax between actions") | |
121 | ||
122 | comment = line.lstrip()[2:].strip() | |
123 | while not comment.endswith('*/'): | |
124 | get_line() | |
125 | if line.startswith('/*') or not line or line.isspace(): | |
126 | fatal("unexpected syntax within action") | |
127 | comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n') | |
128 | comment = re.sub('\[[^]]*\]', '', comment) | |
129 | comment = comment[:-2].rstrip() | |
130 | ||
131 | m = re.match('([^:]+):\s+(.*)$', comment) | |
132 | if not m: | |
133 | fatal("unexpected syntax between actions") | |
134 | ||
135 | dsts = m.group(1) | |
04f48a68 YHW |
136 | argtypes = m.group(2).strip().replace('.', '', 1) |
137 | ||
138 | if 'VLMFF' in argtypes: | |
139 | arg_vl_mff_map = True | |
140 | else: | |
141 | arg_vl_mff_map = False | |
142 | argtype = argtypes.replace('VLMFF', '', 1).rstrip() | |
c2d936a4 BP |
143 | |
144 | get_line() | |
145 | m = re.match(r'\s+(([A-Z]+)_RAW([0-9]*)_([A-Z0-9_]+)),?', line) | |
146 | if not m: | |
147 | fatal("syntax error expecting enum value") | |
148 | ||
149 | enum = m.group(1) | |
150 | if enum in names: | |
151 | fatal("%s specified twice" % enum) | |
152 | ||
153 | names.append(enum) | |
154 | ||
155 | for dst in dsts.split(', '): | |
156 | m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?(?:\((\d+)\))(?: is deprecated \(([^)]+)\))?$', dst) | |
157 | if not m: | |
158 | fatal("%r: syntax error in destination" % dst) | |
159 | vendor_name = m.group(1) | |
160 | version1_name = m.group(2) | |
161 | version2_name = m.group(3) | |
162 | type_ = int(m.group(4)) | |
163 | deprecation = m.group(5) | |
164 | ||
165 | if vendor_name not in vendor_map: | |
166 | fatal("%s: unknown vendor" % vendor_name) | |
167 | vendor = vendor_map[vendor_name][0] | |
168 | ||
169 | if version1_name not in version_map: | |
170 | fatal("%s: unknown OpenFlow version" % version1_name) | |
171 | v1 = version_map[version1_name] | |
172 | ||
173 | if version2_name is None: | |
174 | v2 = v1 | |
175 | elif version2_name == "+": | |
176 | v2 = max(version_map.values()) | |
177 | elif version2_name[1:] not in version_map: | |
178 | fatal("%s: unknown OpenFlow version" % version2_name[1:]) | |
179 | else: | |
180 | v2 = version_map[version2_name[1:]] | |
181 | ||
182 | if v2 < v1: | |
183 | fatal("%s%s: %s precedes %s" | |
184 | % (version1_name, version2_name, | |
185 | version2_name, version1_name)) | |
186 | ||
187 | for version in range(v1, v2 + 1): | |
188 | domain[vendor].setdefault(type_, {}) | |
189 | if version in domain[vendor][type_]: | |
190 | v = domain[vendor][type_][version] | |
191 | msg = "%#x,%d in OF%s means both %s and %s" % ( | |
192 | vendor, type_, version_reverse_map[version], | |
193 | v["enum"], enum) | |
194 | error("%s: %s." % (dst, msg)) | |
195 | sys.stderr.write("%s:%d: %s: Here is the location " | |
196 | "of the previous definition.\n" | |
197 | % (v["file_name"], v["line_number"], | |
198 | dst)) | |
199 | n_errors += 1 | |
200 | else: | |
201 | header_len = vendor_map[vendor_name][1] | |
202 | ||
203 | base_argtype = argtype.replace(', ..', '', 1) | |
204 | if base_argtype in types: | |
205 | arg_align = types[base_argtype]['align'] | |
206 | arg_len = types[base_argtype]['size'] | |
207 | arg_ofs = round_up(header_len, arg_align) | |
208 | min_length = round_up(arg_ofs + arg_len, | |
209 | OFP_ACTION_ALIGN) | |
210 | elif base_argtype == 'void': | |
211 | min_length = round_up(header_len, OFP_ACTION_ALIGN) | |
212 | arg_len = 0 | |
213 | arg_ofs = 0 | |
214 | elif re.match(r'struct [a-zA-Z0-9_]+$', base_argtype): | |
215 | min_length = 'sizeof(%s)' % base_argtype | |
216 | arg_structs.add(base_argtype) | |
217 | arg_len = 0 | |
218 | arg_ofs = 0 | |
219 | # should also emit OFP_ACTION_ALIGN assertion | |
220 | else: | |
221 | fatal("bad argument type %s" % argtype) | |
222 | ||
223 | ellipsis = argtype != base_argtype | |
224 | if ellipsis: | |
225 | max_length = '65536 - OFP_ACTION_ALIGN' | |
226 | else: | |
227 | max_length = min_length | |
228 | ||
04f48a68 YHW |
229 | info = {"enum": enum, # 0 |
230 | "deprecation": deprecation, # 1 | |
231 | "file_name": file_name, # 2 | |
232 | "line_number": line_number, # 3 | |
233 | "min_length": min_length, # 4 | |
234 | "max_length": max_length, # 5 | |
235 | "arg_ofs": arg_ofs, # 6 | |
236 | "arg_len": arg_len, # 7 | |
237 | "base_argtype": base_argtype, # 8 | |
238 | "arg_vl_mff_map": arg_vl_mff_map, # 9 | |
239 | "version": version, # 10 | |
240 | "type": type_} # 11 | |
c2d936a4 BP |
241 | domain[vendor][type_][version] = info |
242 | ||
243 | enums.setdefault(enum, []) | |
244 | enums[enum].append(info) | |
245 | ||
246 | input_file.close() | |
247 | ||
248 | if n_errors: | |
249 | sys.exit(1) | |
250 | ||
d34a1cc0 | 251 | print("""\ |
c2d936a4 | 252 | /* Generated automatically; do not modify! -*- buffer-read-only: t -*- */ |
d34a1cc0 | 253 | """) |
c2d936a4 BP |
254 | |
255 | if definitions: | |
d34a1cc0 | 256 | print("/* Verify that structs used as actions are reasonable sizes. */") |
c2d936a4 | 257 | for s in sorted(arg_structs): |
d34a1cc0 | 258 | print("BUILD_ASSERT_DECL(sizeof(%s) %% OFP_ACTION_ALIGN == 0);" % s) |
c2d936a4 | 259 | |
d34a1cc0 | 260 | print("\nstatic struct ofpact_raw_instance all_raw_instances[] = {") |
c2d936a4 BP |
261 | for vendor in domain: |
262 | for type_ in domain[vendor]: | |
263 | for version in domain[vendor][type_]: | |
264 | d = domain[vendor][type_][version] | |
d34a1cc0 JW |
265 | print(" { { 0x%08x, %2d, 0x%02x }, " % ( |
266 | vendor, type_, version)) | |
267 | print(" %s," % d["enum"]) | |
268 | print(" HMAP_NODE_NULL_INITIALIZER,") | |
269 | print(" HMAP_NODE_NULL_INITIALIZER,") | |
270 | print(" %s," % d["min_length"]) | |
271 | print(" %s," % d["max_length"]) | |
272 | print(" %s," % d["arg_ofs"]) | |
273 | print(" %s," % d["arg_len"]) | |
274 | print(" \"%s\"," % re.sub('_RAW[0-9]*', '', d["enum"], 1)) | |
c2d936a4 | 275 | if d["deprecation"]: |
d34a1cc0 | 276 | print(" \"%s\"," % re.sub(r'(["\\])', r'\\\1', d["deprecation"])) |
c2d936a4 | 277 | else: |
d34a1cc0 JW |
278 | print(" NULL,") |
279 | print(" },") | |
280 | print("};") | |
c2d936a4 BP |
281 | |
282 | for versions in enums.values(): | |
283 | need_ofp_version = False | |
284 | for v in versions: | |
285 | assert v["arg_len"] == versions[0]["arg_len"] | |
286 | assert v["base_argtype"] == versions[0]["base_argtype"] | |
287 | if (v["min_length"] != versions[0]["min_length"] or | |
288 | v["arg_ofs"] != versions[0]["arg_ofs"] or | |
289 | v["type"] != versions[0]["type"]): | |
290 | need_ofp_version = True | |
291 | base_argtype = versions[0]["base_argtype"] | |
292 | ||
293 | decl = "static inline " | |
294 | if base_argtype.startswith('struct'): | |
295 | decl += "%s *" %base_argtype | |
296 | else: | |
297 | decl += "void" | |
298 | decl += "\nput_%s(struct ofpbuf *openflow" % versions[0]["enum"].replace('_RAW', '', 1) | |
299 | if need_ofp_version: | |
300 | decl += ", enum ofp_version version" | |
301 | if base_argtype != 'void' and not base_argtype.startswith('struct'): | |
302 | decl += ", %s arg" % base_argtype | |
303 | decl += ")" | |
304 | if definitions: | |
305 | decl += "{\n" | |
306 | decl += " " | |
307 | if base_argtype.startswith('struct'): | |
308 | decl += "return " | |
309 | decl += "ofpact_put_raw(openflow, " | |
310 | if need_ofp_version: | |
311 | decl += "version" | |
312 | else: | |
313 | decl += "%s" % versions[0]["version"] | |
314 | decl += ", %s, " % versions[0]["enum"] | |
315 | if base_argtype.startswith('struct') or base_argtype == 'void': | |
316 | decl += "0" | |
317 | else: | |
318 | ntoh = types[base_argtype]['ntoh'] | |
319 | if ntoh: | |
320 | decl += "%s(arg)" % ntoh | |
321 | else: | |
322 | decl += "arg" | |
323 | decl += ");\n" | |
324 | decl += "}" | |
325 | else: | |
326 | decl += ";" | |
d34a1cc0 JW |
327 | print(decl) |
328 | print("") | |
c2d936a4 BP |
329 | |
330 | if definitions: | |
d34a1cc0 | 331 | print("""\ |
c2d936a4 BP |
332 | static enum ofperr |
333 | ofpact_decode(const struct ofp_action_header *a, enum ofp_raw_action_type raw, | |
04f48a68 | 334 | enum ofp_version version, uint64_t arg, |
5c7c16d8 YHW |
335 | const struct vl_mff_map *vl_mff_map, |
336 | uint64_t *tlv_bitmap, struct ofpbuf *out) | |
c2d936a4 BP |
337 | { |
338 | switch (raw) {\ | |
d34a1cc0 | 339 | """) |
c2d936a4 BP |
340 | for versions in enums.values(): |
341 | enum = versions[0]["enum"] | |
d34a1cc0 | 342 | print(" case %s:" % enum) |
c2d936a4 | 343 | base_argtype = versions[0]["base_argtype"] |
04f48a68 | 344 | arg_vl_mff_map = versions[0]["arg_vl_mff_map"] |
c2d936a4 | 345 | if base_argtype == 'void': |
d34a1cc0 | 346 | print(" return decode_%s(out);" % enum) |
c2d936a4 BP |
347 | else: |
348 | if base_argtype.startswith('struct'): | |
349 | arg = "ALIGNED_CAST(const %s *, a)" % base_argtype | |
350 | else: | |
351 | hton = types[base_argtype]['hton'] | |
352 | if hton: | |
353 | arg = "%s(arg)" % hton | |
354 | else: | |
355 | arg = "arg" | |
04f48a68 | 356 | if arg_vl_mff_map: |
d34a1cc0 | 357 | print(" return decode_%s(%s, version, vl_mff_map, tlv_bitmap, out);" % (enum, arg)) |
04f48a68 | 358 | else: |
d34a1cc0 JW |
359 | print(" return decode_%s(%s, version, out);" % (enum, arg)) |
360 | print("") | |
361 | print("""\ | |
c2d936a4 BP |
362 | default: |
363 | OVS_NOT_REACHED(); | |
364 | } | |
365 | }\ | |
d34a1cc0 | 366 | """) |
c2d936a4 BP |
367 | else: |
368 | for versions in enums.values(): | |
369 | enum = versions[0]["enum"] | |
370 | prototype = "static enum ofperr decode_%s(" % enum | |
371 | base_argtype = versions[0]["base_argtype"] | |
04f48a68 | 372 | arg_vl_mff_map = versions[0]["arg_vl_mff_map"] |
c2d936a4 BP |
373 | if base_argtype != 'void': |
374 | if base_argtype.startswith('struct'): | |
f3cd3ac7 | 375 | prototype += "const %s *, enum ofp_version, " % base_argtype |
c2d936a4 | 376 | else: |
f3cd3ac7 | 377 | prototype += "%s, enum ofp_version, " % base_argtype |
04f48a68 | 378 | if arg_vl_mff_map: |
5c7c16d8 | 379 | prototype += 'const struct vl_mff_map *, uint64_t *, ' |
c2d936a4 | 380 | prototype += "struct ofpbuf *);" |
d34a1cc0 | 381 | print(prototype) |
c2d936a4 | 382 | |
d34a1cc0 | 383 | print(""" |
c2d936a4 BP |
384 | static enum ofperr ofpact_decode(const struct ofp_action_header *, |
385 | enum ofp_raw_action_type raw, | |
f3cd3ac7 | 386 | enum ofp_version version, |
04f48a68 | 387 | uint64_t arg, const struct vl_mff_map *vl_mff_map, |
5c7c16d8 | 388 | uint64_t *tlv_bitmap, struct ofpbuf *out); |
d34a1cc0 | 389 | """) |
be51cd41 BP |
390 | \f |
391 | ## ------------------------ ## | |
392 | ## Documentation Generation ## | |
393 | ## ------------------------ ## | |
394 | ||
395 | def action_to_xml(action_node, body): | |
396 | syntax = 0 | |
397 | for node in action_node.childNodes: | |
398 | if node.nodeType == node.ELEMENT_NODE and node.tagName == 'syntax': | |
399 | if body[-1].strip() == '.PP': | |
400 | del body[-1] | |
401 | if syntax: | |
402 | body += ['.IQ\n'] | |
403 | else: | |
404 | body += ['.IP "\\fBSyntax:\\fR"\n'] | |
405 | body += [build.nroff.inline_xml_to_nroff(x, r'\fR') | |
406 | for x in node.childNodes] + ['\n'] | |
407 | syntax += 1 | |
408 | elif (node.nodeType == node.ELEMENT_NODE | |
409 | and node.tagName == 'conformance'): | |
410 | body += ['.IP "\\fBConformance:\\fR"\n'] | |
411 | body += [build.nroff.block_xml_to_nroff(node.childNodes)] | |
412 | else: | |
413 | body += [build.nroff.block_xml_to_nroff([node])] | |
414 | ||
415 | def group_xml_to_nroff(group_node): | |
416 | title = group_node.attributes['title'].nodeValue | |
417 | ||
418 | body = [] | |
419 | for node in group_node.childNodes: | |
420 | if node.nodeType == node.ELEMENT_NODE and node.tagName == 'action': | |
421 | action_to_xml(node, body) | |
422 | else: | |
423 | body += [build.nroff.block_xml_to_nroff([node])] | |
424 | ||
425 | content = [ | |
426 | '.bp\n', | |
427 | '.SH \"%s\"\n' % build.nroff.text_to_nroff(title.upper())] | |
428 | content += body | |
429 | return ''.join(content) | |
430 | ||
431 | def make_ovs_actions(ovs_actions_xml): | |
432 | document = xml.dom.minidom.parse(ovs_actions_xml) | |
433 | doc = document.documentElement | |
434 | ||
435 | global version | |
436 | if version == None: | |
437 | version = "UNKNOWN" | |
438 | ||
439 | print('''\ | |
440 | '\\" tp | |
441 | .\\" -*- mode: troff; coding: utf-8 -*- | |
442 | .TH "ovs\-actions" 7 "%s" "Open vSwitch" "Open vSwitch Manual" | |
443 | .fp 5 L CR \\" Make fixed-width font available as \\fL. | |
444 | .de ST | |
445 | . PP | |
446 | . RS -0.15in | |
447 | . I "\\\\$1" | |
448 | . RE | |
449 | .. | |
450 | ||
451 | .de SU | |
452 | . PP | |
453 | . I "\\\\$1" | |
454 | .. | |
455 | ||
456 | .de IQ | |
457 | . br | |
458 | . ns | |
459 | . IP "\\\\$1" | |
460 | .. | |
461 | ||
462 | .de TQ | |
463 | . br | |
464 | . ns | |
465 | . TP "\\\\$1" | |
466 | .. | |
467 | .de URL | |
468 | \\\\$2 \\(laURL: \\\\$1 \\(ra\\\\$3 | |
469 | .. | |
470 | .if \\n[.g] .mso www.tmac | |
471 | .SH NAME | |
472 | ovs\-actions \- OpenFlow actions and instructions with Open vSwitch extensions | |
473 | . | |
474 | .PP | |
475 | ''' % version) | |
476 | ||
477 | s = '' | |
478 | for node in doc.childNodes: | |
479 | if node.nodeType == node.ELEMENT_NODE and node.tagName == "group": | |
480 | s += group_xml_to_nroff(node) | |
481 | elif node.nodeType == node.TEXT_NODE: | |
482 | assert node.data.isspace() | |
483 | elif node.nodeType == node.COMMENT_NODE: | |
484 | pass | |
485 | else: | |
486 | s += build.nroff.block_xml_to_nroff([node]) | |
487 | ||
488 | if n_errors: | |
489 | sys.exit(1) | |
490 | ||
491 | output = [] | |
492 | for oline in s.split("\n"): | |
493 | oline = oline.strip() | |
494 | ||
495 | # Life is easier with nroff if we don't try to feed it Unicode. | |
496 | # Fortunately, we only use a few characters outside the ASCII range. | |
497 | oline = oline.replace(u'\u2208', r'\[mo]') | |
498 | oline = oline.replace(u'\u2260', r'\[!=]') | |
499 | oline = oline.replace(u'\u2264', r'\[<=]') | |
500 | oline = oline.replace(u'\u2265', r'\[>=]') | |
501 | oline = oline.replace(u'\u00d7', r'\[mu]') | |
502 | if len(oline): | |
503 | output += [oline] | |
504 | ||
505 | # nroff tends to ignore .bp requests if they come after .PP requests, | |
506 | # so remove .PPs that precede .bp. | |
507 | for i in range(len(output)): | |
508 | if output[i] == '.bp': | |
509 | j = i - 1 | |
510 | while j >= 0 and output[j] == '.PP': | |
511 | output[j] = None | |
512 | j -= 1 | |
513 | for i in range(len(output)): | |
514 | if output[i] is not None: | |
515 | print(output[i]) | |
516 | ||
517 | \f | |
518 | ## ------------ ## | |
519 | ## Main Program ## | |
520 | ## ------------ ## | |
c2d936a4 BP |
521 | |
522 | if __name__ == '__main__': | |
be51cd41 BP |
523 | argv0 = sys.argv[0] |
524 | try: | |
525 | options, args = getopt.gnu_getopt(sys.argv[1:], 'h', | |
526 | ['help', 'ovs-version=']) | |
527 | except getopt.GetoptError as geo: | |
528 | sys.stderr.write("%s: %s\n" % (argv0, geo.msg)) | |
c2d936a4 | 529 | sys.exit(1) |
be51cd41 BP |
530 | |
531 | global version | |
532 | version = None | |
533 | for key, value in options: | |
534 | if key in ['-h', '--help']: | |
535 | usage() | |
536 | elif key == '--ovs-version': | |
537 | version = value | |
538 | else: | |
539 | sys.exit(0) | |
540 | ||
541 | if not args: | |
542 | sys.stderr.write("%s: missing command argument " | |
543 | "(use --help for help)\n" % argv0) | |
544 | sys.exit(1) | |
545 | ||
546 | commands = {"prototypes": (lambda fn: extract_ofp_actions(fn, False), 1), | |
547 | "definitions": (lambda fn: extract_ofp_actions(fn, True), 1), | |
548 | "ovs-actions": (make_ovs_actions, 1)} | |
549 | ||
550 | if not args[0] in commands: | |
551 | sys.stderr.write("%s: unknown command \"%s\" " | |
552 | "(use --help for help)\n" % (argv0, args[0])) | |
553 | sys.exit(1) | |
554 | ||
555 | func, n_args = commands[args[0]] | |
556 | if len(args) - 1 != n_args: | |
557 | sys.stderr.write("%s: \"%s\" requires %d arguments but %d " | |
558 | "provided\n" | |
559 | % (argv0, args[0], n_args, len(args) - 1)) | |
c2d936a4 BP |
560 | sys.exit(1) |
561 | ||
be51cd41 | 562 | func(*args[1:]) |