Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(11)

Side by Side Diff: third_party/manifestdestiny/manifestparser/manifestparser.py

Issue 108313011: Adding mozilla libraries required by Firefox interop test. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/deps/
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:executable
+ *
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 #!/usr/bin/env python
2
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 # You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 """
8 Mozilla universal manifest parser
9 """
10
11 __all__ = ['read_ini', # .ini reader
12 'ManifestParser', 'TestManifest', 'convert', # manifest handling
13 'parse', 'ParseError', 'ExpressionParser'] # conditional expression p arser
14
15 import os
16 import re
17 import shutil
18 import sys
19 from fnmatch import fnmatch
20 from optparse import OptionParser
21
22 # we need relpath, but it is introduced in python 2.6
23 # http://docs.python.org/library/os.path.html
24 try:
25 relpath = os.path.relpath
26 except AttributeError:
27 def relpath(path, start):
28 """
29 Return a relative version of a path
30 from /usr/lib/python2.6/posixpath.py
31 """
32
33 if not path:
34 raise ValueError("no path specified")
35
36 start_list = os.path.abspath(start).split(os.path.sep)
37 path_list = os.path.abspath(path).split(os.path.sep)
38
39 # Work out how much of the filepath is shared by start and path.
40 i = len(os.path.commonprefix([start_list, path_list]))
41
42 rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
43 if not rel_list:
44 return os.curdir
45 return os.path.join(*rel_list)
46
47 # expr.py
48 # from:
49 # http://k0s.org/mozilla/hg/expressionparser
50 # http://hg.mozilla.org/users/tmielczarek_mozilla.com/expressionparser
51
52 # Implements a top-down parser/evaluator for simple boolean expressions.
53 # ideas taken from http://effbot.org/zone/simple-top-down-parsing.htm
54 #
55 # Rough grammar:
56 # expr := literal
57 # | '(' expr ')'
58 # | expr '&&' expr
59 # | expr '||' expr
60 # | expr '==' expr
61 # | expr '!=' expr
62 # literal := BOOL
63 # | INT
64 # | STRING
65 # | IDENT
66 # BOOL := true|false
67 # INT := [0-9]+
68 # STRING := "[^"]*"
69 # IDENT := [A-Za-z_]\w*
70
71 # Identifiers take their values from a mapping dictionary passed as the second
72 # argument.
73
74 # Glossary (see above URL for details):
75 # - nud: null denotation
76 # - led: left detonation
77 # - lbp: left binding power
78 # - rbp: right binding power
79
80 class ident_token(object):
81 def __init__(self, value):
82 self.value = value
83 def nud(self, parser):
84 # identifiers take their value from the value mappings passed
85 # to the parser
86 return parser.value(self.value)
87
88 class literal_token(object):
89 def __init__(self, value):
90 self.value = value
91 def nud(self, parser):
92 return self.value
93
94 class eq_op_token(object):
95 "=="
96 def led(self, parser, left):
97 return left == parser.expression(self.lbp)
98
99 class neq_op_token(object):
100 "!="
101 def led(self, parser, left):
102 return left != parser.expression(self.lbp)
103
104 class not_op_token(object):
105 "!"
106 def nud(self, parser):
107 return not parser.expression()
108
109 class and_op_token(object):
110 "&&"
111 def led(self, parser, left):
112 right = parser.expression(self.lbp)
113 return left and right
114
115 class or_op_token(object):
116 "||"
117 def led(self, parser, left):
118 right = parser.expression(self.lbp)
119 return left or right
120
121 class lparen_token(object):
122 "("
123 def nud(self, parser):
124 expr = parser.expression()
125 parser.advance(rparen_token)
126 return expr
127
128 class rparen_token(object):
129 ")"
130
131 class end_token(object):
132 """always ends parsing"""
133
134 ### derived literal tokens
135
136 class bool_token(literal_token):
137 def __init__(self, value):
138 value = {'true':True, 'false':False}[value]
139 literal_token.__init__(self, value)
140
141 class int_token(literal_token):
142 def __init__(self, value):
143 literal_token.__init__(self, int(value))
144
145 class string_token(literal_token):
146 def __init__(self, value):
147 literal_token.__init__(self, value[1:-1])
148
149 precedence = [(end_token, rparen_token),
150 (or_op_token,),
151 (and_op_token,),
152 (eq_op_token, neq_op_token),
153 (lparen_token,),
154 ]
155 for index, rank in enumerate(precedence):
156 for token in rank:
157 token.lbp = index # lbp = lowest left binding power
158
159 class ParseError(Exception):
160 """errror parsing conditional expression"""
161
162 class ExpressionParser(object):
163 def __init__(self, text, valuemapping, strict=False):
164 """
165 Initialize the parser with input |text|, and |valuemapping| as
166 a dict mapping identifier names to values.
167 """
168 self.text = text
169 self.valuemapping = valuemapping
170 self.strict = strict
171
172 def _tokenize(self):
173 """
174 Lex the input text into tokens and yield them in sequence.
175 """
176 # scanner callbacks
177 def bool_(scanner, t): return bool_token(t)
178 def identifier(scanner, t): return ident_token(t)
179 def integer(scanner, t): return int_token(t)
180 def eq(scanner, t): return eq_op_token()
181 def neq(scanner, t): return neq_op_token()
182 def or_(scanner, t): return or_op_token()
183 def and_(scanner, t): return and_op_token()
184 def lparen(scanner, t): return lparen_token()
185 def rparen(scanner, t): return rparen_token()
186 def string_(scanner, t): return string_token(t)
187 def not_(scanner, t): return not_op_token()
188
189 scanner = re.Scanner([
190 (r"true|false", bool_),
191 (r"[a-zA-Z_]\w*", identifier),
192 (r"[0-9]+", integer),
193 (r'("[^"]*")|(\'[^\']*\')', string_),
194 (r"==", eq),
195 (r"!=", neq),
196 (r"\|\|", or_),
197 (r"!", not_),
198 (r"&&", and_),
199 (r"\(", lparen),
200 (r"\)", rparen),
201 (r"\s+", None), # skip whitespace
202 ])
203 tokens, remainder = scanner.scan(self.text)
204 for t in tokens:
205 yield t
206 yield end_token()
207
208 def value(self, ident):
209 """
210 Look up the value of |ident| in the value mapping passed in the
211 constructor.
212 """
213 if self.strict:
214 return self.valuemapping[ident]
215 else:
216 return self.valuemapping.get(ident, None)
217
218 def advance(self, expected):
219 """
220 Assert that the next token is an instance of |expected|, and advance
221 to the next token.
222 """
223 if not isinstance(self.token, expected):
224 raise Exception, "Unexpected token!"
225 self.token = self.iter.next()
226
227 def expression(self, rbp=0):
228 """
229 Parse and return the value of an expression until a token with
230 right binding power greater than rbp is encountered.
231 """
232 t = self.token
233 self.token = self.iter.next()
234 left = t.nud(self)
235 while rbp < self.token.lbp:
236 t = self.token
237 self.token = self.iter.next()
238 left = t.led(self, left)
239 return left
240
241 def parse(self):
242 """
243 Parse and return the value of the expression in the text
244 passed to the constructor. Raises a ParseError if the expression
245 could not be parsed.
246 """
247 try:
248 self.iter = self._tokenize()
249 self.token = self.iter.next()
250 return self.expression()
251 except:
252 raise ParseError("could not parse: %s; variables: %s" % (self.text, self.valuemapping))
253
254 __call__ = parse
255
256 def parse(text, **values):
257 """
258 Parse and evaluate a boolean expression in |text|. Use |values| to look
259 up the value of identifiers referenced in the expression. Returns the final
260 value of the expression. A ParseError will be raised if parsing fails.
261 """
262 return ExpressionParser(text, values).parse()
263
264 def normalize_path(path):
265 """normalize a relative path"""
266 if sys.platform.startswith('win'):
267 return path.replace('/', os.path.sep)
268 return path
269
270 def denormalize_path(path):
271 """denormalize a relative path"""
272 if sys.platform.startswith('win'):
273 return path.replace(os.path.sep, '/')
274 return path
275
276
277 def read_ini(fp, variables=None, default='DEFAULT',
278 comments=';#', separators=('=', ':'),
279 strict=True):
280 """
281 read an .ini file and return a list of [(section, values)]
282 - fp : file pointer or path to read
283 - variables : default set of variables
284 - default : name of the section for the default section
285 - comments : characters that if they start a line denote a comment
286 - separators : strings that denote key, value separation in order
287 - strict : whether to be strict about parsing
288 """
289
290 if variables is None:
291 variables = {}
292
293 if isinstance(fp, basestring):
294 fp = file(fp)
295
296 sections = []
297 key = value = None
298 section_names = set([])
299
300 # read the lines
301 for line in fp.readlines():
302
303 stripped = line.strip()
304
305 # ignore blank lines
306 if not stripped:
307 # reset key and value to avoid continuation lines
308 key = value = None
309 continue
310
311 # ignore comment lines
312 if stripped[0] in comments:
313 continue
314
315 # check for a new section
316 if len(stripped) > 2 and stripped[0] == '[' and stripped[-1] == ']':
317 section = stripped[1:-1].strip()
318 key = value = None
319
320 # deal with DEFAULT section
321 if section.lower() == default.lower():
322 if strict:
323 assert default not in section_names
324 section_names.add(default)
325 current_section = variables
326 continue
327
328 if strict:
329 # make sure this section doesn't already exist
330 assert section not in section_names, "Section '%s' already found in '%s'" % (section, section_names)
331
332 section_names.add(section)
333 current_section = {}
334 sections.append((section, current_section))
335 continue
336
337 # if there aren't any sections yet, something bad happen
338 if not section_names:
339 raise Exception('No sections found')
340
341 # (key, value) pair
342 for separator in separators:
343 if separator in stripped:
344 key, value = stripped.split(separator, 1)
345 key = key.strip()
346 value = value.strip()
347
348 if strict:
349 # make sure this key isn't already in the section or empty
350 assert key
351 if current_section is not variables:
352 assert key not in current_section
353
354 current_section[key] = value
355 break
356 else:
357 # continuation line ?
358 if line[0].isspace() and key:
359 value = '%s%s%s' % (value, os.linesep, stripped)
360 current_section[key] = value
361 else:
362 # something bad happen!
363 raise Exception("Not sure what you're trying to do")
364
365 # interpret the variables
366 def interpret_variables(global_dict, local_dict):
367 variables = global_dict.copy()
368 variables.update(local_dict)
369 return variables
370
371 sections = [(i, interpret_variables(variables, j)) for i, j in sections]
372 return sections
373
374
375 ### objects for parsing manifests
376
377 class ManifestParser(object):
378 """read .ini manifests"""
379
380 ### methods for reading manifests
381
382 def __init__(self, manifests=(), defaults=None, strict=True):
383 self._defaults = defaults or {}
384 self.tests = []
385 self.strict = strict
386 self.rootdir = None
387 self.relativeRoot = None
388 if manifests:
389 self.read(*manifests)
390
391 def getRelativeRoot(self, root):
392 return root
393
394 def _read(self, root, filename, defaults):
395
396 # get directory of this file
397 here = os.path.dirname(os.path.abspath(filename))
398 defaults['here'] = here
399
400 # read the configuration
401 sections = read_ini(fp=filename, variables=defaults, strict=self.strict)
402
403 # get the tests
404 for section, data in sections:
405
406 # a file to include
407 # TODO: keep track of included file structure:
408 # self.manifests = {'manifest.ini': 'relative/path.ini'}
409 if section.startswith('include:'):
410 include_file = section.split('include:', 1)[-1]
411 include_file = normalize_path(include_file)
412 if not os.path.isabs(include_file):
413 include_file = os.path.join(self.getRelativeRoot(here), incl ude_file)
414 if not os.path.exists(include_file):
415 if self.strict:
416 raise IOError("File '%s' does not exist" % include_file)
417 else:
418 continue
419 include_defaults = data.copy()
420 self._read(root, include_file, include_defaults)
421 continue
422
423 # otherwise an item
424 test = data
425 test['name'] = section
426 test['manifest'] = os.path.abspath(filename)
427
428 # determine the path
429 path = test.get('path', section)
430 _relpath = path
431 if '://' not in path: # don't futz with URLs
432 path = normalize_path(path)
433 if not os.path.isabs(path):
434 path = os.path.join(here, path)
435 _relpath = relpath(path, self.rootdir)
436
437 test['path'] = path
438 test['relpath'] = _relpath
439
440 # append the item
441 self.tests.append(test)
442
443 def read(self, *filenames, **defaults):
444
445 # ensure all files exist
446 missing = [ filename for filename in filenames
447 if not os.path.exists(filename) ]
448 if missing:
449 raise IOError('Missing files: %s' % ', '.join(missing))
450
451 # process each file
452 for filename in filenames:
453
454 # set the per file defaults
455 defaults = defaults.copy() or self._defaults.copy()
456 here = os.path.dirname(os.path.abspath(filename))
457 defaults['here'] = here
458
459 if self.rootdir is None:
460 # set the root directory
461 # == the directory of the first manifest given
462 self.rootdir = here
463
464 self._read(here, filename, defaults)
465
466 ### methods for querying manifests
467
468 def query(self, *checks, **kw):
469 """
470 general query function for tests
471 - checks : callable conditions to test if the test fulfills the query
472 """
473 tests = kw.get('tests', None)
474 if tests is None:
475 tests = self.tests
476 retval = []
477 for test in tests:
478 for check in checks:
479 if not check(test):
480 break
481 else:
482 retval.append(test)
483 return retval
484
485 def get(self, _key=None, inverse=False, tags=None, tests=None, **kwargs):
486 # TODO: pass a dict instead of kwargs since you might hav
487 # e.g. 'inverse' as a key in the dict
488
489 # TODO: tags should just be part of kwargs with None values
490 # (None == any is kinda weird, but probably still better)
491
492 # fix up tags
493 if tags:
494 tags = set(tags)
495 else:
496 tags = set()
497
498 # make some check functions
499 if inverse:
500 has_tags = lambda test: not tags.intersection(test.keys())
501 def dict_query(test):
502 for key, value in kwargs.items():
503 if test.get(key) == value:
504 return False
505 return True
506 else:
507 has_tags = lambda test: tags.issubset(test.keys())
508 def dict_query(test):
509 for key, value in kwargs.items():
510 if test.get(key) != value:
511 return False
512 return True
513
514 # query the tests
515 tests = self.query(has_tags, dict_query, tests=tests)
516
517 # if a key is given, return only a list of that key
518 # useful for keys like 'name' or 'path'
519 if _key:
520 return [test[_key] for test in tests]
521
522 # return the tests
523 return tests
524
525 def missing(self, tests=None):
526 """return list of tests that do not exist on the filesystem"""
527 if tests is None:
528 tests = self.tests
529 return [test for test in tests
530 if not os.path.exists(test['path'])]
531
532 def manifests(self, tests=None):
533 """
534 return manifests in order in which they appear in the tests
535 """
536 if tests is None:
537 tests = self.tests
538 manifests = []
539 for test in tests:
540 manifest = test.get('manifest')
541 if not manifest:
542 continue
543 if manifest not in manifests:
544 manifests.append(manifest)
545 return manifests
546
547 ### methods for outputting from manifests
548
549 def write(self, fp=sys.stdout, rootdir=None,
550 global_tags=None, global_kwargs=None,
551 local_tags=None, local_kwargs=None):
552 """
553 write a manifest given a query
554 global and local options will be munged to do the query
555 globals will be written to the top of the file
556 locals (if given) will be written per test
557 """
558
559 # root directory
560 if rootdir is None:
561 rootdir = self.rootdir
562
563 # sanitize input
564 global_tags = global_tags or set()
565 local_tags = local_tags or set()
566 global_kwargs = global_kwargs or {}
567 local_kwargs = local_kwargs or {}
568
569 # create the query
570 tags = set([])
571 tags.update(global_tags)
572 tags.update(local_tags)
573 kwargs = {}
574 kwargs.update(global_kwargs)
575 kwargs.update(local_kwargs)
576
577 # get matching tests
578 tests = self.get(tags=tags, **kwargs)
579
580 # print the .ini manifest
581 if global_tags or global_kwargs:
582 print >> fp, '[DEFAULT]'
583 for tag in global_tags:
584 print >> fp, '%s =' % tag
585 for key, value in global_kwargs.items():
586 print >> fp, '%s = %s' % (key, value)
587 print >> fp
588
589 for test in tests:
590 test = test.copy() # don't overwrite
591
592 path = test['name']
593 if not os.path.isabs(path):
594 path = test['path']
595 if self.rootdir:
596 path = relpath(test['path'], self.rootdir)
597 path = denormalize_path(path)
598 print >> fp, '[%s]' % path
599
600 # reserved keywords:
601 reserved = ['path', 'name', 'here', 'manifest', 'relpath']
602 for key in sorted(test.keys()):
603 if key in reserved:
604 continue
605 if key in global_kwargs:
606 continue
607 if key in global_tags and not test[key]:
608 continue
609 print >> fp, '%s = %s' % (key, test[key])
610 print >> fp
611
612 def copy(self, directory, rootdir=None, *tags, **kwargs):
613 """
614 copy the manifests and associated tests
615 - directory : directory to copy to
616 - rootdir : root directory to copy to (if not given from manifests)
617 - tags : keywords the tests must have
618 - kwargs : key, values the tests must match
619 """
620 # XXX note that copy does *not* filter the tests out of the
621 # resulting manifest; it just stupidly copies them over.
622 # ideally, it would reread the manifests and filter out the
623 # tests that don't match *tags and **kwargs
624
625 # destination
626 if not os.path.exists(directory):
627 os.path.makedirs(directory)
628 else:
629 # sanity check
630 assert os.path.isdir(directory)
631
632 # tests to copy
633 tests = self.get(tags=tags, **kwargs)
634 if not tests:
635 return # nothing to do!
636
637 # root directory
638 if rootdir is None:
639 rootdir = self.rootdir
640
641 # copy the manifests + tests
642 manifests = [relpath(manifest, rootdir) for manifest in self.manifests() ]
643 for manifest in manifests:
644 destination = os.path.join(directory, manifest)
645 dirname = os.path.dirname(destination)
646 if not os.path.exists(dirname):
647 os.makedirs(dirname)
648 else:
649 # sanity check
650 assert os.path.isdir(dirname)
651 shutil.copy(os.path.join(rootdir, manifest), destination)
652 for test in tests:
653 if os.path.isabs(test['name']):
654 continue
655 source = test['path']
656 if not os.path.exists(source):
657 print >> sys.stderr, "Missing test: '%s' does not exist!" % sour ce
658 continue
659 # TODO: should err on strict
660 destination = os.path.join(directory, relpath(test['path'], rootdir) )
661 shutil.copy(source, destination)
662 # TODO: ensure that all of the tests are below the from_dir
663
664 def update(self, from_dir, rootdir=None, *tags, **kwargs):
665 """
666 update the tests as listed in a manifest from a directory
667 - from_dir : directory where the tests live
668 - rootdir : root directory to copy to (if not given from manifests)
669 - tags : keys the tests must have
670 - kwargs : key, values the tests must match
671 """
672
673 # get the tests
674 tests = self.get(tags=tags, **kwargs)
675
676 # get the root directory
677 if not rootdir:
678 rootdir = self.rootdir
679
680 # copy them!
681 for test in tests:
682 if not os.path.isabs(test['name']):
683 _relpath = relpath(test['path'], rootdir)
684 source = os.path.join(from_dir, _relpath)
685 if not os.path.exists(source):
686 # TODO err on strict
687 print >> sys.stderr, "Missing test: '%s'; skipping" % test[' name']
688 continue
689 destination = os.path.join(rootdir, _relpath)
690 shutil.copy(source, destination)
691
692
693 class TestManifest(ManifestParser):
694 """
695 apply logic to manifests; this is your integration layer :)
696 specific harnesses may subclass from this if they need more logic
697 """
698
699 def filter(self, values, tests):
700 """
701 filter on a specific list tag, e.g.:
702 run-if = os == win linux
703 skip-if = os == mac
704 """
705
706 # tags:
707 run_tag = 'run-if'
708 skip_tag = 'skip-if'
709 fail_tag = 'fail-if'
710
711 # loop over test
712 for test in tests:
713 reason = None # reason to disable
714
715 # tagged-values to run
716 if run_tag in test:
717 condition = test[run_tag]
718 if not parse(condition, **values):
719 reason = '%s: %s' % (run_tag, condition)
720
721 # tagged-values to skip
722 if skip_tag in test:
723 condition = test[skip_tag]
724 if parse(condition, **values):
725 reason = '%s: %s' % (skip_tag, condition)
726
727 # mark test as disabled if there's a reason
728 if reason:
729 test.setdefault('disabled', reason)
730
731 # mark test as a fail if so indicated
732 if fail_tag in test:
733 condition = test[fail_tag]
734 if parse(condition, **values):
735 test['expected'] = 'fail'
736
737 def active_tests(self, exists=True, disabled=True, **values):
738 """
739 - exists : return only existing tests
740 - disabled : whether to return disabled tests
741 - tags : keys and values to filter on (e.g. `os = linux mac`)
742 """
743
744 tests = [i.copy() for i in self.tests] # shallow copy
745
746 # mark all tests as passing unless indicated otherwise
747 for test in tests:
748 test['expected'] = test.get('expected', 'pass')
749
750 # ignore tests that do not exist
751 if exists:
752 tests = [test for test in tests if os.path.exists(test['path'])]
753
754 # filter by tags
755 self.filter(values, tests)
756
757 # ignore disabled tests if specified
758 if not disabled:
759 tests = [test for test in tests
760 if not 'disabled' in test]
761
762 # return active tests
763 return tests
764
765 def test_paths(self):
766 return [test['path'] for test in self.active_tests()]
767
768
769 ### utility function(s); probably belongs elsewhere
770
771 def convert(directories, pattern=None, ignore=(), write=None):
772 """
773 convert directories to a simple manifest
774 """
775
776 retval = []
777 include = []
778 for directory in directories:
779 for dirpath, dirnames, filenames in os.walk(directory):
780
781 # filter out directory names
782 dirnames = [ i for i in dirnames if i not in ignore ]
783 dirnames.sort()
784
785 # reference only the subdirectory
786 _dirpath = dirpath
787 dirpath = dirpath.split(directory, 1)[-1].strip(os.path.sep)
788
789 if dirpath.split(os.path.sep)[0] in ignore:
790 continue
791
792 # filter by glob
793 if pattern:
794 filenames = [filename for filename in filenames
795 if fnmatch(filename, pattern)]
796
797 filenames.sort()
798
799 # write a manifest for each directory
800 if write and (dirnames or filenames):
801 manifest = file(os.path.join(_dirpath, write), 'w')
802 for dirname in dirnames:
803 print >> manifest, '[include:%s]' % os.path.join(dirname, wr ite)
804 for filename in filenames:
805 print >> manifest, '[%s]' % filename
806 manifest.close()
807
808 # add to the list
809 retval.extend([denormalize_path(os.path.join(dirpath, filename))
810 for filename in filenames])
811
812 if write:
813 return # the manifests have already been written!
814
815 retval.sort()
816 retval = ['[%s]' % filename for filename in retval]
817 return '\n'.join(retval)
818
819 ### command line attributes
820
821 class ParserError(Exception):
822 """error for exceptions while parsing the command line"""
823
824 def parse_args(_args):
825 """
826 parse and return:
827 --keys=value (or --key value)
828 -tags
829 args
830 """
831
832 # return values
833 _dict = {}
834 tags = []
835 args = []
836
837 # parse the arguments
838 key = None
839 for arg in _args:
840 if arg.startswith('---'):
841 raise ParserError("arguments should start with '-' or '--' only")
842 elif arg.startswith('--'):
843 if key:
844 raise ParserError("Key %s still open" % key)
845 key = arg[2:]
846 if '=' in key:
847 key, value = key.split('=', 1)
848 _dict[key] = value
849 key = None
850 continue
851 elif arg.startswith('-'):
852 if key:
853 raise ParserError("Key %s still open" % key)
854 tags.append(arg[1:])
855 continue
856 else:
857 if key:
858 _dict[key] = arg
859 continue
860 args.append(arg)
861
862 # return values
863 return (_dict, tags, args)
864
865
866 ### classes for subcommands
867
868 class CLICommand(object):
869 usage = '%prog [options] command'
870 def __init__(self, parser):
871 self._parser = parser # master parser
872 def parser(self):
873 return OptionParser(usage=self.usage, description=self.__doc__,
874 add_help_option=False)
875
876 class Copy(CLICommand):
877 usage = '%prog [options] copy manifest directory -tag1 -tag2 --key1=value1 - -key2=value2 ...'
878 def __call__(self, options, args):
879 # parse the arguments
880 try:
881 kwargs, tags, args = parse_args(args)
882 except ParserError, e:
883 self._parser.error(e.message)
884
885 # make sure we have some manifests, otherwise it will
886 # be quite boring
887 if not len(args) == 2:
888 HelpCLI(self._parser)(options, ['copy'])
889 return
890
891 # read the manifests
892 # TODO: should probably ensure these exist here
893 manifests = ManifestParser()
894 manifests.read(args[0])
895
896 # print the resultant query
897 manifests.copy(args[1], None, *tags, **kwargs)
898
899
900 class CreateCLI(CLICommand):
901 """
902 create a manifest from a list of directories
903 """
904 usage = '%prog [options] create directory <directory> <...>'
905
906 def parser(self):
907 parser = CLICommand.parser(self)
908 parser.add_option('-p', '--pattern', dest='pattern',
909 help="glob pattern for files")
910 parser.add_option('-i', '--ignore', dest='ignore',
911 default=[], action='append',
912 help='directories to ignore')
913 parser.add_option('-w', '--in-place', dest='in_place',
914 help='Write .ini files in place; filename to write to' )
915 return parser
916
917 def __call__(self, _options, args):
918 parser = self.parser()
919 options, args = parser.parse_args(args)
920
921 # need some directories
922 if not len(args):
923 parser.print_usage()
924 return
925
926 # add the directories to the manifest
927 for arg in args:
928 assert os.path.exists(arg)
929 assert os.path.isdir(arg)
930 manifest = convert(args, pattern=options.pattern, ignore=options.ign ore,
931 write=options.in_place)
932 if manifest:
933 print manifest
934
935
936 class WriteCLI(CLICommand):
937 """
938 write a manifest based on a query
939 """
940 usage = '%prog [options] write manifest <manifest> -tag1 -tag2 --key1=value1 --key2=value2 ...'
941 def __call__(self, options, args):
942
943 # parse the arguments
944 try:
945 kwargs, tags, args = parse_args(args)
946 except ParserError, e:
947 self._parser.error(e.message)
948
949 # make sure we have some manifests, otherwise it will
950 # be quite boring
951 if not args:
952 HelpCLI(self._parser)(options, ['write'])
953 return
954
955 # read the manifests
956 # TODO: should probably ensure these exist here
957 manifests = ManifestParser()
958 manifests.read(*args)
959
960 # print the resultant query
961 manifests.write(global_tags=tags, global_kwargs=kwargs)
962
963
964 class HelpCLI(CLICommand):
965 """
966 get help on a command
967 """
968 usage = '%prog [options] help [command]'
969
970 def __call__(self, options, args):
971 if len(args) == 1 and args[0] in commands:
972 commands[args[0]](self._parser).parser().print_help()
973 else:
974 self._parser.print_help()
975 print '\nCommands:'
976 for command in sorted(commands):
977 print ' %s : %s' % (command, commands[command].__doc__.strip())
978
979 class UpdateCLI(CLICommand):
980 """
981 update the tests as listed in a manifest from a directory
982 """
983 usage = '%prog [options] update manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...'
984
985 def __call__(self, options, args):
986 # parse the arguments
987 try:
988 kwargs, tags, args = parse_args(args)
989 except ParserError, e:
990 self._parser.error(e.message)
991
992 # make sure we have some manifests, otherwise it will
993 # be quite boring
994 if not len(args) == 2:
995 HelpCLI(self._parser)(options, ['update'])
996 return
997
998 # read the manifests
999 # TODO: should probably ensure these exist here
1000 manifests = ManifestParser()
1001 manifests.read(args[0])
1002
1003 # print the resultant query
1004 manifests.update(args[1], None, *tags, **kwargs)
1005
1006
1007 # command -> class mapping
1008 commands = { 'create': CreateCLI,
1009 'help': HelpCLI,
1010 'update': UpdateCLI,
1011 'write': WriteCLI }
1012
1013 def main(args=sys.argv[1:]):
1014 """console_script entry point"""
1015
1016 # set up an option parser
1017 usage = '%prog [options] [command] ...'
1018 description = "%s. Use `help` to display commands" % __doc__.strip()
1019 parser = OptionParser(usage=usage, description=description)
1020 parser.add_option('-s', '--strict', dest='strict',
1021 action='store_true', default=False,
1022 help='adhere strictly to errors')
1023 parser.disable_interspersed_args()
1024
1025 options, args = parser.parse_args(args)
1026
1027 if not args:
1028 HelpCLI(parser)(options, args)
1029 parser.exit()
1030
1031 # get the command
1032 command = args[0]
1033 if command not in commands:
1034 parser.error("Command must be one of %s (you gave '%s')" % (', '.join(so rted(commands.keys())), command))
1035
1036 handler = commands[command](parser)
1037 handler(options, args[1:])
1038
1039 if __name__ == '__main__':
1040 main()
OLDNEW
« no previous file with comments | « third_party/manifestdestiny/manifestparser/__init__.py ('k') | third_party/manifestdestiny/setup.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698