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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/manifestdestiny/manifestparser/__init__.py ('k') | third_party/manifestdestiny/setup.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/manifestdestiny/manifestparser/manifestparser.py
===================================================================
--- third_party/manifestdestiny/manifestparser/manifestparser.py (revision 0)
+++ third_party/manifestdestiny/manifestparser/manifestparser.py (revision 0)
@@ -0,0 +1,1040 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Mozilla universal manifest parser
+"""
+
+__all__ = ['read_ini', # .ini reader
+ 'ManifestParser', 'TestManifest', 'convert', # manifest handling
+ 'parse', 'ParseError', 'ExpressionParser'] # conditional expression parser
+
+import os
+import re
+import shutil
+import sys
+from fnmatch import fnmatch
+from optparse import OptionParser
+
+# we need relpath, but it is introduced in python 2.6
+# http://docs.python.org/library/os.path.html
+try:
+ relpath = os.path.relpath
+except AttributeError:
+ def relpath(path, start):
+ """
+ Return a relative version of a path
+ from /usr/lib/python2.6/posixpath.py
+ """
+
+ if not path:
+ raise ValueError("no path specified")
+
+ start_list = os.path.abspath(start).split(os.path.sep)
+ path_list = os.path.abspath(path).split(os.path.sep)
+
+ # Work out how much of the filepath is shared by start and path.
+ i = len(os.path.commonprefix([start_list, path_list]))
+
+ rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
+ if not rel_list:
+ return os.curdir
+ return os.path.join(*rel_list)
+
+# expr.py
+# from:
+# http://k0s.org/mozilla/hg/expressionparser
+# http://hg.mozilla.org/users/tmielczarek_mozilla.com/expressionparser
+
+# Implements a top-down parser/evaluator for simple boolean expressions.
+# ideas taken from http://effbot.org/zone/simple-top-down-parsing.htm
+#
+# Rough grammar:
+# expr := literal
+# | '(' expr ')'
+# | expr '&&' expr
+# | expr '||' expr
+# | expr '==' expr
+# | expr '!=' expr
+# literal := BOOL
+# | INT
+# | STRING
+# | IDENT
+# BOOL := true|false
+# INT := [0-9]+
+# STRING := "[^"]*"
+# IDENT := [A-Za-z_]\w*
+
+# Identifiers take their values from a mapping dictionary passed as the second
+# argument.
+
+# Glossary (see above URL for details):
+# - nud: null denotation
+# - led: left detonation
+# - lbp: left binding power
+# - rbp: right binding power
+
+class ident_token(object):
+ def __init__(self, value):
+ self.value = value
+ def nud(self, parser):
+ # identifiers take their value from the value mappings passed
+ # to the parser
+ return parser.value(self.value)
+
+class literal_token(object):
+ def __init__(self, value):
+ self.value = value
+ def nud(self, parser):
+ return self.value
+
+class eq_op_token(object):
+ "=="
+ def led(self, parser, left):
+ return left == parser.expression(self.lbp)
+
+class neq_op_token(object):
+ "!="
+ def led(self, parser, left):
+ return left != parser.expression(self.lbp)
+
+class not_op_token(object):
+ "!"
+ def nud(self, parser):
+ return not parser.expression()
+
+class and_op_token(object):
+ "&&"
+ def led(self, parser, left):
+ right = parser.expression(self.lbp)
+ return left and right
+
+class or_op_token(object):
+ "||"
+ def led(self, parser, left):
+ right = parser.expression(self.lbp)
+ return left or right
+
+class lparen_token(object):
+ "("
+ def nud(self, parser):
+ expr = parser.expression()
+ parser.advance(rparen_token)
+ return expr
+
+class rparen_token(object):
+ ")"
+
+class end_token(object):
+ """always ends parsing"""
+
+### derived literal tokens
+
+class bool_token(literal_token):
+ def __init__(self, value):
+ value = {'true':True, 'false':False}[value]
+ literal_token.__init__(self, value)
+
+class int_token(literal_token):
+ def __init__(self, value):
+ literal_token.__init__(self, int(value))
+
+class string_token(literal_token):
+ def __init__(self, value):
+ literal_token.__init__(self, value[1:-1])
+
+precedence = [(end_token, rparen_token),
+ (or_op_token,),
+ (and_op_token,),
+ (eq_op_token, neq_op_token),
+ (lparen_token,),
+ ]
+for index, rank in enumerate(precedence):
+ for token in rank:
+ token.lbp = index # lbp = lowest left binding power
+
+class ParseError(Exception):
+ """errror parsing conditional expression"""
+
+class ExpressionParser(object):
+ def __init__(self, text, valuemapping, strict=False):
+ """
+ Initialize the parser with input |text|, and |valuemapping| as
+ a dict mapping identifier names to values.
+ """
+ self.text = text
+ self.valuemapping = valuemapping
+ self.strict = strict
+
+ def _tokenize(self):
+ """
+ Lex the input text into tokens and yield them in sequence.
+ """
+ # scanner callbacks
+ def bool_(scanner, t): return bool_token(t)
+ def identifier(scanner, t): return ident_token(t)
+ def integer(scanner, t): return int_token(t)
+ def eq(scanner, t): return eq_op_token()
+ def neq(scanner, t): return neq_op_token()
+ def or_(scanner, t): return or_op_token()
+ def and_(scanner, t): return and_op_token()
+ def lparen(scanner, t): return lparen_token()
+ def rparen(scanner, t): return rparen_token()
+ def string_(scanner, t): return string_token(t)
+ def not_(scanner, t): return not_op_token()
+
+ scanner = re.Scanner([
+ (r"true|false", bool_),
+ (r"[a-zA-Z_]\w*", identifier),
+ (r"[0-9]+", integer),
+ (r'("[^"]*")|(\'[^\']*\')', string_),
+ (r"==", eq),
+ (r"!=", neq),
+ (r"\|\|", or_),
+ (r"!", not_),
+ (r"&&", and_),
+ (r"\(", lparen),
+ (r"\)", rparen),
+ (r"\s+", None), # skip whitespace
+ ])
+ tokens, remainder = scanner.scan(self.text)
+ for t in tokens:
+ yield t
+ yield end_token()
+
+ def value(self, ident):
+ """
+ Look up the value of |ident| in the value mapping passed in the
+ constructor.
+ """
+ if self.strict:
+ return self.valuemapping[ident]
+ else:
+ return self.valuemapping.get(ident, None)
+
+ def advance(self, expected):
+ """
+ Assert that the next token is an instance of |expected|, and advance
+ to the next token.
+ """
+ if not isinstance(self.token, expected):
+ raise Exception, "Unexpected token!"
+ self.token = self.iter.next()
+
+ def expression(self, rbp=0):
+ """
+ Parse and return the value of an expression until a token with
+ right binding power greater than rbp is encountered.
+ """
+ t = self.token
+ self.token = self.iter.next()
+ left = t.nud(self)
+ while rbp < self.token.lbp:
+ t = self.token
+ self.token = self.iter.next()
+ left = t.led(self, left)
+ return left
+
+ def parse(self):
+ """
+ Parse and return the value of the expression in the text
+ passed to the constructor. Raises a ParseError if the expression
+ could not be parsed.
+ """
+ try:
+ self.iter = self._tokenize()
+ self.token = self.iter.next()
+ return self.expression()
+ except:
+ raise ParseError("could not parse: %s; variables: %s" % (self.text, self.valuemapping))
+
+ __call__ = parse
+
+def parse(text, **values):
+ """
+ Parse and evaluate a boolean expression in |text|. Use |values| to look
+ up the value of identifiers referenced in the expression. Returns the final
+ value of the expression. A ParseError will be raised if parsing fails.
+ """
+ return ExpressionParser(text, values).parse()
+
+def normalize_path(path):
+ """normalize a relative path"""
+ if sys.platform.startswith('win'):
+ return path.replace('/', os.path.sep)
+ return path
+
+def denormalize_path(path):
+ """denormalize a relative path"""
+ if sys.platform.startswith('win'):
+ return path.replace(os.path.sep, '/')
+ return path
+
+
+def read_ini(fp, variables=None, default='DEFAULT',
+ comments=';#', separators=('=', ':'),
+ strict=True):
+ """
+ read an .ini file and return a list of [(section, values)]
+ - fp : file pointer or path to read
+ - variables : default set of variables
+ - default : name of the section for the default section
+ - comments : characters that if they start a line denote a comment
+ - separators : strings that denote key, value separation in order
+ - strict : whether to be strict about parsing
+ """
+
+ if variables is None:
+ variables = {}
+
+ if isinstance(fp, basestring):
+ fp = file(fp)
+
+ sections = []
+ key = value = None
+ section_names = set([])
+
+ # read the lines
+ for line in fp.readlines():
+
+ stripped = line.strip()
+
+ # ignore blank lines
+ if not stripped:
+ # reset key and value to avoid continuation lines
+ key = value = None
+ continue
+
+ # ignore comment lines
+ if stripped[0] in comments:
+ continue
+
+ # check for a new section
+ if len(stripped) > 2 and stripped[0] == '[' and stripped[-1] == ']':
+ section = stripped[1:-1].strip()
+ key = value = None
+
+ # deal with DEFAULT section
+ if section.lower() == default.lower():
+ if strict:
+ assert default not in section_names
+ section_names.add(default)
+ current_section = variables
+ continue
+
+ if strict:
+ # make sure this section doesn't already exist
+ assert section not in section_names, "Section '%s' already found in '%s'" % (section, section_names)
+
+ section_names.add(section)
+ current_section = {}
+ sections.append((section, current_section))
+ continue
+
+ # if there aren't any sections yet, something bad happen
+ if not section_names:
+ raise Exception('No sections found')
+
+ # (key, value) pair
+ for separator in separators:
+ if separator in stripped:
+ key, value = stripped.split(separator, 1)
+ key = key.strip()
+ value = value.strip()
+
+ if strict:
+ # make sure this key isn't already in the section or empty
+ assert key
+ if current_section is not variables:
+ assert key not in current_section
+
+ current_section[key] = value
+ break
+ else:
+ # continuation line ?
+ if line[0].isspace() and key:
+ value = '%s%s%s' % (value, os.linesep, stripped)
+ current_section[key] = value
+ else:
+ # something bad happen!
+ raise Exception("Not sure what you're trying to do")
+
+ # interpret the variables
+ def interpret_variables(global_dict, local_dict):
+ variables = global_dict.copy()
+ variables.update(local_dict)
+ return variables
+
+ sections = [(i, interpret_variables(variables, j)) for i, j in sections]
+ return sections
+
+
+### objects for parsing manifests
+
+class ManifestParser(object):
+ """read .ini manifests"""
+
+ ### methods for reading manifests
+
+ def __init__(self, manifests=(), defaults=None, strict=True):
+ self._defaults = defaults or {}
+ self.tests = []
+ self.strict = strict
+ self.rootdir = None
+ self.relativeRoot = None
+ if manifests:
+ self.read(*manifests)
+
+ def getRelativeRoot(self, root):
+ return root
+
+ def _read(self, root, filename, defaults):
+
+ # get directory of this file
+ here = os.path.dirname(os.path.abspath(filename))
+ defaults['here'] = here
+
+ # read the configuration
+ sections = read_ini(fp=filename, variables=defaults, strict=self.strict)
+
+ # get the tests
+ for section, data in sections:
+
+ # a file to include
+ # TODO: keep track of included file structure:
+ # self.manifests = {'manifest.ini': 'relative/path.ini'}
+ if section.startswith('include:'):
+ include_file = section.split('include:', 1)[-1]
+ include_file = normalize_path(include_file)
+ if not os.path.isabs(include_file):
+ include_file = os.path.join(self.getRelativeRoot(here), include_file)
+ if not os.path.exists(include_file):
+ if self.strict:
+ raise IOError("File '%s' does not exist" % include_file)
+ else:
+ continue
+ include_defaults = data.copy()
+ self._read(root, include_file, include_defaults)
+ continue
+
+ # otherwise an item
+ test = data
+ test['name'] = section
+ test['manifest'] = os.path.abspath(filename)
+
+ # determine the path
+ path = test.get('path', section)
+ _relpath = path
+ if '://' not in path: # don't futz with URLs
+ path = normalize_path(path)
+ if not os.path.isabs(path):
+ path = os.path.join(here, path)
+ _relpath = relpath(path, self.rootdir)
+
+ test['path'] = path
+ test['relpath'] = _relpath
+
+ # append the item
+ self.tests.append(test)
+
+ def read(self, *filenames, **defaults):
+
+ # ensure all files exist
+ missing = [ filename for filename in filenames
+ if not os.path.exists(filename) ]
+ if missing:
+ raise IOError('Missing files: %s' % ', '.join(missing))
+
+ # process each file
+ for filename in filenames:
+
+ # set the per file defaults
+ defaults = defaults.copy() or self._defaults.copy()
+ here = os.path.dirname(os.path.abspath(filename))
+ defaults['here'] = here
+
+ if self.rootdir is None:
+ # set the root directory
+ # == the directory of the first manifest given
+ self.rootdir = here
+
+ self._read(here, filename, defaults)
+
+ ### methods for querying manifests
+
+ def query(self, *checks, **kw):
+ """
+ general query function for tests
+ - checks : callable conditions to test if the test fulfills the query
+ """
+ tests = kw.get('tests', None)
+ if tests is None:
+ tests = self.tests
+ retval = []
+ for test in tests:
+ for check in checks:
+ if not check(test):
+ break
+ else:
+ retval.append(test)
+ return retval
+
+ def get(self, _key=None, inverse=False, tags=None, tests=None, **kwargs):
+ # TODO: pass a dict instead of kwargs since you might hav
+ # e.g. 'inverse' as a key in the dict
+
+ # TODO: tags should just be part of kwargs with None values
+ # (None == any is kinda weird, but probably still better)
+
+ # fix up tags
+ if tags:
+ tags = set(tags)
+ else:
+ tags = set()
+
+ # make some check functions
+ if inverse:
+ has_tags = lambda test: not tags.intersection(test.keys())
+ def dict_query(test):
+ for key, value in kwargs.items():
+ if test.get(key) == value:
+ return False
+ return True
+ else:
+ has_tags = lambda test: tags.issubset(test.keys())
+ def dict_query(test):
+ for key, value in kwargs.items():
+ if test.get(key) != value:
+ return False
+ return True
+
+ # query the tests
+ tests = self.query(has_tags, dict_query, tests=tests)
+
+ # if a key is given, return only a list of that key
+ # useful for keys like 'name' or 'path'
+ if _key:
+ return [test[_key] for test in tests]
+
+ # return the tests
+ return tests
+
+ def missing(self, tests=None):
+ """return list of tests that do not exist on the filesystem"""
+ if tests is None:
+ tests = self.tests
+ return [test for test in tests
+ if not os.path.exists(test['path'])]
+
+ def manifests(self, tests=None):
+ """
+ return manifests in order in which they appear in the tests
+ """
+ if tests is None:
+ tests = self.tests
+ manifests = []
+ for test in tests:
+ manifest = test.get('manifest')
+ if not manifest:
+ continue
+ if manifest not in manifests:
+ manifests.append(manifest)
+ return manifests
+
+ ### methods for outputting from manifests
+
+ def write(self, fp=sys.stdout, rootdir=None,
+ global_tags=None, global_kwargs=None,
+ local_tags=None, local_kwargs=None):
+ """
+ write a manifest given a query
+ global and local options will be munged to do the query
+ globals will be written to the top of the file
+ locals (if given) will be written per test
+ """
+
+ # root directory
+ if rootdir is None:
+ rootdir = self.rootdir
+
+ # sanitize input
+ global_tags = global_tags or set()
+ local_tags = local_tags or set()
+ global_kwargs = global_kwargs or {}
+ local_kwargs = local_kwargs or {}
+
+ # create the query
+ tags = set([])
+ tags.update(global_tags)
+ tags.update(local_tags)
+ kwargs = {}
+ kwargs.update(global_kwargs)
+ kwargs.update(local_kwargs)
+
+ # get matching tests
+ tests = self.get(tags=tags, **kwargs)
+
+ # print the .ini manifest
+ if global_tags or global_kwargs:
+ print >> fp, '[DEFAULT]'
+ for tag in global_tags:
+ print >> fp, '%s =' % tag
+ for key, value in global_kwargs.items():
+ print >> fp, '%s = %s' % (key, value)
+ print >> fp
+
+ for test in tests:
+ test = test.copy() # don't overwrite
+
+ path = test['name']
+ if not os.path.isabs(path):
+ path = test['path']
+ if self.rootdir:
+ path = relpath(test['path'], self.rootdir)
+ path = denormalize_path(path)
+ print >> fp, '[%s]' % path
+
+ # reserved keywords:
+ reserved = ['path', 'name', 'here', 'manifest', 'relpath']
+ for key in sorted(test.keys()):
+ if key in reserved:
+ continue
+ if key in global_kwargs:
+ continue
+ if key in global_tags and not test[key]:
+ continue
+ print >> fp, '%s = %s' % (key, test[key])
+ print >> fp
+
+ def copy(self, directory, rootdir=None, *tags, **kwargs):
+ """
+ copy the manifests and associated tests
+ - directory : directory to copy to
+ - rootdir : root directory to copy to (if not given from manifests)
+ - tags : keywords the tests must have
+ - kwargs : key, values the tests must match
+ """
+ # XXX note that copy does *not* filter the tests out of the
+ # resulting manifest; it just stupidly copies them over.
+ # ideally, it would reread the manifests and filter out the
+ # tests that don't match *tags and **kwargs
+
+ # destination
+ if not os.path.exists(directory):
+ os.path.makedirs(directory)
+ else:
+ # sanity check
+ assert os.path.isdir(directory)
+
+ # tests to copy
+ tests = self.get(tags=tags, **kwargs)
+ if not tests:
+ return # nothing to do!
+
+ # root directory
+ if rootdir is None:
+ rootdir = self.rootdir
+
+ # copy the manifests + tests
+ manifests = [relpath(manifest, rootdir) for manifest in self.manifests()]
+ for manifest in manifests:
+ destination = os.path.join(directory, manifest)
+ dirname = os.path.dirname(destination)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ else:
+ # sanity check
+ assert os.path.isdir(dirname)
+ shutil.copy(os.path.join(rootdir, manifest), destination)
+ for test in tests:
+ if os.path.isabs(test['name']):
+ continue
+ source = test['path']
+ if not os.path.exists(source):
+ print >> sys.stderr, "Missing test: '%s' does not exist!" % source
+ continue
+ # TODO: should err on strict
+ destination = os.path.join(directory, relpath(test['path'], rootdir))
+ shutil.copy(source, destination)
+ # TODO: ensure that all of the tests are below the from_dir
+
+ def update(self, from_dir, rootdir=None, *tags, **kwargs):
+ """
+ update the tests as listed in a manifest from a directory
+ - from_dir : directory where the tests live
+ - rootdir : root directory to copy to (if not given from manifests)
+ - tags : keys the tests must have
+ - kwargs : key, values the tests must match
+ """
+
+ # get the tests
+ tests = self.get(tags=tags, **kwargs)
+
+ # get the root directory
+ if not rootdir:
+ rootdir = self.rootdir
+
+ # copy them!
+ for test in tests:
+ if not os.path.isabs(test['name']):
+ _relpath = relpath(test['path'], rootdir)
+ source = os.path.join(from_dir, _relpath)
+ if not os.path.exists(source):
+ # TODO err on strict
+ print >> sys.stderr, "Missing test: '%s'; skipping" % test['name']
+ continue
+ destination = os.path.join(rootdir, _relpath)
+ shutil.copy(source, destination)
+
+
+class TestManifest(ManifestParser):
+ """
+ apply logic to manifests; this is your integration layer :)
+ specific harnesses may subclass from this if they need more logic
+ """
+
+ def filter(self, values, tests):
+ """
+ filter on a specific list tag, e.g.:
+ run-if = os == win linux
+ skip-if = os == mac
+ """
+
+ # tags:
+ run_tag = 'run-if'
+ skip_tag = 'skip-if'
+ fail_tag = 'fail-if'
+
+ # loop over test
+ for test in tests:
+ reason = None # reason to disable
+
+ # tagged-values to run
+ if run_tag in test:
+ condition = test[run_tag]
+ if not parse(condition, **values):
+ reason = '%s: %s' % (run_tag, condition)
+
+ # tagged-values to skip
+ if skip_tag in test:
+ condition = test[skip_tag]
+ if parse(condition, **values):
+ reason = '%s: %s' % (skip_tag, condition)
+
+ # mark test as disabled if there's a reason
+ if reason:
+ test.setdefault('disabled', reason)
+
+ # mark test as a fail if so indicated
+ if fail_tag in test:
+ condition = test[fail_tag]
+ if parse(condition, **values):
+ test['expected'] = 'fail'
+
+ def active_tests(self, exists=True, disabled=True, **values):
+ """
+ - exists : return only existing tests
+ - disabled : whether to return disabled tests
+ - tags : keys and values to filter on (e.g. `os = linux mac`)
+ """
+
+ tests = [i.copy() for i in self.tests] # shallow copy
+
+ # mark all tests as passing unless indicated otherwise
+ for test in tests:
+ test['expected'] = test.get('expected', 'pass')
+
+ # ignore tests that do not exist
+ if exists:
+ tests = [test for test in tests if os.path.exists(test['path'])]
+
+ # filter by tags
+ self.filter(values, tests)
+
+ # ignore disabled tests if specified
+ if not disabled:
+ tests = [test for test in tests
+ if not 'disabled' in test]
+
+ # return active tests
+ return tests
+
+ def test_paths(self):
+ return [test['path'] for test in self.active_tests()]
+
+
+### utility function(s); probably belongs elsewhere
+
+def convert(directories, pattern=None, ignore=(), write=None):
+ """
+ convert directories to a simple manifest
+ """
+
+ retval = []
+ include = []
+ for directory in directories:
+ for dirpath, dirnames, filenames in os.walk(directory):
+
+ # filter out directory names
+ dirnames = [ i for i in dirnames if i not in ignore ]
+ dirnames.sort()
+
+ # reference only the subdirectory
+ _dirpath = dirpath
+ dirpath = dirpath.split(directory, 1)[-1].strip(os.path.sep)
+
+ if dirpath.split(os.path.sep)[0] in ignore:
+ continue
+
+ # filter by glob
+ if pattern:
+ filenames = [filename for filename in filenames
+ if fnmatch(filename, pattern)]
+
+ filenames.sort()
+
+ # write a manifest for each directory
+ if write and (dirnames or filenames):
+ manifest = file(os.path.join(_dirpath, write), 'w')
+ for dirname in dirnames:
+ print >> manifest, '[include:%s]' % os.path.join(dirname, write)
+ for filename in filenames:
+ print >> manifest, '[%s]' % filename
+ manifest.close()
+
+ # add to the list
+ retval.extend([denormalize_path(os.path.join(dirpath, filename))
+ for filename in filenames])
+
+ if write:
+ return # the manifests have already been written!
+
+ retval.sort()
+ retval = ['[%s]' % filename for filename in retval]
+ return '\n'.join(retval)
+
+### command line attributes
+
+class ParserError(Exception):
+ """error for exceptions while parsing the command line"""
+
+def parse_args(_args):
+ """
+ parse and return:
+ --keys=value (or --key value)
+ -tags
+ args
+ """
+
+ # return values
+ _dict = {}
+ tags = []
+ args = []
+
+ # parse the arguments
+ key = None
+ for arg in _args:
+ if arg.startswith('---'):
+ raise ParserError("arguments should start with '-' or '--' only")
+ elif arg.startswith('--'):
+ if key:
+ raise ParserError("Key %s still open" % key)
+ key = arg[2:]
+ if '=' in key:
+ key, value = key.split('=', 1)
+ _dict[key] = value
+ key = None
+ continue
+ elif arg.startswith('-'):
+ if key:
+ raise ParserError("Key %s still open" % key)
+ tags.append(arg[1:])
+ continue
+ else:
+ if key:
+ _dict[key] = arg
+ continue
+ args.append(arg)
+
+ # return values
+ return (_dict, tags, args)
+
+
+### classes for subcommands
+
+class CLICommand(object):
+ usage = '%prog [options] command'
+ def __init__(self, parser):
+ self._parser = parser # master parser
+ def parser(self):
+ return OptionParser(usage=self.usage, description=self.__doc__,
+ add_help_option=False)
+
+class Copy(CLICommand):
+ usage = '%prog [options] copy manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...'
+ def __call__(self, options, args):
+ # parse the arguments
+ try:
+ kwargs, tags, args = parse_args(args)
+ except ParserError, e:
+ self._parser.error(e.message)
+
+ # make sure we have some manifests, otherwise it will
+ # be quite boring
+ if not len(args) == 2:
+ HelpCLI(self._parser)(options, ['copy'])
+ return
+
+ # read the manifests
+ # TODO: should probably ensure these exist here
+ manifests = ManifestParser()
+ manifests.read(args[0])
+
+ # print the resultant query
+ manifests.copy(args[1], None, *tags, **kwargs)
+
+
+class CreateCLI(CLICommand):
+ """
+ create a manifest from a list of directories
+ """
+ usage = '%prog [options] create directory <directory> <...>'
+
+ def parser(self):
+ parser = CLICommand.parser(self)
+ parser.add_option('-p', '--pattern', dest='pattern',
+ help="glob pattern for files")
+ parser.add_option('-i', '--ignore', dest='ignore',
+ default=[], action='append',
+ help='directories to ignore')
+ parser.add_option('-w', '--in-place', dest='in_place',
+ help='Write .ini files in place; filename to write to')
+ return parser
+
+ def __call__(self, _options, args):
+ parser = self.parser()
+ options, args = parser.parse_args(args)
+
+ # need some directories
+ if not len(args):
+ parser.print_usage()
+ return
+
+ # add the directories to the manifest
+ for arg in args:
+ assert os.path.exists(arg)
+ assert os.path.isdir(arg)
+ manifest = convert(args, pattern=options.pattern, ignore=options.ignore,
+ write=options.in_place)
+ if manifest:
+ print manifest
+
+
+class WriteCLI(CLICommand):
+ """
+ write a manifest based on a query
+ """
+ usage = '%prog [options] write manifest <manifest> -tag1 -tag2 --key1=value1 --key2=value2 ...'
+ def __call__(self, options, args):
+
+ # parse the arguments
+ try:
+ kwargs, tags, args = parse_args(args)
+ except ParserError, e:
+ self._parser.error(e.message)
+
+ # make sure we have some manifests, otherwise it will
+ # be quite boring
+ if not args:
+ HelpCLI(self._parser)(options, ['write'])
+ return
+
+ # read the manifests
+ # TODO: should probably ensure these exist here
+ manifests = ManifestParser()
+ manifests.read(*args)
+
+ # print the resultant query
+ manifests.write(global_tags=tags, global_kwargs=kwargs)
+
+
+class HelpCLI(CLICommand):
+ """
+ get help on a command
+ """
+ usage = '%prog [options] help [command]'
+
+ def __call__(self, options, args):
+ if len(args) == 1 and args[0] in commands:
+ commands[args[0]](self._parser).parser().print_help()
+ else:
+ self._parser.print_help()
+ print '\nCommands:'
+ for command in sorted(commands):
+ print ' %s : %s' % (command, commands[command].__doc__.strip())
+
+class UpdateCLI(CLICommand):
+ """
+ update the tests as listed in a manifest from a directory
+ """
+ usage = '%prog [options] update manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...'
+
+ def __call__(self, options, args):
+ # parse the arguments
+ try:
+ kwargs, tags, args = parse_args(args)
+ except ParserError, e:
+ self._parser.error(e.message)
+
+ # make sure we have some manifests, otherwise it will
+ # be quite boring
+ if not len(args) == 2:
+ HelpCLI(self._parser)(options, ['update'])
+ return
+
+ # read the manifests
+ # TODO: should probably ensure these exist here
+ manifests = ManifestParser()
+ manifests.read(args[0])
+
+ # print the resultant query
+ manifests.update(args[1], None, *tags, **kwargs)
+
+
+# command -> class mapping
+commands = { 'create': CreateCLI,
+ 'help': HelpCLI,
+ 'update': UpdateCLI,
+ 'write': WriteCLI }
+
+def main(args=sys.argv[1:]):
+ """console_script entry point"""
+
+ # set up an option parser
+ usage = '%prog [options] [command] ...'
+ description = "%s. Use `help` to display commands" % __doc__.strip()
+ parser = OptionParser(usage=usage, description=description)
+ parser.add_option('-s', '--strict', dest='strict',
+ action='store_true', default=False,
+ help='adhere strictly to errors')
+ parser.disable_interspersed_args()
+
+ options, args = parser.parse_args(args)
+
+ if not args:
+ HelpCLI(parser)(options, args)
+ parser.exit()
+
+ # get the command
+ command = args[0]
+ if command not in commands:
+ parser.error("Command must be one of %s (you gave '%s')" % (', '.join(sorted(commands.keys())), command))
+
+ handler = commands[command](parser)
+ handler(options, args[1:])
+
+if __name__ == '__main__':
+ main()
Property changes on: third_party/manifestdestiny/manifestparser/manifestparser.py
___________________________________________________________________
Added: svn:executable
+ *
Added: svn:eol-style
+ LF
« 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