diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 861092308c0f29dec9b202634df1b25055d00d0d..0000000000000000000000000000000000000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: python -python: - - 2.7 -script: make tests -notifications: - email: false \ No newline at end of file diff --git a/bin/compile b/bin/compile index f49f2800c03ba5c7107765f93c4813bcc414be7c..de12e1b3efb5a0199a2dcf015dac6206f7ba34ff 100755 --- a/bin/compile +++ b/bin/compile @@ -37,6 +37,9 @@ LOGPLEX_KEY="t.b90d9d29-5388-4908-9737-b4576af1d4ce" export BPWATCH_STORE_PATH=$CACHE_DIR/bpwatch.json BUILDPACK_VERSION=v28 +# Setup pip-pop (pip-diff) +export PATH=$PATH:$ROOT_DIR/vendor/pip-pop + # Support Anvil Build_IDs [ ! "$SLUG_ID" ] && SLUG_ID="defaultslug" [ ! "$REQUEST_ID" ] && REQUEST_ID=$SLUG_ID @@ -144,115 +147,24 @@ set -e mkdir -p $(dirname $PROFILE_PATH) -set +e -PYTHON_VERSION=$(cat runtime.txt) - # Install Python. -if [ -f .heroku/python-version ]; then - if [ ! $(cat .heroku/python-version) = $PYTHON_VERSION ]; then - bpwatch start uninstall_python - puts-step "Found runtime $(cat .heroku/python-version), removing" - rm -fr .heroku/python - bpwatch stop uninstall_python - else - SKIP_INSTALL=1 - fi -fi - -if [ ! $STACK = $CACHED_PYTHON_STACK ]; then - bpwatch start uninstall_python - puts-step "Stack changed, re-installing runtime" - rm -fr .heroku/python - unset SKIP_INSTALL - bpwatch stop uninstall_python -fi - - -if [ ! "$SKIP_INSTALL" ]; then - bpwatch start install_python - puts-step "Installing runtime ($PYTHON_VERSION)" - - # Prepare destination directory. - mkdir -p .heroku/python - - curl http://lang-python.s3.amazonaws.com/$STACK/runtimes/$PYTHON_VERSION.tar.gz -s | tar zxv -C .heroku/python &> /dev/null - if [[ $? != 0 ]] ; then - puts-warn "Requested runtime ($PYTHON_VERSION) is not available for this stack ($STACK)." - puts-warn "Aborting. More info: https://devcenter.heroku.com/articles/python-support" - exit 1 - fi +source $BIN_DIR/steps/python - bpwatch stop install_python +# Uninstall removed dependencies with Pip. +source $BIN_DIR/steps/pip-uninstall - # Record for future reference. - echo $PYTHON_VERSION > .heroku/python-version - echo $STACK > .heroku/python-stack - FRESH_PYTHON=true - - hash -r -fi - -# If Pip isn't up to date: -if [ "$FRESH_PYTHON" ] || [[ ! $(pip --version) == *$PIP_VERSION* ]]; then - WORKING_DIR=$(pwd) - - bpwatch start prepare_environment - - bpwatch start install_setuptools - # Prepare it for the real world - # puts-step "Installing Setuptools ($SETUPTOOLS_VERSION)" - cd $ROOT_DIR/vendor/ - tar zxf setuptools-$SETUPTOOLS_VERSION.tar.gz - cd $ROOT_DIR/vendor/setuptools-$SETUPTOOLS_VERSION/ - python setup.py install &> /dev/null - cd $WORKING_DIR - bpwatch stop install_setuptoools - - bpwatch start install_pip - # puts-step "Installing Pip ($PIP_VERSION)" - - cd $ROOT_DIR/vendor/ - tar zxf pip-$PIP_VERSION.tar.gz - cd $ROOT_DIR/vendor/pip-$PIP_VERSION/ - python setup.py install &> /dev/null - cd $WORKING_DIR - - bpwatch stop install_pip - bpwatch stop prepare_environment -fi - -set -e -hash -r +# Mercurial support. +source $BIN_DIR/steps/mercurial # Pylibmc support. -# See [`bin/steps/pylibmc`](pylibmc.html). -bpwatch start pylibmc_install - source $BIN_DIR/steps/pylibmc -bpwatch stop pylibmc_install - -# Install Mercurial if it appears to be required. -if (grep -Fiq "hg+" requirements.txt) then - bpwatch start mercurial_install - /app/.heroku/python/bin/pip install mercurial | cleanup | indent - bpwatch stop mercurial_install -fi +source $BIN_DIR/steps/pylibmc # Install dependencies with Pip. -puts-step "Installing dependencies with pip" - - -[ ! "$FRESH_PYTHON" ] && bpwatch start pip_install -[ "$FRESH_PYTHON" ] && bpwatch start pip_install_first - -/app/.heroku/python/bin/pip install -r requirements.txt --exists-action=w --src=./.heroku/src --allow-all-external | cleanup | indent - -[ ! "$FRESH_PYTHON" ] && bpwatch stop pip_install -[ "$FRESH_PYTHON" ] && bpwatch stop pip_install_first +source $BIN_DIR/steps/pip-install # Django collectstatic support. -bpwatch start collectstatic - sub-env $BIN_DIR/steps/collectstatic -bpwatch stop collectstatic +source $BIN_DIR/steps/collectstatic + # ### Finalize # diff --git a/bin/steps/collectstatic b/bin/steps/collectstatic index 0821fd2a9881365272796dfd1c15c0b6b82dba72..134b5c6947f897296323b5bccd6c84edc4d94773 100755 --- a/bin/steps/collectstatic +++ b/bin/steps/collectstatic @@ -7,6 +7,8 @@ MANAGE_FILE=${MANAGE_FILE:-fakepath} [ -f .heroku/collectstatic_disabled ] && DISABLE_COLLECTSTATIC=1 +bpwatch start collectstatic + if [ ! "$DISABLE_COLLECTSTATIC" ] && [ -f "$MANAGE_FILE" ]; then set +e @@ -28,9 +30,7 @@ if [ ! "$DISABLE_COLLECTSTATIC" ] && [ -f "$MANAGE_FILE" ]; then echo " Collectstatic configuration error. To debug, run:" echo " $ heroku run python $MANAGE_FILE collectstatic --noinput" fi - echo - - fi +bpwatch stop collectstatic \ No newline at end of file diff --git a/bin/steps/mercurial b/bin/steps/mercurial new file mode 100755 index 0000000000000000000000000000000000000000..505aad603b7e14e26173180f1b76437a1d724b10 --- /dev/null +++ b/bin/steps/mercurial @@ -0,0 +1,6 @@ +# Install Mercurial if it appears to be required. +if (grep -Fiq "hg+" requirements.txt) then + bpwatch start mercurial_install + /app/.heroku/python/bin/pip install mercurial | cleanup | indent + bpwatch stop mercurial_install +fi \ No newline at end of file diff --git a/bin/steps/pip-install b/bin/steps/pip-install new file mode 100755 index 0000000000000000000000000000000000000000..00aabd79d71565766f6a7729e0dd5d533a0b158f --- /dev/null +++ b/bin/steps/pip-install @@ -0,0 +1,16 @@ +# Install dependencies with Pip. +puts-step "Installing dependencies with pip" + +[ ! "$FRESH_PYTHON" ] && bpwatch start pip_install +[ "$FRESH_PYTHON" ] && bpwatch start pip_install_first + +/app/.heroku/python/bin/pip install -r requirements.txt --exists-action=w --src=./.heroku/src --allow-all-external | cleanup | indent + +# Smart Requirements handling +cp requirements.txt .heroku/python/requirements-declared.txt +/app/.heroku/python/bin/pip freeze > .heroku/python/requirements-installed.txt + +[ ! "$FRESH_PYTHON" ] && bpwatch stop pip_install +[ "$FRESH_PYTHON" ] && bpwatch stop pip_install_first + +echo \ No newline at end of file diff --git a/bin/steps/pip-uninstall b/bin/steps/pip-uninstall new file mode 100755 index 0000000000000000000000000000000000000000..312d425010c2a268f4d8b830f4a6cf97ddf0481b --- /dev/null +++ b/bin/steps/pip-uninstall @@ -0,0 +1,14 @@ +set +e +# Install dependencies with Pip. +bpwatch start pip_uninstall +if [[ -f .heroku/python/requirements-declared.txt ]]; then + + pip-diff --stale .heroku/python/requirements-declared.txt requirements.txt > .heroku/python/requirements-stale.txt + + if [[ -s .heroku/python/requirements-stale.txt ]]; then + puts-step "Uninstalling stale dependencies" + /app/.heroku/python/bin/pip uninstall -r .heroku/python/requirements-stale.txt -y --exists-action=w | cleanup | indent + fi +fi +bpwatch stop pip_uninstall +set -e \ No newline at end of file diff --git a/bin/steps/pylibmc b/bin/steps/pylibmc index b6c0225dfdf70ac8f94283e30db9aa099a01fb80..05a13618268bbdbe4829112dc261c602d7949242 100755 --- a/bin/steps/pylibmc +++ b/bin/steps/pylibmc @@ -15,8 +15,10 @@ VENDORED_MEMCACHED="http://cl.ly/0a191R3K160t1w1P0N25/vendor-libmemcached.tar.gz # Syntax sugar. source $BIN_DIR/utils +bpwatch start pylibmc_install + # If pylibmc exists within requirements, use vendored libmemcached. -if (grep -Eiq "\s*pylibmc" requirements.txt) then +if (pip-grep -s requirements.txt pylibmc) then echo "-----> Noticed pylibmc. Bootstrapping libmemcached." cd .heroku @@ -34,5 +36,4 @@ if (grep -Eiq "\s*pylibmc" requirements.txt) then cd .. fi - - +bpwatch stop pylibmc_install diff --git a/bin/steps/python b/bin/steps/python new file mode 100755 index 0000000000000000000000000000000000000000..8d2384fa6ec5ae6b6bc7cceaf1fe6339add4b0d9 --- /dev/null +++ b/bin/steps/python @@ -0,0 +1,79 @@ +set +e +PYTHON_VERSION=$(cat runtime.txt) + +# Install Python. +if [ -f .heroku/python-version ]; then + if [ ! $(cat .heroku/python-version) = $PYTHON_VERSION ]; then + bpwatch start uninstall_python + puts-step "Found runtime $(cat .heroku/python-version), removing" + rm -fr .heroku/python + bpwatch stop uninstall_python + else + SKIP_INSTALL=1 + fi +fi + +if [ ! $STACK = $CACHED_PYTHON_STACK ]; then + bpwatch start uninstall_python + puts-step "Stack changed, re-installing runtime" + rm -fr .heroku/python + unset SKIP_INSTALL + bpwatch stop uninstall_python +fi + + +if [ ! "$SKIP_INSTALL" ]; then + bpwatch start install_python + puts-step "Installing runtime ($PYTHON_VERSION)" + + # Prepare destination directory. + mkdir -p .heroku/python + + curl http://lang-python.s3.amazonaws.com/$STACK/runtimes/$PYTHON_VERSION.tar.gz -s | tar zxv -C .heroku/python &> /dev/null + if [[ $? != 0 ]] ; then + puts-warn "Requested runtime ($PYTHON_VERSION) is not available for this stack ($STACK)." + puts-warn "Aborting. More info: https://devcenter.heroku.com/articles/python-support" + exit 1 + fi + + bpwatch stop install_python + + # Record for future reference. + echo $PYTHON_VERSION > .heroku/python-version + echo $STACK > .heroku/python-stack + FRESH_PYTHON=true + + hash -r +fi + +# If Pip isn't up to date: +if [ "$FRESH_PYTHON" ] || [[ ! $(pip --version) == *$PIP_VERSION* ]]; then + WORKING_DIR=$(pwd) + + bpwatch start prepare_environment + + bpwatch start install_setuptools + # Prepare it for the real world + # puts-step "Installing Setuptools ($SETUPTOOLS_VERSION)" + cd $ROOT_DIR/vendor/ + tar zxf setuptools-$SETUPTOOLS_VERSION.tar.gz + cd $ROOT_DIR/vendor/setuptools-$SETUPTOOLS_VERSION/ + python setup.py install &> /dev/null + cd $WORKING_DIR + bpwatch stop install_setuptoools + + bpwatch start install_pip + # puts-step "Installing Pip ($PIP_VERSION)" + + cd $ROOT_DIR/vendor/ + tar zxf pip-$PIP_VERSION.tar.gz + cd $ROOT_DIR/vendor/pip-$PIP_VERSION/ + python setup.py install &> /dev/null + cd $WORKING_DIR + + bpwatch stop install_pip + bpwatch stop prepare_environment +fi + +set -e +hash -r \ No newline at end of file diff --git a/vendor/pip-pop/docopt.py b/vendor/pip-pop/docopt.py new file mode 100644 index 0000000000000000000000000000000000000000..2e43f7cef8193b2273e687e27204c98115078d57 --- /dev/null +++ b/vendor/pip-pop/docopt.py @@ -0,0 +1,581 @@ +"""Pythonic command-line interface parser that will make you smile. + + * http://docopt.org + * Repository and issue-tracker: https://github.com/docopt/docopt + * Licensed under terms of MIT license (see LICENSE-MIT) + * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com + +""" +import sys +import re + + +__all__ = ['docopt'] +__version__ = '0.6.1' + + +class DocoptLanguageError(Exception): + + """Error in construction of usage-message by developer.""" + + +class DocoptExit(SystemExit): + + """Exit in case user invoked program with incorrect arguments.""" + + usage = '' + + def __init__(self, message=''): + SystemExit.__init__(self, (message + '\n' + self.usage).strip()) + + +class Pattern(object): + + def __eq__(self, other): + return repr(self) == repr(other) + + def __hash__(self): + return hash(repr(self)) + + def fix(self): + self.fix_identities() + self.fix_repeating_arguments() + return self + + def fix_identities(self, uniq=None): + """Make pattern-tree tips point to same object if they are equal.""" + if not hasattr(self, 'children'): + return self + uniq = list(set(self.flat())) if uniq is None else uniq + for i, child in enumerate(self.children): + if not hasattr(child, 'children'): + assert child in uniq + self.children[i] = uniq[uniq.index(child)] + else: + child.fix_identities(uniq) + + def fix_repeating_arguments(self): + """Fix elements that should accumulate/increment values.""" + either = [list(child.children) for child in transform(self).children] + for case in either: + for e in [child for child in case if case.count(child) > 1]: + if type(e) is Argument or type(e) is Option and e.argcount: + if e.value is None: + e.value = [] + elif type(e.value) is not list: + e.value = e.value.split() + if type(e) is Command or type(e) is Option and e.argcount == 0: + e.value = 0 + return self + + +def transform(pattern): + """Expand pattern into an (almost) equivalent one, but with single Either. + + Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) + Quirks: [-a] => (-a), (-a...) => (-a -a) + + """ + result = [] + groups = [[pattern]] + while groups: + children = groups.pop(0) + parents = [Required, Optional, OptionsShortcut, Either, OneOrMore] + if any(t in map(type, children) for t in parents): + child = [c for c in children if type(c) in parents][0] + children.remove(child) + if type(child) is Either: + for c in child.children: + groups.append([c] + children) + elif type(child) is OneOrMore: + groups.append(child.children * 2 + children) + else: + groups.append(child.children + children) + else: + result.append(children) + return Either(*[Required(*e) for e in result]) + + +class LeafPattern(Pattern): + + """Leaf/terminal node of a pattern tree.""" + + def __init__(self, name, value=None): + self.name, self.value = name, value + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) + + def flat(self, *types): + return [self] if not types or type(self) in types else [] + + def match(self, left, collected=None): + collected = [] if collected is None else collected + pos, match = self.single_match(left) + if match is None: + return False, left, collected + left_ = left[:pos] + left[pos + 1:] + same_name = [a for a in collected if a.name == self.name] + if type(self.value) in (int, list): + if type(self.value) is int: + increment = 1 + else: + increment = ([match.value] if type(match.value) is str + else match.value) + if not same_name: + match.value = increment + return True, left_, collected + [match] + same_name[0].value += increment + return True, left_, collected + return True, left_, collected + [match] + + +class BranchPattern(Pattern): + + """Branch/inner node of a pattern tree.""" + + def __init__(self, *children): + self.children = list(children) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, + ', '.join(repr(a) for a in self.children)) + + def flat(self, *types): + if type(self) in types: + return [self] + return sum([child.flat(*types) for child in self.children], []) + + +class Argument(LeafPattern): + + def single_match(self, left): + for n, pattern in enumerate(left): + if type(pattern) is Argument: + return n, Argument(self.name, pattern.value) + return None, None + + @classmethod + def parse(class_, source): + name = re.findall('(<\S*?>)', source)[0] + value = re.findall('\[default: (.*)\]', source, flags=re.I) + return class_(name, value[0] if value else None) + + +class Command(Argument): + + def __init__(self, name, value=False): + self.name, self.value = name, value + + def single_match(self, left): + for n, pattern in enumerate(left): + if type(pattern) is Argument: + if pattern.value == self.name: + return n, Command(self.name, True) + else: + break + return None, None + + +class Option(LeafPattern): + + def __init__(self, short=None, long=None, argcount=0, value=False): + assert argcount in (0, 1) + self.short, self.long, self.argcount = short, long, argcount + self.value = None if value is False and argcount else value + + @classmethod + def parse(class_, option_description): + short, long, argcount, value = None, None, 0, False + options, _, description = option_description.strip().partition(' ') + options = options.replace(',', ' ').replace('=', ' ') + for s in options.split(): + if s.startswith('--'): + long = s + elif s.startswith('-'): + short = s + else: + argcount = 1 + if argcount: + matched = re.findall('\[default: (.*)\]', description, flags=re.I) + value = matched[0] if matched else None + return class_(short, long, argcount, value) + + def single_match(self, left): + for n, pattern in enumerate(left): + if self.name == pattern.name: + return n, pattern + return None, None + + @property + def name(self): + return self.long or self.short + + def __repr__(self): + return 'Option(%r, %r, %r, %r)' % (self.short, self.long, + self.argcount, self.value) + + +class Required(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + l = left + c = collected + for pattern in self.children: + matched, l, c = pattern.match(l, c) + if not matched: + return False, left, collected + return True, l, c + + +class Optional(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + for pattern in self.children: + m, left, collected = pattern.match(left, collected) + return True, left, collected + + +class OptionsShortcut(Optional): + + """Marker/placeholder for [options] shortcut.""" + + +class OneOrMore(BranchPattern): + + def match(self, left, collected=None): + assert len(self.children) == 1 + collected = [] if collected is None else collected + l = left + c = collected + l_ = None + matched = True + times = 0 + while matched: + # could it be that something didn't match but changed l or c? + matched, l, c = self.children[0].match(l, c) + times += 1 if matched else 0 + if l_ == l: + break + l_ = l + if times >= 1: + return True, l, c + return False, left, collected + + +class Either(BranchPattern): + + def match(self, left, collected=None): + collected = [] if collected is None else collected + outcomes = [] + for pattern in self.children: + matched, _, _ = outcome = pattern.match(left, collected) + if matched: + outcomes.append(outcome) + if outcomes: + return min(outcomes, key=lambda outcome: len(outcome[1])) + return False, left, collected + + +class Tokens(list): + + def __init__(self, source, error=DocoptExit): + self += source.split() if hasattr(source, 'split') else source + self.error = error + + @staticmethod + def from_pattern(source): + source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source) + source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s] + return Tokens(source, error=DocoptLanguageError) + + def move(self): + return self.pop(0) if len(self) else None + + def current(self): + return self[0] if len(self) else None + + +def parse_long(tokens, options): + """long ::= '--' chars [ ( ' ' | '=' ) chars ] ;""" + long, eq, value = tokens.move().partition('=') + assert long.startswith('--') + value = None if eq == value == '' else value + similar = [o for o in options if o.long == long] + if tokens.error is DocoptExit and similar == []: # if no exact match + similar = [o for o in options if o.long and o.long.startswith(long)] + if len(similar) > 1: # might be simply specified ambiguously 2+ times? + raise tokens.error('%s is not a unique prefix: %s?' % + (long, ', '.join(o.long for o in similar))) + elif len(similar) < 1: + argcount = 1 if eq == '=' else 0 + o = Option(None, long, argcount) + options.append(o) + if tokens.error is DocoptExit: + o = Option(None, long, argcount, value if argcount else True) + else: + o = Option(similar[0].short, similar[0].long, + similar[0].argcount, similar[0].value) + if o.argcount == 0: + if value is not None: + raise tokens.error('%s must not have an argument' % o.long) + else: + if value is None: + if tokens.current() in [None, '--']: + raise tokens.error('%s requires argument' % o.long) + value = tokens.move() + if tokens.error is DocoptExit: + o.value = value if value is not None else True + return [o] + + +def parse_shorts(tokens, options): + """shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;""" + token = tokens.move() + assert token.startswith('-') and not token.startswith('--') + left = token.lstrip('-') + parsed = [] + while left != '': + short, left = '-' + left[0], left[1:] + similar = [o for o in options if o.short == short] + if len(similar) > 1: + raise tokens.error('%s is specified ambiguously %d times' % + (short, len(similar))) + elif len(similar) < 1: + o = Option(short, None, 0) + options.append(o) + if tokens.error is DocoptExit: + o = Option(short, None, 0, True) + else: # why copying is necessary here? + o = Option(short, similar[0].long, + similar[0].argcount, similar[0].value) + value = None + if o.argcount != 0: + if left == '': + if tokens.current() in [None, '--']: + raise tokens.error('%s requires argument' % short) + value = tokens.move() + else: + value = left + left = '' + if tokens.error is DocoptExit: + o.value = value if value is not None else True + parsed.append(o) + return parsed + + +def parse_pattern(source, options): + tokens = Tokens.from_pattern(source) + result = parse_expr(tokens, options) + if tokens.current() is not None: + raise tokens.error('unexpected ending: %r' % ' '.join(tokens)) + return Required(*result) + + +def parse_expr(tokens, options): + """expr ::= seq ( '|' seq )* ;""" + seq = parse_seq(tokens, options) + if tokens.current() != '|': + return seq + result = [Required(*seq)] if len(seq) > 1 else seq + while tokens.current() == '|': + tokens.move() + seq = parse_seq(tokens, options) + result += [Required(*seq)] if len(seq) > 1 else seq + return [Either(*result)] if len(result) > 1 else result + + +def parse_seq(tokens, options): + """seq ::= ( atom [ '...' ] )* ;""" + result = [] + while tokens.current() not in [None, ']', ')', '|']: + atom = parse_atom(tokens, options) + if tokens.current() == '...': + atom = [OneOrMore(*atom)] + tokens.move() + result += atom + return result + + +def parse_atom(tokens, options): + """atom ::= '(' expr ')' | '[' expr ']' | 'options' + | long | shorts | argument | command ; + """ + token = tokens.current() + result = [] + if token in '([': + tokens.move() + matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token] + result = pattern(*parse_expr(tokens, options)) + if tokens.move() != matching: + raise tokens.error("unmatched '%s'" % token) + return [result] + elif token == 'options': + tokens.move() + return [OptionsShortcut()] + elif token.startswith('--') and token != '--': + return parse_long(tokens, options) + elif token.startswith('-') and token not in ('-', '--'): + return parse_shorts(tokens, options) + elif token.startswith('<') and token.endswith('>') or token.isupper(): + return [Argument(tokens.move())] + else: + return [Command(tokens.move())] + + +def parse_argv(tokens, options, options_first=False): + """Parse command-line argument vector. + + If options_first: + argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; + else: + argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; + + """ + parsed = [] + while tokens.current() is not None: + if tokens.current() == '--': + return parsed + [Argument(None, v) for v in tokens] + elif tokens.current().startswith('--'): + parsed += parse_long(tokens, options) + elif tokens.current().startswith('-') and tokens.current() != '-': + parsed += parse_shorts(tokens, options) + elif options_first: + return parsed + [Argument(None, v) for v in tokens] + else: + parsed.append(Argument(None, tokens.move())) + return parsed + + +def parse_defaults(doc): + defaults = [] + for s in parse_section('options:', doc): + # FIXME corner case "bla: options: --foo" + _, _, s = s.partition(':') # get rid of "options:" + split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:] + split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] + options = [Option.parse(s) for s in split if s.startswith('-')] + defaults += options + return defaults + + +def parse_section(name, source): + pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)', + re.IGNORECASE | re.MULTILINE) + return [s.strip() for s in pattern.findall(source)] + + +def formal_usage(section): + _, _, section = section.partition(':') # drop "usage:" + pu = section.split() + return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )' + + +def extras(help, version, options, doc): + if help and any((o.name in ('-h', '--help')) and o.value for o in options): + print(doc.strip("\n")) + sys.exit() + if version and any(o.name == '--version' and o.value for o in options): + print(version) + sys.exit() + + +class Dict(dict): + def __repr__(self): + return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items())) + + +def docopt(doc, argv=None, help=True, version=None, options_first=False): + """Parse `argv` based on command-line interface described in `doc`. + + `docopt` creates your command-line interface based on its + description that you pass as `doc`. Such description can contain + --options, <positional-argument>, commands, which could be + [optional], (required), (mutually | exclusive) or repeated... + + Parameters + ---------- + doc : str + Description of your command-line interface. + argv : list of str, optional + Argument vector to be parsed. sys.argv[1:] is used if not + provided. + help : bool (default: True) + Set to False to disable automatic help on -h or --help + options. + version : any object + If passed, the object will be printed if --version is in + `argv`. + options_first : bool (default: False) + Set to True to require options precede positional arguments, + i.e. to forbid options and positional arguments intermix. + + Returns + ------- + args : dict + A dictionary, where keys are names of command-line elements + such as e.g. "--verbose" and "<path>", and values are the + parsed values of those elements. + + Example + ------- + >>> from docopt import docopt + >>> doc = ''' + ... Usage: + ... my_program tcp <host> <port> [--timeout=<seconds>] + ... my_program serial <port> [--baud=<n>] [--timeout=<seconds>] + ... my_program (-h | --help | --version) + ... + ... Options: + ... -h, --help Show this screen and exit. + ... --baud=<n> Baudrate [default: 9600] + ... ''' + >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30'] + >>> docopt(doc, argv) + {'--baud': '9600', + '--help': False, + '--timeout': '30', + '--version': False, + '<host>': '127.0.0.1', + '<port>': '80', + 'serial': False, + 'tcp': True} + + See also + -------- + * For video introduction see http://docopt.org + * Full documentation is available in README.rst as well as online + at https://github.com/docopt/docopt#readme + + """ + argv = sys.argv[1:] if argv is None else argv + + usage_sections = parse_section('usage:', doc) + if len(usage_sections) == 0: + raise DocoptLanguageError('"usage:" (case-insensitive) not found.') + if len(usage_sections) > 1: + raise DocoptLanguageError('More than one "usage:" (case-insensitive).') + DocoptExit.usage = usage_sections[0] + + options = parse_defaults(doc) + pattern = parse_pattern(formal_usage(DocoptExit.usage), options) + # [default] syntax for argument is disabled + #for a in pattern.flat(Argument): + # same_name = [d for d in arguments if d.name == a.name] + # if same_name: + # a.value = same_name[0].value + argv = parse_argv(Tokens(argv), list(options), options_first) + pattern_options = set(pattern.flat(Option)) + for options_shortcut in pattern.flat(OptionsShortcut): + doc_options = parse_defaults(doc) + options_shortcut.children = list(set(doc_options) - pattern_options) + #if any_options: + # options_shortcut.children += [Option(o.short, o.long, o.argcount) + # for o in argv if type(o) is Option] + extras(help, version, argv, doc) + matched, left, collected = pattern.fix().match(argv) + if matched and left == []: # better error message if left? + return Dict((a.name, a.value) for a in (pattern.flat() + collected)) + raise DocoptExit() diff --git a/vendor/pip-pop/pip-diff b/vendor/pip-pop/pip-diff new file mode 100755 index 0000000000000000000000000000000000000000..221b6bdf4ec1df5baf5c0b6db7072470f2bbf7f0 --- /dev/null +++ b/vendor/pip-pop/pip-diff @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Usage: + pip-diff (--fresh | --stale) <reqfile1> <reqfile2> + pip-diff (-h | --help) + +Options: + -h --help Show this screen. + --fresh List newly added packages. + --stale List removed packages. +""" +import os +from docopt import docopt +from pip.req import parse_requirements + +class Requirements(object): + def __init__(self, reqfile=None): + super(Requirements, self).__init__() + self.path = reqfile + self.requirements = [] + + if reqfile: + self.load(reqfile) + + def __repr__(self): + return '<Requirements \'{}\'>'.format(self.path) + + def load(self, reqfile): + + if not os.path.exists(reqfile): + raise ValueError('The given requirements file does not exist.') + + for requirement in parse_requirements(reqfile): + if requirement.req: + self.requirements.append(requirement.req) + + + def diff(self, requirements, ignore_versions=False): + r1 = self + r2 = requirements + results = {'fresh': [], 'stale': []} + + # Generate fresh packages. + other_reqs = ( + [r.project_name for r in r1.requirements] + if ignore_versions else r1.requirements + ) + + for req in r2.requirements: + r = req.project_name if ignore_versions else req + + if r not in other_reqs: + results['fresh'].append(req) + + # Generate stale packages. + other_reqs = ( + [r.project_name for r in r2.requirements] + if ignore_versions else r2.requirements + ) + + for req in r1.requirements: + r = req.project_name if ignore_versions else req + + if r not in other_reqs: + results['stale'].append(req) + + return results + + + + + +def diff(r1, r2, include_fresh=False, include_stale=False): + + include_versions = True if include_stale else False + + try: + r1 = Requirements(r1) + r2 = Requirements(r2) + except ValueError: + print 'There was a problem loading the given requirements files.' + exit(os.EX_NOINPUT) + + results = r1.diff(r2, ignore_versions=True) + + if include_fresh: + for line in results['fresh']: + print line.project_name if include_versions else line + + if include_stale: + for line in results['stale']: + print line.project_name if include_versions else line + + + +def main(): + args = docopt(__doc__, version='pip-diff') + + kwargs = { + 'r1': args['<reqfile1>'], + 'r2': args['<reqfile2>'], + 'include_fresh': args['--fresh'], + 'include_stale': args['--stale'] + } + + diff(**kwargs) + + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/vendor/pip-pop/pip-grep b/vendor/pip-pop/pip-grep new file mode 100755 index 0000000000000000000000000000000000000000..bf28300f2f83d89e59816ccae7c47e2d980eb62b --- /dev/null +++ b/vendor/pip-pop/pip-grep @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Usage: + pip-grep [-s] <reqfile> <package>... + +Options: + -h --help Show this screen. +""" +import os +from docopt import docopt +from pip.req import parse_requirements + +class Requirements(object): + def __init__(self, reqfile=None): + super(Requirements, self).__init__() + self.path = reqfile + self.requirements = [] + + if reqfile: + self.load(reqfile) + + def __repr__(self): + return '<Requirements \'{}\'>'.format(self.path) + + def load(self, reqfile): + + if not os.path.exists(reqfile): + raise ValueError('The given requirements file does not exist.') + + for requirement in parse_requirements(reqfile): + self.requirements.append(requirement) + + + + +def grep(reqfile, packages, silent=False): + + try: + r = Requirements(reqfile) + except ValueError: + if not silent: + print 'There was a problem loading the given requirement file.' + exit(os.EX_NOINPUT) + + for requirement in r.requirements: + + if requirement.req: + + if requirement.req.project_name in packages: + if not silent: + print 'Package {} found!'.format(requirement.req.project_name) + exit(0) + + print 'Not found.'.format(requirement.req.project_name) + exit(1) + + +def main(): + args = docopt(__doc__, version='pip-grep') + + kwargs = {'reqfile': args['<reqfile>'], 'packages': args['<package>'], 'silent': args['-s']} + + + grep(**kwargs) + + + +if __name__ == '__main__': + main() \ No newline at end of file