| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 """ | 2 """ |
| 3 KVM test configuration file parser | 3 KVM test configuration file parser |
| 4 | 4 |
| 5 @copyright: Red Hat 2008-2011 | 5 @copyright: Red Hat 2008-2011 |
| 6 """ | 6 """ |
| 7 | 7 |
| 8 import re, os, sys, optparse, collections, string | 8 import re, os, sys, optparse, collections |
| 9 | 9 |
| 10 | 10 |
| 11 # Filter syntax: | 11 # Filter syntax: |
| 12 # , means OR | 12 # , means OR |
| 13 # .. means AND | 13 # .. means AND |
| 14 # . means IMMEDIATELY-FOLLOWED-BY | 14 # . means IMMEDIATELY-FOLLOWED-BY |
| 15 | 15 |
| 16 # Example: | 16 # Example: |
| 17 # qcow2..Fedora.14, RHEL.6..raw..boot, smp2..qcow2..migrate..ide | 17 # qcow2..Fedora.14, RHEL.6..raw..boot, smp2..qcow2..migrate..ide |
| 18 # means match all dicts whose names have: | 18 # means match all dicts whose names have: |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 130 return False | 130 return False |
| 131 | 131 |
| 132 | 132 |
| 133 class NoOnlyFilter(Filter): | 133 class NoOnlyFilter(Filter): |
| 134 def __init__(self, line): | 134 def __init__(self, line): |
| 135 Filter.__init__(self, line.split(None, 1)[1]) | 135 Filter.__init__(self, line.split(None, 1)[1]) |
| 136 self.line = line | 136 self.line = line |
| 137 | 137 |
| 138 | 138 |
| 139 class OnlyFilter(NoOnlyFilter): | 139 class OnlyFilter(NoOnlyFilter): |
| 140 def is_irrelevant(self, ctx, ctx_set, descendant_labels): |
| 141 return self.match(ctx, ctx_set) |
| 142 |
| 143 |
| 144 def requires_action(self, ctx, ctx_set, descendant_labels): |
| 145 return not self.might_match(ctx, ctx_set, descendant_labels) |
| 146 |
| 147 |
| 140 def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set, | 148 def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set, |
| 141 descendant_labels): | 149 descendant_labels): |
| 142 for word in self.filter: | 150 for word in self.filter: |
| 143 for block in word: | 151 for block in word: |
| 144 if (_match_adjacent(block, ctx, ctx_set) > | 152 if (_match_adjacent(block, ctx, ctx_set) > |
| 145 _match_adjacent(block, failed_ctx, failed_ctx_set)): | 153 _match_adjacent(block, failed_ctx, failed_ctx_set)): |
| 146 return self.might_match(ctx, ctx_set, descendant_labels) | 154 return self.might_match(ctx, ctx_set, descendant_labels) |
| 147 return False | 155 return False |
| 148 | 156 |
| 149 | 157 |
| 150 class NoFilter(NoOnlyFilter): | 158 class NoFilter(NoOnlyFilter): |
| 159 def is_irrelevant(self, ctx, ctx_set, descendant_labels): |
| 160 return not self.might_match(ctx, ctx_set, descendant_labels) |
| 161 |
| 162 |
| 163 def requires_action(self, ctx, ctx_set, descendant_labels): |
| 164 return self.match(ctx, ctx_set) |
| 165 |
| 166 |
| 151 def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set, | 167 def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set, |
| 152 descendant_labels): | 168 descendant_labels): |
| 153 for word in self.filter: | 169 for word in self.filter: |
| 154 for block in word: | 170 for block in word: |
| 155 if (_match_adjacent(block, ctx, ctx_set) < | 171 if (_match_adjacent(block, ctx, ctx_set) < |
| 156 _match_adjacent(block, failed_ctx, failed_ctx_set)): | 172 _match_adjacent(block, failed_ctx, failed_ctx_set)): |
| 157 return not self.match(ctx, ctx_set) | 173 return not self.match(ctx, ctx_set) |
| 158 return False | 174 return False |
| 159 | 175 |
| 160 | 176 |
| 161 class Condition(NoFilter): | 177 class Condition(NoFilter): |
| 162 def __init__(self, line): | 178 def __init__(self, line): |
| 163 Filter.__init__(self, line.rstrip(":")) | 179 Filter.__init__(self, line.rstrip(":")) |
| 164 self.line = line | 180 self.line = line |
| 165 self.content = [] | 181 self.content = [] |
| 166 | 182 |
| 167 | 183 |
| 184 class NegativeCondition(OnlyFilter): |
| 185 def __init__(self, line): |
| 186 Filter.__init__(self, line.lstrip("!").rstrip(":")) |
| 187 self.line = line |
| 188 self.content = [] |
| 189 |
| 190 |
| 168 class Parser(object): | 191 class Parser(object): |
| 169 """ | 192 """ |
| 170 Parse an input file or string that follows the KVM Test Config File format | 193 Parse an input file or string that follows the KVM Test Config File format |
| 171 and generate a list of dicts that will be later used as configuration | 194 and generate a list of dicts that will be later used as configuration |
| 172 parameters by the KVM tests. | 195 parameters by the KVM tests. |
| 173 | 196 |
| 174 @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File | 197 @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File |
| 175 """ | 198 """ |
| 176 | 199 |
| 177 def __init__(self, filename=None, debug=False): | 200 def __init__(self, filename=None, debug=False): |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 219 # new_content and unpack conditional blocks if appropriate. | 242 # new_content and unpack conditional blocks if appropriate. |
| 220 # For example, if an 'only' statement fully matches ctx, it | 243 # For example, if an 'only' statement fully matches ctx, it |
| 221 # becomes irrelevant and is not appended to new_content. | 244 # becomes irrelevant and is not appended to new_content. |
| 222 # If a conditional block fully matches, its contents are | 245 # If a conditional block fully matches, its contents are |
| 223 # unpacked into new_content. | 246 # unpacked into new_content. |
| 224 # 3. Move failed filters into failed_filters, so that next time we | 247 # 3. Move failed filters into failed_filters, so that next time we |
| 225 # reach this node or one of its ancestors, we'll check those | 248 # reach this node or one of its ancestors, we'll check those |
| 226 # filters first. | 249 # filters first. |
| 227 for t in content: | 250 for t in content: |
| 228 filename, linenum, obj = t | 251 filename, linenum, obj = t |
| 229 if type(obj) is str: | 252 if type(obj) is Op: |
| 230 new_content.append(t) | 253 new_content.append(t) |
| 231 continue | 254 continue |
| 232 elif type(obj) is OnlyFilter: | 255 # obj is an OnlyFilter/NoFilter/Condition/NegativeCondition |
| 233 if not obj.might_match(ctx, ctx_set, labels): | 256 if obj.requires_action(ctx, ctx_set, labels): |
| 257 # This filter requires action now |
| 258 if type(obj) is OnlyFilter or type(obj) is NoFilter: |
| 234 self._debug(" filter did not pass: %r (%s:%s)", | 259 self._debug(" filter did not pass: %r (%s:%s)", |
| 235 obj.line, filename, linenum) | 260 obj.line, filename, linenum) |
| 236 failed_filters.append(t) | 261 failed_filters.append(t) |
| 237 return False | 262 return False |
| 238 elif obj.match(ctx, ctx_set): | 263 else: |
| 239 continue | |
| 240 elif type(obj) is NoFilter: | |
| 241 if obj.match(ctx, ctx_set): | |
| 242 self._debug(" filter did not pass: %r (%s:%s)", | |
| 243 obj.line, filename, linenum) | |
| 244 failed_filters.append(t) | |
| 245 return False | |
| 246 elif not obj.might_match(ctx, ctx_set, labels): | |
| 247 continue | |
| 248 elif type(obj) is Condition: | |
| 249 if obj.match(ctx, ctx_set): | |
| 250 self._debug(" conditional block matches: %r (%s:%s)", | 264 self._debug(" conditional block matches: %r (%s:%s)", |
| 251 obj.line, filename, linenum) | 265 obj.line, filename, linenum) |
| 252 # Check and unpack the content inside this Condition | 266 # Check and unpack the content inside this Condition |
| 253 # object (note: the failed filters should go into | 267 # object (note: the failed filters should go into |
| 254 # new_internal_filters because we don't expect them to | 268 # new_internal_filters because we don't expect them to |
| 255 # come from outside this node, even if the Condition | 269 # come from outside this node, even if the Condition |
| 256 # itself was external) | 270 # itself was external) |
| 257 if not process_content(obj.content, | 271 if not process_content(obj.content, |
| 258 new_internal_filters): | 272 new_internal_filters): |
| 259 failed_filters.append(t) | 273 failed_filters.append(t) |
| 260 return False | 274 return False |
| 261 continue | 275 continue |
| 262 elif not obj.might_match(ctx, ctx_set, labels): | 276 elif obj.is_irrelevant(ctx, ctx_set, labels): |
| 263 continue | 277 # This filter is no longer relevant and can be removed |
| 264 new_content.append(t) | 278 continue |
| 279 else: |
| 280 # Keep the filter and check it again later |
| 281 new_content.append(t) |
| 265 return True | 282 return True |
| 266 | 283 |
| 267 def might_pass(failed_ctx, | 284 def might_pass(failed_ctx, |
| 268 failed_ctx_set, | 285 failed_ctx_set, |
| 269 failed_external_filters, | 286 failed_external_filters, |
| 270 failed_internal_filters): | 287 failed_internal_filters): |
| 271 for t in failed_external_filters: | 288 for t in failed_external_filters: |
| 272 if t not in content: | 289 if t not in content: |
| 273 return True | 290 return True |
| 274 filename, linenum, filter = t | 291 filename, linenum, filter = t |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 323 count = 0 | 340 count = 0 |
| 324 for n in node.children: | 341 for n in node.children: |
| 325 for d in self.get_dicts(n, ctx, new_content, shortname, dep): | 342 for d in self.get_dicts(n, ctx, new_content, shortname, dep): |
| 326 count += 1 | 343 count += 1 |
| 327 yield d | 344 yield d |
| 328 # Reached leaf? | 345 # Reached leaf? |
| 329 if not node.children: | 346 if not node.children: |
| 330 self._debug(" reached leaf, returning it") | 347 self._debug(" reached leaf, returning it") |
| 331 d = {"name": name, "dep": dep, "shortname": ".".join(shortname)} | 348 d = {"name": name, "dep": dep, "shortname": ".".join(shortname)} |
| 332 for filename, linenum, op in new_content: | 349 for filename, linenum, op in new_content: |
| 333 op.apply_to_dict(d, ctx, ctx_set) | 350 op.apply_to_dict(d) |
| 334 yield d | 351 yield d |
| 335 # If this node did not produce any dicts, remember the failed filters | 352 # If this node did not produce any dicts, remember the failed filters |
| 336 # of its descendants | 353 # of its descendants |
| 337 elif not count: | 354 elif not count: |
| 338 new_external_filters = [] | 355 new_external_filters = [] |
| 339 new_internal_filters = [] | 356 new_internal_filters = [] |
| 340 for n in node.children: | 357 for n in node.children: |
| 341 (failed_ctx, | 358 (failed_ctx, |
| 342 failed_ctx_set, | 359 failed_ctx_set, |
| 343 failed_external_filters, | 360 failed_external_filters, |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 422 while True: | 439 while True: |
| 423 line, indent, linenum = cr.get_next_line(prev_indent) | 440 line, indent, linenum = cr.get_next_line(prev_indent) |
| 424 if not line: | 441 if not line: |
| 425 break | 442 break |
| 426 | 443 |
| 427 words = line.split(None, 1) | 444 words = line.split(None, 1) |
| 428 | 445 |
| 429 # Parse 'variants' | 446 # Parse 'variants' |
| 430 if line == "variants:": | 447 if line == "variants:": |
| 431 # 'variants' is not allowed inside a conditional block | 448 # 'variants' is not allowed inside a conditional block |
| 432 if isinstance(node, Condition): | 449 if (isinstance(node, Condition) or |
| 450 isinstance(node, NegativeCondition)): |
| 433 raise ParserError("'variants' is not allowed inside a " | 451 raise ParserError("'variants' is not allowed inside a " |
| 434 "conditional block", | 452 "conditional block", |
| 435 None, cr.filename, linenum) | 453 None, cr.filename, linenum) |
| 436 node = self._parse_variants(cr, node, prev_indent=indent) | 454 node = self._parse_variants(cr, node, prev_indent=indent) |
| 437 continue | 455 continue |
| 438 | 456 |
| 439 # Parse 'include' statements | 457 # Parse 'include' statements |
| 440 if words[0] == "include": | 458 if words[0] == "include": |
| 441 if len(words) < 2: | 459 if len(words) < 2: |
| 442 raise ParserError("Syntax error: missing parameter", | 460 raise ParserError("Syntax error: missing parameter", |
| 443 line, cr.filename, linenum) | 461 line, cr.filename, linenum) |
| 444 if not isinstance(cr, FileReader): | 462 filename = os.path.expanduser(words[1]) |
| 445 raise ParserError("Cannot include because no file is " | 463 if isinstance(cr, FileReader) and not os.path.isabs(filename): |
| 446 "currently open", | 464 filename = os.path.join(os.path.dirname(cr.filename), |
| 447 line, cr.filename, linenum) | 465 filename) |
| 448 filename = os.path.join(os.path.dirname(cr.filename), words[1]) | |
| 449 if not os.path.isfile(filename): | 466 if not os.path.isfile(filename): |
| 450 self._warn("%r (%s:%s): file doesn't exist or is not a " | 467 self._warn("%r (%s:%s): file doesn't exist or is not a " |
| 451 "regular file", line, cr.filename, linenum) | 468 "regular file", line, cr.filename, linenum) |
| 452 continue | 469 continue |
| 453 node = self._parse(FileReader(filename), node) | 470 node = self._parse(FileReader(filename), node) |
| 454 continue | 471 continue |
| 455 | 472 |
| 456 # Parse 'only' and 'no' filters | 473 # Parse 'only' and 'no' filters |
| 457 if words[0] in ("only", "no"): | 474 if words[0] in ("only", "no"): |
| 458 if len(words) < 2: | 475 if len(words) < 2: |
| 459 raise ParserError("Syntax error: missing parameter", | 476 raise ParserError("Syntax error: missing parameter", |
| 460 line, cr.filename, linenum) | 477 line, cr.filename, linenum) |
| 461 try: | 478 try: |
| 462 if words[0] == "only": | 479 if words[0] == "only": |
| 463 f = OnlyFilter(line) | 480 f = OnlyFilter(line) |
| 464 elif words[0] == "no": | 481 elif words[0] == "no": |
| 465 f = NoFilter(line) | 482 f = NoFilter(line) |
| 466 except ParserError, e: | 483 except ParserError, e: |
| 467 e.line = line | 484 e.line = line |
| 468 e.filename = cr.filename | 485 e.filename = cr.filename |
| 469 e.linenum = linenum | 486 e.linenum = linenum |
| 470 raise | 487 raise |
| 471 node.content += [(cr.filename, linenum, f)] | 488 node.content += [(cr.filename, linenum, f)] |
| 472 continue | 489 continue |
| 473 | 490 |
| 491 # Look for operators |
| 492 op_match = _ops_exp.search(line) |
| 493 |
| 474 # Parse conditional blocks | 494 # Parse conditional blocks |
| 475 if line.endswith(":"): | 495 if ":" in line: |
| 476 try: | 496 index = line.index(":") |
| 477 cond = Condition(line) | 497 if not op_match or index < op_match.start(): |
| 478 except ParserError, e: | 498 index += 1 |
| 479 e.line = line | 499 cr.set_next_line(line[index:], indent, linenum) |
| 480 e.filename = cr.filename | 500 line = line[:index] |
| 481 e.linenum = linenum | 501 try: |
| 482 raise | 502 if line.startswith("!"): |
| 483 self._parse(cr, cond, prev_indent=indent) | 503 cond = NegativeCondition(line) |
| 484 node.content += [(cr.filename, linenum, cond)] | 504 else: |
| 485 continue | 505 cond = Condition(line) |
| 506 except ParserError, e: |
| 507 e.line = line |
| 508 e.filename = cr.filename |
| 509 e.linenum = linenum |
| 510 raise |
| 511 self._parse(cr, cond, prev_indent=indent) |
| 512 node.content += [(cr.filename, linenum, cond)] |
| 513 continue |
| 486 | 514 |
| 487 # Parse regular operators | 515 # Parse regular operators |
| 488 try: | 516 if not op_match: |
| 489 op = Op(line) | 517 raise ParserError("Syntax error", line, cr.filename, linenum) |
| 490 except ParserError, e: | 518 node.content += [(cr.filename, linenum, Op(line, op_match))] |
| 491 e.line = line | |
| 492 e.filename = cr.filename | |
| 493 e.linenum = linenum | |
| 494 raise | |
| 495 node.content += [(cr.filename, linenum, op)] | |
| 496 | 519 |
| 497 return node | 520 return node |
| 498 | 521 |
| 499 | 522 |
| 500 # Assignment operators | 523 # Assignment operators |
| 501 | 524 |
| 502 _reserved_keys = set(("name", "shortname", "dep")) | 525 _reserved_keys = set(("name", "shortname", "dep")) |
| 503 | 526 |
| 504 | 527 |
| 505 def _op_set(d, key, value): | 528 def _op_set(d, key, value): |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 550 "<=": (r"\<\=", _op_prepend), | 573 "<=": (r"\<\=", _op_prepend), |
| 551 "?=": (r"\?\=", _op_regex_set), | 574 "?=": (r"\?\=", _op_regex_set), |
| 552 "?+=": (r"\?\+\=", _op_regex_append), | 575 "?+=": (r"\?\+\=", _op_regex_append), |
| 553 "?<=": (r"\?\<\=", _op_regex_prepend), | 576 "?<=": (r"\?\<\=", _op_regex_prepend), |
| 554 "del": (r"^del\b", _op_regex_del)} | 577 "del": (r"^del\b", _op_regex_del)} |
| 555 | 578 |
| 556 _ops_exp = re.compile("|".join([op[0] for op in _ops.values()])) | 579 _ops_exp = re.compile("|".join([op[0] for op in _ops.values()])) |
| 557 | 580 |
| 558 | 581 |
| 559 class Op(object): | 582 class Op(object): |
| 560 def __init__(self, line): | 583 def __init__(self, line, m): |
| 561 m = re.search(_ops_exp, line) | 584 self.func = _ops[m.group()][1] |
| 562 if not m: | 585 self.key = line[:m.start()].strip() |
| 563 raise ParserError("Syntax error: missing operator") | |
| 564 left = line[:m.start()].strip() | |
| 565 value = line[m.end():].strip() | 586 value = line[m.end():].strip() |
| 566 if value and ((value[0] == '"' and value[-1] == '"') or | 587 if value and (value[0] == value[-1] == '"' or |
| 567 (value[0] == "'" and value[-1] == "'")): | 588 value[0] == value[-1] == "'"): |
| 568 value = value[1:-1] | 589 value = value[1:-1] |
| 569 filters_and_key = map(str.strip, left.split(":")) | |
| 570 self.filters = [Filter(f) for f in filters_and_key[:-1]] | |
| 571 self.key = filters_and_key[-1] | |
| 572 self.value = value | 590 self.value = value |
| 573 self.func = _ops[m.group()][1] | |
| 574 | 591 |
| 575 | 592 |
| 576 def apply_to_dict(self, d, ctx, ctx_set): | 593 def apply_to_dict(self, d): |
| 577 for f in self.filters: | |
| 578 if not f.match(ctx, ctx_set): | |
| 579 return | |
| 580 self.func(d, self.key, self.value) | 594 self.func(d, self.key, self.value) |
| 581 | 595 |
| 582 | 596 |
| 583 # StrReader and FileReader | 597 # StrReader and FileReader |
| 584 | 598 |
| 585 class StrReader(object): | 599 class StrReader(object): |
| 586 """ | 600 """ |
| 587 Preprocess an input string for easy reading. | 601 Preprocess an input string for easy reading. |
| 588 """ | 602 """ |
| 589 def __init__(self, s): | 603 def __init__(self, s): |
| 590 """ | 604 """ |
| 591 Initialize the reader. | 605 Initialize the reader. |
| 592 | 606 |
| 593 @param s: The string to parse. | 607 @param s: The string to parse. |
| 594 """ | 608 """ |
| 595 self.filename = "<string>" | 609 self.filename = "<string>" |
| 596 self._lines = [] | 610 self._lines = [] |
| 597 self._line_index = 0 | 611 self._line_index = 0 |
| 612 self._stored_line = None |
| 598 for linenum, line in enumerate(s.splitlines()): | 613 for linenum, line in enumerate(s.splitlines()): |
| 599 line = line.rstrip().expandtabs() | 614 line = line.rstrip().expandtabs() |
| 600 stripped_line = line.lstrip() | 615 stripped_line = line.lstrip() |
| 601 indent = len(line) - len(stripped_line) | 616 indent = len(line) - len(stripped_line) |
| 602 if (not stripped_line | 617 if (not stripped_line |
| 603 or stripped_line.startswith("#") | 618 or stripped_line.startswith("#") |
| 604 or stripped_line.startswith("//")): | 619 or stripped_line.startswith("//")): |
| 605 continue | 620 continue |
| 606 self._lines.append((stripped_line, indent, linenum + 1)) | 621 self._lines.append((stripped_line, indent, linenum + 1)) |
| 607 | 622 |
| 608 | 623 |
| 609 def get_next_line(self, prev_indent): | 624 def get_next_line(self, prev_indent): |
| 610 """ | 625 """ |
| 611 Get the next non-empty, non-comment line in the string, whose | 626 Get the next line in the current block. |
| 612 indentation level is higher than prev_indent. | |
| 613 | 627 |
| 614 @param prev_indent: The indentation level of the previous block. | 628 @param prev_indent: The indentation level of the previous block. |
| 615 @return: (line, indent, linenum), where indent is the line's | 629 @return: (line, indent, linenum), where indent is the line's |
| 616 indentation level. If no line is available, (None, -1, -1) is | 630 indentation level. If no line is available, (None, -1, -1) is |
| 617 returned. | 631 returned. |
| 618 """ | 632 """ |
| 633 if self._stored_line: |
| 634 ret = self._stored_line |
| 635 self._stored_line = None |
| 636 return ret |
| 619 if self._line_index >= len(self._lines): | 637 if self._line_index >= len(self._lines): |
| 620 return None, -1, -1 | 638 return None, -1, -1 |
| 621 line, indent, linenum = self._lines[self._line_index] | 639 line, indent, linenum = self._lines[self._line_index] |
| 622 if indent <= prev_indent: | 640 if indent <= prev_indent: |
| 623 return None, -1, -1 | 641 return None, -1, -1 |
| 624 self._line_index += 1 | 642 self._line_index += 1 |
| 625 return line, indent, linenum | 643 return line, indent, linenum |
| 626 | 644 |
| 627 | 645 |
| 646 def set_next_line(self, line, indent, linenum): |
| 647 """ |
| 648 Make the next call to get_next_line() return the given line instead of |
| 649 the real next line. |
| 650 """ |
| 651 line = line.strip() |
| 652 if line: |
| 653 self._stored_line = line, indent, linenum |
| 654 |
| 655 |
| 628 class FileReader(StrReader): | 656 class FileReader(StrReader): |
| 629 """ | 657 """ |
| 630 Preprocess an input file for easy reading. | 658 Preprocess an input file for easy reading. |
| 631 """ | 659 """ |
| 632 def __init__(self, filename): | 660 def __init__(self, filename): |
| 633 """ | 661 """ |
| 634 Initialize the reader. | 662 Initialize the reader. |
| 635 | 663 |
| 636 @parse filename: The name of the input file. | 664 @parse filename: The name of the input file. |
| 637 """ | 665 """ |
| 638 StrReader.__init__(self, open(filename).read()) | 666 StrReader.__init__(self, open(filename).read()) |
| 639 self.filename = filename | 667 self.filename = filename |
| 640 | 668 |
| 641 | 669 |
| 642 if __name__ == "__main__": | 670 if __name__ == "__main__": |
| 643 parser = optparse.OptionParser("usage: %prog [options] <filename>") | 671 parser = optparse.OptionParser('usage: %prog [options] filename ' |
| 672 '[extra code] ...\n\nExample:\n\n ' |
| 673 '%prog tests.cfg "only my_set" "no qcow2"') |
| 644 parser.add_option("-v", "--verbose", dest="debug", action="store_true", | 674 parser.add_option("-v", "--verbose", dest="debug", action="store_true", |
| 645 help="include debug messages in console output") | 675 help="include debug messages in console output") |
| 646 parser.add_option("-f", "--fullname", dest="fullname", action="store_true", | 676 parser.add_option("-f", "--fullname", dest="fullname", action="store_true", |
| 647 help="show full dict names instead of short names") | 677 help="show full dict names instead of short names") |
| 648 parser.add_option("-c", "--contents", dest="contents", action="store_true", | 678 parser.add_option("-c", "--contents", dest="contents", action="store_true", |
| 649 help="show dict contents") | 679 help="show dict contents") |
| 650 | 680 |
| 651 options, args = parser.parse_args() | 681 options, args = parser.parse_args() |
| 652 if not args: | 682 if not args: |
| 653 parser.error("filename required") | 683 parser.error("filename required") |
| 654 | 684 |
| 655 c = Parser(args[0], debug=options.debug) | 685 c = Parser(args[0], debug=options.debug) |
| 686 for s in args[1:]: |
| 687 c.parse_string(s) |
| 688 |
| 656 for i, d in enumerate(c.get_dicts()): | 689 for i, d in enumerate(c.get_dicts()): |
| 657 if options.fullname: | 690 if options.fullname: |
| 658 print "dict %4d: %s" % (i + 1, d["name"]) | 691 print "dict %4d: %s" % (i + 1, d["name"]) |
| 659 else: | 692 else: |
| 660 print "dict %4d: %s" % (i + 1, d["shortname"]) | 693 print "dict %4d: %s" % (i + 1, d["shortname"]) |
| 661 if options.contents: | 694 if options.contents: |
| 662 keys = d.keys() | 695 keys = d.keys() |
| 663 keys.sort() | 696 keys.sort() |
| 664 for key in keys: | 697 for key in keys: |
| 665 print " %s = %s" % (key, d[key]) | 698 print " %s = %s" % (key, d[key]) |
| OLD | NEW |