diff --git a/.gitignore b/.gitignore index b326464..3f3b100 100644 --- a/.gitignore +++ b/.gitignore @@ -48,5 +48,12 @@ docs/_build *.bak /.cache/ +# Transpilation results +__javascript__ + +# node.js and yarn +/node_modules/ +yarn.lock + /share/ -/local/ \ No newline at end of file +/local/ diff --git a/.travis.yml b/.travis.yml index 3f4001e..359d1ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,27 @@ -# This deliberately is not "python" as a work-around to support -# multi-os builds with custom Python versions in Travis CI. -language: cpp +language: generic -os: - - osx - - linux +os: + - osx + - linux env: matrix: - - PYTHON_EXE="`which python2`" - - PYTHON_EXE="`which python3.4`" - - PYTHON_EXE="`which python3.5`" + - PYENV_VERSION='3.6.1' NODE_VERSION='6.11.2' PYENV_VERSION_STRING='Python 3.6.1' +# - PYENV_VERSION='3.5.3' NODE_VERSION='8.4.0' PYENV_VERSION_STRING='Python 3.5.3' + +before_install: + - nvm install ${NODE_VERSION} && nvm use ${NODE_VERSION} + - source setup-pyenv.sh install: - - PYTHON_EXE=$PYTHON_EXE ./configure + - npm install + - ./configure + - ./configure ./etc/conf/dev/transpile + - ./bin/python transpile/transpile.py script: - - "bin/py.test -vvs" + - ./bin/py.test -vvs + - npm test notifications: irc: diff --git a/appveyor.yml b/appveyor.yml index 2bab98a..e430eab 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,12 +2,12 @@ version: '{build}' environment: matrix: - - PYTHON_EXE: "C:\\Python27\\python.exe" - - PYTHON_EXE: "C:\\Python27-x64\\python.exe" - PYTHON_EXE: "C:\\Python34\\python.exe" - PYTHON_EXE: "C:\\Python34-x64\\python.exe" - PYTHON_EXE: "C:\\Python35\\python.exe" - PYTHON_EXE: "C:\\Python35-x64\\python.exe" + - PYTHON_EXE: "C:\\Python36\\python.exe" + - PYTHON_EXE: "C:\\Python36-x64\\python.exe" install: - configure diff --git a/etc/conf/dev/transpile/base.txt b/etc/conf/dev/transpile/base.txt new file mode 100644 index 0000000..56ec845 --- /dev/null +++ b/etc/conf/dev/transpile/base.txt @@ -0,0 +1,3 @@ +# Transpiling +thirdparty/dev/Transcrypt-3.6.47-py2.py3-none-any.whl +thirdparty/dev/PyYAML-3.12.tar.gz diff --git a/package.json b/package.json new file mode 100644 index 0000000..669c34f --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "license-expression", + "version": "0.93.0", + "main": "src/license_expression.js/__javascript__/__init__.js", + "repository": "git@github.com:nexB/license-expression", + "author": "nexB Inc. ", + "license": "Apache-2.0", + "scripts": { + "test": "mocha tests.js" + }, + "devDependencies": { + "mocha": "^3.5.0" + } +} diff --git a/setup-pyenv.sh b/setup-pyenv.sh new file mode 100644 index 0000000..33212a4 --- /dev/null +++ b/setup-pyenv.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +# NOTE: This script needs to be sourced so it can modify the environment. +# +# Environment variables that can be set: +# - PYENV_VERSION +# Python to install [required] +# - PYENV_VERSION_STRING +# String to `grep -F` against the output of `python --version` to validate +# that the correct Python was installed (recommended) [default: none] +# - PYENV_ROOT +# Directory in which to install pyenv [default: ~/.travis-pyenv] +# - PYENV_RELEASE +# Release tag of pyenv to download [default: clone from master] +# - PYENV_CACHE_PATH +# Directory where full Python builds are cached (i.e., for Travis) + +# PYENV_ROOT is exported because pyenv uses it +export PYENV_ROOT="${PYENV_ROOT:-$HOME/.travis-pyenv}" +PYENV_CACHE_PATH="${PYENV_CACHE_PATH:-$HOME/.pyenv_cache}" +version_cache_path="$PYENV_CACHE_PATH/$PYENV_VERSION" +version_pyenv_path="$PYENV_ROOT/versions/$PYENV_VERSION" + +# Functions +# +# verify_python -- attempts to call the Python command or binary +# supplied in the first argument with the --version flag. If +# PYENV_VERSION_STRING is set, then it validates the returned version string +# as well (using grep -F). Returns whatever status code the command returns. +verify_python() { + local python_bin="$1"; shift + + if [[ -n "$PYENV_VERSION_STRING" ]]; then + "$python_bin" --version 2>&1 | grep -F "$PYENV_VERSION_STRING" &>/dev/null + else + "$python_bin" --version &>/dev/null + fi +} + +# use_cached_python -- Tries symlinking to the cached PYENV_VERSION and +# verifying that it's a working build. Returns 0 if it's found and it +# verifies, otherwise returns 1. +use_cached_python() { + if [[ -d "$version_cache_path" ]]; then + printf "Cached python found, %s. Verifying..." "$PYENV_VERSION" + ln -s "$version_cache_path" "$version_pyenv_path" + if verify_python "$version_pyenv_path/bin/python"; then + printf "success!\n" + return 0 + else + printf "FAILED.\nClearing cached version..." + rm -f "$version_pyenv_path" + rm -rf "$version_cache_path" + printf "done.\n" + return 1 + fi + else + echo "No cached python found." + return 1 + fi +} + +# output_debugging_info -- Outputs useful debugging information +output_debugging_info() { + echo "**** Debugging information" + printf "PYENV_VERSION\n%s\n" "$PYENV_VERSION" + printf "PYENV_VERSION_STRING\n%s\n" "$PYENV_VERSION_STRING" + printf "PYENV_CACHE_PATH\n%s\n" "$PYENV_CACHE_PATH" + set -x + python --version + "$version_cache_path/bin/python" --version + which python + pyenv which python + set +x +} + +# Main script begins. + +if [[ -z "$PYENV_VERSION" ]]; then + echo "PYENV_VERSION is not set. Not installing a pyenv." + return 0 +fi + +# Get out of the virtualenv we're in (if we're in one). +[[ -z "$VIRTUAL_ENV" ]] || deactivate + +# Install pyenv +echo "**** Installing pyenv." +if [[ -n "$PYENV_RELEASE" ]]; then + # Fetch the release archive from Github (slightly faster than cloning) + mkdir "$PYENV_ROOT" + curl -fsSL "https://github.com/yyuu/pyenv/archive/$PYENV_RELEASE.tar.gz" \ + | tar -xz -C "$PYENV_ROOT" --strip-components 1 +else + # Don't have a release to fetch, so just clone directly + git clone --depth 1 https://github.com/yyuu/pyenv.git "$PYENV_ROOT" +fi + +export PATH="$PYENV_ROOT/bin:$PATH" +eval "$(pyenv init -)" + +# Make sure the cache directory exists +mkdir -p "$PYENV_CACHE_PATH" + +# Try using an already cached PYENV_VERSION. If it fails or is not found, +# then install from scratch. +echo "**** Trying to find and use cached python $PYENV_VERSION." +if ! use_cached_python; then + echo "**** Installing python $PYENV_VERSION with pyenv now." + if pyenv install "$PYENV_VERSION"; then + if mv "$version_pyenv_path" "$PYENV_CACHE_PATH"; then + echo "Python was successfully built and moved to cache." + echo "**** Trying to find and use cached python $PYENV_VERSION." + if ! use_cached_python; then + echo "Python version $PYENV_VERSION was apparently successfully built" + echo "with pyenv, but, once cached, it could not be verified." + output_debugging_info + return 1 + fi + else + echo "**** Warning: Python was succesfully built, but moving to cache" + echo "failed. Proceeding anyway without caching." + fi + else + echo "Python version $PYENV_VERSION build FAILED." + return 1 + fi +fi + +# Now we have to reinitialize pyenv, as we need the shims etc to be created so +# the pyenv activates correctly. +echo "**** Activating python $PYENV_VERSION and generating new virtualenv." +eval "$(pyenv init -)" +pyenv global "$PYENV_VERSION" + +# Make sure virtualenv is installed and up-to-date... +pip install -U virtualenv + +# Then make and source a new virtualenv +VIRTUAL_ENV="$HOME/ve-pyenv-$PYENV_VERSION" +virtualenv -p "$(which python)" "$VIRTUAL_ENV" +# shellcheck source=/dev/null +source "$VIRTUAL_ENV/bin/activate" + +printf "One final verification that the virtualenv is working..." +if verify_python "python"; then + printf "success!\n" +else + printf "FAILED!\n" + output_debugging_info + return 1 +fi diff --git a/setup.cfg b/setup.cfg index 17acfb1..c489f9a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ norecursedirs = lib Scripts thirdparty + transpile tmp src/*/data tests/*/data @@ -39,4 +40,4 @@ addopts = --ignore docs/conf.py --ignore setup.py --doctest-modules - --doctest-glob=README.rst \ No newline at end of file + --doctest-glob=README.rst diff --git a/src/license_expression.js/index.html b/src/license_expression.js/index.html new file mode 100644 index 0000000..7bd5aae --- /dev/null +++ b/src/license_expression.js/index.html @@ -0,0 +1,25 @@ + + + + + License Expression Sandbox + + +

+ If you would like to run license expression in the browser, + you should tell Transcrypt to transpile it for the browser: +

+ ./bin/python transpile/transpile.py --browser +

+ This will define function __init__ on the + window object. +

+

+ Without the --browser flag, the __init__ + function will be defined on the module.exports object, + which works for node.js +

+

To debug license expression in the browser, open developer tools.

+ + + diff --git a/src/license_expression/__init__.py b/src/license_expression/__init__.py index dca8054..d879423 100644 --- a/src/license_expression/__init__.py +++ b/src/license_expression/__init__.py @@ -32,22 +32,8 @@ from __future__ import unicode_literals from __future__ import print_function -# Python 2 and 3 support -try: - # Python 2 - unicode - str = unicode -except NameError: - # Python 3 - unicode = str - -import collections -from copy import copy -from copy import deepcopy -from functools import total_ordering import itertools import re -import string import boolean from boolean import Expression as LicenseExpression @@ -70,6 +56,7 @@ from license_expression._pyahocorasick import Trie as Scanner from license_expression._pyahocorasick import Output from license_expression._pyahocorasick import Result +from license_expression.my_collections import Deque # append new error codes to PARSE_ERRORS by monkey patching @@ -100,7 +87,19 @@ class ExpressionError(Exception): # Used for tokenizing -Keyword = collections.namedtuple('Keyword', 'value type') +class Keyword: + def __init__(self, value, type): + self.value = value + self.type = type + + def __eq__(self, other): + return self.value == other.value and self.type == other.type + +def copy(target): + if isinstance(target, LicenseSymbol): + return target.__copy__() + + raise RuntimeError('Custom copy cannot copy ' + str(type(target))) # id for "with" token which is not a proper boolean symbol but an expression symbol TOKEN_WITH = 10 @@ -163,7 +162,7 @@ def __init__(self, symbols=tuple(), quiet=True): or LicenseSymbol-like objects or license key strings. If provided and this list data is invalid, raise a ValueError. """ - super(Licensing, self).__init__(Symbol_class=LicenseSymbol, AND_class=AND, OR_class=OR) + super().__init__(Symbol_class=LicenseSymbol, AND_class=AND, OR_class=OR) # FIXME: this should be instead a super class of all symbols self.LicenseSymbol = self.Symbol @@ -342,7 +341,7 @@ def parse(self, expression, validate=False, strict=False, **kwargs): string. Check that the expression syntax is valid and raise an Exception, ExpressionError or ParseError on errors. Return None for empty expressions. `expression` is either a string or a LicenseExpression object. If this is a - LicenseExpression it is retruned as-si. + LicenseExpression it is retruned as-is. Symbols are always recognized from known symbols if `symbols` were provided Licensing creation time: each license and exception is recognized from known @@ -374,13 +373,6 @@ def parse(self, expression, validate=False, strict=False, **kwargs): if isinstance(expression, LicenseExpression): return expression - if isinstance(expression, bytes): - try: - expression = unicode(expression) - except: - ext = type(expression) - raise ExpressionError('expression must be a string and not: %(ext)r' % locals()) - if not isinstance(expression, str): ext = type(expression) raise ExpressionError('expression must be a string and not: %(ext)r' % locals()) @@ -389,8 +381,8 @@ def parse(self, expression, validate=False, strict=False, **kwargs): return try: # this will raise a ParseError on errors - tokens = list(self.tokenize(expression, strict=strict)) - expression = super(Licensing, self).parse(tokens) + tokens = [token for token in self.tokenize(expression, strict=strict)] + expression = super().parse(tokens) except TypeError as e: msg = 'Invalid expression syntax: ' + repr(e) raise ExpressionError(msg) @@ -498,7 +490,8 @@ def tokenize(self, expression, strict=False): PARSE_INVALID_SYMBOL) if not lic_sym: - lic_sym = LicenseSymbol(lic_res.string, is_exception=False) + # Transcrypt stumbles if you skip a named argument, so aliases=[] + lic_sym = LicenseSymbol(lic_res.string, aliases=[], is_exception=False) if not isinstance(lic_sym, LicenseSymbol): raise ParseError(TOKEN_SYMBOL, lic_res.string, lic_res.start, @@ -602,27 +595,18 @@ def decompose(self): # validate license keys is_valid_license_key = re.compile(r'^[-\w\s\.\+]+$', re.UNICODE).match -#FIXME: we need to implement comparison!!!! -@total_ordering class LicenseSymbol(BaseSymbol): """ A LicenseSymbol represents a license as used in a license expression. """ - def __init__(self, key, aliases=tuple(), is_exception=False, *args, **kwargs): + def __init__(self, key, aliases=tuple(), is_exception=False): if not key: raise ExpressionError( 'A license key cannot be empty: %(key)r' % locals()) if not isinstance(key, str): - if isinstance(key, bytes): - try: - key = unicode(key) - except: - raise ExpressionError( - 'A license key must be a unicode string: %(key)r' % locals()) - else: - raise ExpressionError( - 'A license key must be a unicode string: %(key)r' % locals()) + raise ExpressionError( + 'A license key must be a unicode string: %(key)r' % locals()) key = key.strip() @@ -654,7 +638,7 @@ def __init__(self, key, aliases=tuple(), is_exception=False, *args, **kwargs): self.as_exception = False # super only know about a single "obj" object. - super(LicenseSymbol, self).__init__(self.key) + super().__init__(self.key) def decompose(self): """ @@ -670,12 +654,16 @@ def __eq__(self, other): or (isinstance(other, self.__class__) and self.key == other.key and self.is_exception == other.is_exception) - or (self.symbol_like(other) + or (is_symbol_like(other) and self.key == other.key and self.is_exception == other.is_exception) ) - __nonzero__ = __bool__ = lambda s: True + def __bool__(self): + return True + + def __nonzero__(self): + return True def render(self, template='{symbol.key}', *args, **kwargs): return template.format(symbol=self) @@ -693,28 +681,29 @@ def __repr__(self): def __copy__(self): return LicenseSymbol(self.key, tuple(self.aliases), self.is_exception) - @classmethod - def symbol_like(cls, symbol): - """ - Return True if `symbol` is a symbol-like object with its essential attributes. - """ - return hasattr(symbol, 'key') and hasattr(symbol, 'is_exception') + +# Transcrypt supports classmethods only if you call them by creating an +# instance of the class first (and calling the method of that instance) +# This is confusing, so make this classmethod into a standalone function +def is_symbol_like(symbol): + """ + Return True if `symbol` is a symbol-like object with its essential attributes. + """ + return hasattr(symbol, 'key') and hasattr(symbol, 'is_exception') -#FIXME: we need to implement comparison!!!! -@total_ordering class LicenseSymbolLike(LicenseSymbol): """ A LicenseSymbolLike object wraps a symbol-like object to expose a LicenseSymbol behavior. """ - def __init__(self, symbol_like, *args, **kwargs): - if not self.symbol_like(symbol_like): + def __init__(self, symbol_like): + if not is_symbol_like(symbol_like): raise ExpressionError( 'Not a symbol-like object: %(symbol_like)r' % locals()) self.wrapped = symbol_like - super(LicenseSymbolLike, self).__init__(self.wrapped.key, *args, **kwargs) + super().__init__(self.wrapped.key) self.is_exception = self.wrapped.is_exception self.aliases = getattr(self.wrapped, 'aliases', tuple()) @@ -732,11 +721,9 @@ def __copy__(self): def render(self, template='{symbol.key}', *args, **kwargs): if self._render: return self._render(template, *args, **kwargs) - return super(LicenseSymbolLike, self).render(template, *args, **kwargs) + return super().render(template, *args, **kwargs) -#FIXME: we need to implement comparison!!!! -@total_ordering class LicenseWithExceptionSymbol(BaseSymbol): """ A LicenseWithExceptionSymbol represents a license "with" an exception as used in @@ -753,7 +740,7 @@ def __init__(self, license_symbol, exception_symbol, strict=False, *args, **kwar - license_symbol.is_exception is True - exception_symbol.is_exception is not True """ - if not LicenseSymbol.symbol_like(license_symbol): + if not is_symbol_like(license_symbol): raise ExpressionError( 'license_symbol must be a LicenseSymbol-like object: %(license_symbol)r' % locals()) @@ -761,7 +748,7 @@ def __init__(self, license_symbol, exception_symbol, strict=False, *args, **kwar raise ExpressionError( 'license_symbol cannot be an exception with "is_exception" set to True: %(license_symbol)r' % locals()) - if not LicenseSymbol.symbol_like(exception_symbol): + if not is_symbol_like(exception_symbol): raise ExpressionError( 'exception_symbol must be a LicenseSymbol-like object: %(exception_symbol)r' % locals()) @@ -774,7 +761,7 @@ def __init__(self, license_symbol, exception_symbol, strict=False, *args, **kwar exception_symbol.as_exception = True self.exception_symbol = exception_symbol - super(LicenseWithExceptionSymbol, self).__init__(str(self)) + super().__init__(str(self)) def __copy__(self): return LicenseWithExceptionSymbol(copy(self.license_symbol), copy(self.exception_symbol)) @@ -797,7 +784,11 @@ def __eq__(self, other): and self.license_symbol == other.license_symbol and self.exception_symbol == other.exception_symbol) - __nonzero__ = __bool__ = lambda s: True + def __bool__(self): + return True + + def __nonzero__(self): + return True def __str__(self): lkey = self.license_symbol.key @@ -862,7 +853,7 @@ class AND(RenderableFunction, boolean.AND): Custom representation for the AND operator to uppercase. """ def __init__(self, *args): - super(AND, self).__init__(*args) + super().__init__(*args) self.operator = ' AND ' @@ -871,7 +862,7 @@ class OR(RenderableFunction, boolean.OR): Custom representation for the OR operator to uppercase. """ def __init__(self, *args): - super(OR, self).__init__(*args) + super().__init__(*args) self.operator = ' OR ' @@ -888,61 +879,43 @@ def ordered_unique(seq): uniques.append(item) return uniques - +# NOTE: return a list and *not* a generator because of transpiling issues def strip_and_skip_spaces(results): """ - Yield results given a sequence of Result skipping whitespace-only results + Return results given a list of Result skipping whitespace-only results """ - for result in results: - if result.string.strip(): - yield result - + return [result for result in results if result.string.strip()] def group_results_for_with_subexpression(results): """ - Yield tuples of (Result) given a sequence of Result such that: - - all symbol-with-symbol subsequences of three results are grouped in a three-tuple + Return tuples of (Result) given a list of Result such that: + - all symbol-with-symbol subsequences of 3 results are grouped in a 3-tuple - other results are the single result in a tuple. """ # if n-1 is sym, n is with and n+1 is sym: yield this as a group for a with exp # otherwise: yield each single result as a group - results = list(results) + groups = [] + n = len(results) + lft, mid, rgt = 0, 1, 2 - # check three contiguous result from scanning at a time - triple_len = 3 + while lft != n and mid != n and rgt != n: + res0, res1, res2 = results[lft], results[mid], results[rgt] - # shortcut if there are no grouping possible - if len(results) < triple_len: - for res in results: - yield (res,) - return + if is_with_subexpression(res0, res1, res2): + groups.append((res0, res1, res2)) - # accumulate three contiguous results - triple = collections.deque() - triple_popleft = triple.popleft - triple_clear = triple.clear - tripple_append = triple.append - - for res in results: - if len(triple) == triple_len: - if is_with_subexpression(triple): - yield tuple(triple) - triple_clear() - else: - prev_res = triple_popleft() - yield (prev_res,) - tripple_append(res) - - # end remainders - if triple: - if len(triple) == triple_len and is_with_subexpression(triple): - yield tuple(triple) + lft, mid, rgt = lft + 3, mid + 3, rgt + 3 else: - for res in triple: - yield (res,) + groups.append((res0, )) + + lft, mid, rgt = lft + 1, mid + 1, rgt + 1 + + for result in results[lft:]: + groups.append((result, )) + return groups def is_symbol(result): # either the output value is a known sym, or we have no output for unknown sym @@ -954,9 +927,10 @@ def is_with_keyword(result): and isinstance(result.output.value, Keyword) and result.output.value.type == TOKEN_WITH) - -def is_with_subexpression(results): - lic, wit, exc = results +def is_with_subexpression(lic, wit, exc): + """ + Check if provided arguments are a 'lic'ense 'wit'h 'exc'eption triplet + """ return (is_symbol(lic) and is_with_keyword(wit) and is_symbol(exc)) @@ -968,30 +942,26 @@ def as_symbols(symbols): will raise a TypeError expection if an item is neither a string or LicenseSymbol- like. """ - if symbols: - for symbol in symbols: - if not symbol: - continue - if isinstance(symbol, bytes): - try: - symbol = unicode(symbol) - except: - raise TypeError('%(symbol)r is not a unicode string.' % locals()) + results = [] + for symbol in symbols: + if not symbol: + continue - if isinstance(symbol, unicode): - if symbol.strip(): - yield LicenseSymbol(symbol) + if isinstance(symbol, str): + if symbol.strip(): + results.append(LicenseSymbol(symbol)) - elif isinstance(symbol, LicenseSymbol): - yield symbol + elif isinstance(symbol, LicenseSymbol): + results.append(symbol) - elif LicenseSymbol.symbol_like(symbol): - yield LicenseSymbolLike(symbol) + elif is_symbol_like(symbol): + results.append(LicenseSymbolLike(symbol)) - else: - raise TypeError('%(symbol)r is not a unicode string ' - 'or a LicenseSymbol-like instance.' % locals()) + else: + raise TypeError('%(symbol)r is not a unicode string ' + 'or a LicenseSymbol-like instance.' % locals()) + return results def validate_symbols(symbols, validate_keys=False, _keywords=KEYWORDS): """ @@ -1015,9 +985,9 @@ def validate_symbols(symbols, validate_keys=False, _keywords=KEYWORDS): not_symbol_classes = [] dupe_keys = set() dupe_exceptions = set() - dupe_aliases = collections.defaultdict(list) + dupe_aliases = {} invalid_keys_as_kw = set() - invalid_alias_as_kw = collections.defaultdict(list) + invalid_alias_as_kw = {} # warning warning_dupe_aliases = set() @@ -1066,10 +1036,12 @@ def validate_symbols(symbols, validate_keys=False, _keywords=KEYWORDS): # ensure that a possibly duplicated alias does not point to another key aliased_key = seen_aliases.get(alias) if aliased_key and aliased_key != keyl: + dupe_aliases.setdefault(alias, []) dupe_aliases[alias].append(key) # an alias cannot be an expression keyword if alias in _keywords: + invalid_alias_as_kw.setdefault(key, []) invalid_alias_as_kw[key].append(alias) seen_aliases[alias] = keyl @@ -1110,20 +1082,7 @@ def validate_symbols(symbols, validate_keys=False, _keywords=KEYWORDS): return warnings, errors - -_splitter = re.compile(''' - (?P[^\s\(\)]+) - | - (?P\s+) - | - (?P\() - | - (?P\)) - ''', - re.VERBOSE | re.MULTILINE | re.UNICODE -).finditer - - +# NOTE: drop regular expressions and generators because of transpiling issues def splitter(expression): """ Return an iterable of Result describing each token given an @@ -1141,43 +1100,45 @@ def splitter(expression): # mapping of lowercase token strings to a token type id TOKENS = { - 'and': Keyword(value='and', type=TOKEN_AND), - 'or': Keyword(value='or', type=TOKEN_OR), - 'with': Keyword(value='with', type=TOKEN_WITH), + 'and': Keyword('and', TOKEN_AND), + 'or': Keyword('or', TOKEN_OR), + 'with': Keyword('with', TOKEN_WITH), } - for match in _splitter(expression): - if not match: - continue + lft, results = 0, [] + while lft != len(expression): + rgt = lft + 1 + if expression[lft].isspace(): + while rgt != len(expression) and expression[rgt].isspace(): + rgt += 1 - start, end = match.span() - end = end - 1 - mgd = match.groupdict() + results.append(Result(lft, rgt - 1, expression[lft:rgt], None)) + elif expression[lft] == '(': + lpar = expression[lft:rgt] - space = mgd.get('space') - if space: - yield Result(start, end, space, None) + results.append(Result(lft, rgt - 1, lpar, Output(lpar, KW_LPAR))) + elif expression[lft] == ')': + rpar = expression[lft:rgt] - lpar = mgd.get('lpar') - if lpar: - yield Result(start, end, lpar, Output(lpar, KW_LPAR)) + results.append(Result(lft, rgt - 1, rpar, Output(rpar, KW_RPAR))) + else: + while rgt != len(expression): + nxt = expression[rgt] - rpar = mgd.get('rpar') - if rpar: - yield Result(start, end, rpar, Output(rpar, KW_RPAR)) + if nxt.isspace() or nxt == '(' or nxt == ')': + break - token_or_sym = mgd.get('symbol') - if not token_or_sym: - continue + rgt += 1 - token = TOKENS.get(token_or_sym.lower()) - if token: - yield Result(start, end, token_or_sym, Output(token_or_sym, token)) -# elif token_or_sym.endswith('+') and token_or_sym != '+': -# val = token_or_sym[:-1] -# sym = LicenseSymbol(key=val) -# yield Result(start, end - 1, val, Output(val, sym)) -# yield Result(end, end, '+', Output('+', KW_PLUS)) - else: - sym = LicenseSymbol(key=token_or_sym) - yield Result(start, end, token_or_sym, Output(token_or_sym, sym)) + tok_or_sym = expression[lft:rgt] + + token = TOKENS.get(tok_or_sym.lower()) + if token: + results.append(Result(lft, rgt - 1, tok_or_sym, Output(tok_or_sym, token))) + else: + symbol = LicenseSymbol(tok_or_sym) + results.append(Result(lft, rgt - 1, tok_or_sym, Output(tok_or_sym, symbol))) + + lft = rgt + + return results diff --git a/src/license_expression/_pyahocorasick.py b/src/license_expression/_pyahocorasick.py index 3492a6c..30f8c63 100644 --- a/src/license_expression/_pyahocorasick.py +++ b/src/license_expression/_pyahocorasick.py @@ -18,23 +18,14 @@ from __future__ import absolute_import from __future__ import print_function -from collections import deque -from collections import OrderedDict -import logging - -logger = logging.getLogger(__name__) - -def logger_debug(*args): - return logger.debug(' '.join(isinstance(a, str) and a or repr(a) for a in args)) - -# uncomment for local debug logging -# import sys -# logging.basicConfig(stream=sys.stdout) -# logger.setLevel(logging.DEBUG) +from license_expression.my_collections import Deque # used to distinguish from None -nil = object() +class Nil: + pass + +nil = Nil() class Trie(object): @@ -79,12 +70,7 @@ def add(self, key, value=None, priority=0): node = self.root for char in stored_key: - try: - node = node.children[char] - except KeyError: - child = TrieNode(char) - node.children[char] = child - node = child + node = node.children.setdefault(char, TrieNode(char)) # we always store the original key, not a possibly lowercased version node.output = Output(key, value, priority) @@ -178,7 +164,7 @@ def make_automaton(self): Note that this is an error to add new keys to a Trie once it has been converted to an Automaton. """ - queue = deque() + queue = Deque() queue_append = queue.append queue_popleft = queue.popleft @@ -208,7 +194,7 @@ def make_automaton(self): # Mark the trie as converted so it cannot be modified anymore self._converted = True - def iter(self, string): + def iterate(self, string): """ Yield Result objects for matched strings by performing the Aho-Corasick search procedure. @@ -228,7 +214,7 @@ def iter(self, string): >>> a.add('KL') >>> a.make_automaton() >>> string = 'abcdefghijklm' - >>> results = Result.sort(a.iter(string)) + >>> results = reorder(a.iterate(string)) >>> expected = [ ... Result(1, 5, 'bcdef', Output('BCDEF')), @@ -240,14 +226,14 @@ def iter(self, string): >>> results == expected True - >>> list(a.iter('')) == [] + >>> list(a.iterate('')) == [] True - >>> list(a.iter(' ')) == [] + >>> list(a.iterate(' ')) == [] True """ if not string: - return + return [] # keep a copy for results original_string = string @@ -255,6 +241,7 @@ def iter(self, string): known_chars = self._known_chars state = self.root + results = [] for end, char in enumerate(string): if char not in known_chars: state = self.root @@ -271,9 +258,11 @@ def iter(self, string): # TODO: this could be precomputed or cached n = len(match.output.key) start = end - n + 1 - yield Result(start, end, original_string[start:end + 1], match.output) + results.append(Result(start, end, original_string[start:end + 1], match.output)) match = match.fail + return results + def scan(self, string): """ Scan a string for matched and unmatched sub-sequences and yield non- @@ -317,7 +306,7 @@ def scan(self, string): >>> results == expected True """ - results = self.iter(string) + results = self.iterate(string) results = filter_overlapping(results) results = add_unmatched(string, results) return results @@ -382,7 +371,7 @@ def __hash__(self): return hash((self.key, self.value, self.priority,)) def as_dict(self): - return OrderedDict([(s, getattr(self, s)) for s in self.__slots__]) + return dict([(s, getattr(self, s)) for s in self.__slots__]) class Result(object): @@ -408,8 +397,11 @@ def __init__(self, start, end, string='', output=None): def __repr__(self): return self.__class__.__name__ + '(%(start)r, %(end)r, %(string)r, %(output)r)' % self.as_dict() + def toString(self): + return self.__repr__() + def as_dict(self): - return OrderedDict([(s, getattr(self, s)) for s in self.__slots__]) + return dict([(s, getattr(self, s)) for s in self.__slots__]) def __len__(self): return self.end + 1 - self.start @@ -487,22 +479,31 @@ def overlap(self, other): end = self.end return (start <= other.start <= end) or (start <= other.end <= end) - @classmethod - def sort(cls, results): - """ - Return a new sorted sequence of results given a sequence of results. The - primary sort is on start and the secondary sort is on longer lengths. - Therefore if two results have the same start, the longer result will sort - first. +# Transcrypt supports classmethods only if you call them by creating an +# instance of the class first (and calling the method of that instance) +# This is confusing, so make this classmethod into a standalone function +def reorder(results): + """ + Return a new sorted sequence of results given a sequence of results. The + primary sort is on start and the secondary sort is on longer lengths. + Therefore if two results have the same start, the longer result will sort + first. - For example: - >>> results = [Result(0, 0), Result(5, 5), Result(1, 1), Result(2, 4), Result(2, 5)] - >>> expected = [Result(0, 0), Result(1, 1), Result(2, 5), Result(2, 4), Result(5, 5)] - >>> expected == Result.sort(results) - True - """ - key = lambda s: (s.start, -len(s),) - return sorted(results, key=key) + For example: + >>> results = [Result(0, 0), Result(5, 5), Result(1, 1), Result(2, 4), Result(2, 5)] + >>> expected = [Result(0, 0), Result(1, 1), Result(2, 5), Result(2, 4), Result(5, 5)] + >>> expected == reorder(results) + True + """ + # Transcrypt compares lists (javascript Arrays, actually) elementwise, + # however each element is treated as a Unicode string, so for instance + # 11 < 5 is false (numbers) and [11] < [5] is true (Unicode strings) + # So, use a comparison function that returns a single number + total_length = 0 + for result in results: + total_length += len(result) + + return sorted(results, key=lambda s: s.start * total_length - len(s)) def filter_overlapping(results): @@ -538,51 +539,57 @@ def filter_overlapping(results): >>> filtered == expected True """ - results = Result.sort(results) - - # compare pair of results in the sorted sequence: current and next - i = 0 - while i < len(results) - 1: - j = i + 1 - while j < len(results): - curr_res = results[i] - next_res = results[j] - - logger_debug('curr_res, i, next_res, j:', curr_res, i, next_res, j) - # disjoint results: break, there is nothing to do - if next_res.is_after(curr_res): - logger_debug(' break to next', curr_res) + results = reorder(results) + results_with_no_overlaps = [] + + i, j, N = 0, 0, len(results) + while i != N: + assert i == j + + # advance j to the right as far as it can go + + # advance j over strictly contained results + # this loop makes at least one iteration because: + # a) Result.__contains__ is True for the same results + # b) the above invariant of 'assert i == j' + while j != N and results[j] in results[i]: + j += 1 + + assert i < j + + # advance j over overlapping results + # if right result becomes "better" than left result + # then make right index the new left index and restart + while j != N and results[i].overlap(results[j]): + # right is "better" because it has stronger priority + if results[j].priority < results[i].priority: + i = j + break - # contained result: discard the contained result - if next_res in curr_res: - logger_debug(' del next_res contained:', next_res) - del results[j] - continue + # right is "better" because same priority but longer + if results[j].priority == results[i].priority: + if len(results[j]) > len(results[i]): + i = j - # overlap: keep the biggest result and skip the smallest overlapping results - # in case of length tie: keep the left most - if curr_res.overlap(next_res): - if curr_res.priority < next_res.priority: - logger_debug(' del next_res lower priority:', next_res) - del results[j] - continue - elif curr_res.priority > next_res.priority: - logger_debug(' del curr_res lower priority:', curr_res) - del results[i] break - else: - if len(curr_res) >= len(next_res): - logger_debug(' del next_res smaller overlap:', next_res) - del results[j] - continue - else: - logger_debug(' del curr_res smaller overlap:', curr_res) - del results[i] - break + + # right is not "better", so keep advancing right j += 1 - i += 1 - return results + + # i == j if and only if right was "better" than left + # so, if that is the case, just restart the loop again + + # i != j implies i < j and since j <= N, so i < j <= N + # i != j also implies that either j == N or left and right + # results are disjoint (no longer overlap). So, left result + # should survive the filter and be eventually returned + if i != j: + results_with_no_overlaps.append(results[i]) + + i = j + + return results_with_no_overlaps def add_unmatched(string, results): @@ -607,7 +614,7 @@ def add_unmatched(string, results): ... Result(9, 10, 'jk'), ... Result(11, 13, 'lmn') ... ] - >>> expected == list(add_unmatched(string, results)) + >>> expected == add_unmatched(string, results) True >>> string ='abc2' @@ -618,21 +625,25 @@ def add_unmatched(string, results): ... Result(0, 2, 'abc'), ... Result(3, 3, '2', None), ... ] - >>> expected == list(add_unmatched(string, results)) + >>> expected == add_unmatched(string, results) True """ + + results_and_unmatched = [] string_pos = 0 - for result in Result.sort(results): + for result in reorder(results): if result.start > string_pos: start = string_pos end = result.start - 1 - yield Result(start, end, string[start:end + 1]) - yield result + results_and_unmatched.append(Result(start, end, string[start:end + 1])) + results_and_unmatched.append(result) string_pos = result.end + 1 len_string = len(string) if string_pos < len_string: start = string_pos end = len_string - 1 - yield Result(start, end, string[start:end + 1]) + results_and_unmatched.append(Result(start, end, string[start:end + 1])) + + return results_and_unmatched diff --git a/src/license_expression/my_collections.py b/src/license_expression/my_collections.py new file mode 100644 index 0000000..57588fc --- /dev/null +++ b/src/license_expression/my_collections.py @@ -0,0 +1,18 @@ +class Deque: + def __init__(self): + self.xs = [] + + def clear(self): + self.xs = [] + + def popleft(self): + return self.xs.pop(0) + + def append(self, value): + self.xs.append(value) + + def __len__(self): + return len(self.xs) + + def __iter__(self): + return self.xs.__iter__() diff --git a/tests.js/test_license_expression.js b/tests.js/test_license_expression.js new file mode 100644 index 0000000..dd8eac2 --- /dev/null +++ b/tests.js/test_license_expression.js @@ -0,0 +1,814 @@ +let assert = require('assert') + +let license_expression = require('../src/license_expression.js/__javascript__/__init__.js') + +// Transcrypt uses '__init__.py' to derive the name of the top-level object +// If you want it to be called 'license_expression', rename '__init__.py' file +license_expression = license_expression.__init__ + +let Function = license_expression.Function +let Licensing = license_expression.Licensing + +let LicenseSymbol = license_expression.LicenseSymbol +let LicenseWithExceptionSymbol = license_expression.LicenseWithExceptionSymbol + +let TOKEN_OR = license_expression.TOKEN_OR +let TOKEN_AND = license_expression.TOKEN_AND +let TOKEN_LPAR = license_expression.TOKEN_LPAR +let TOKEN_RPAR = license_expression.TOKEN_RPAR + + describe('LicenseSymbol', function() { + it('should compare equal to itself', function() { + let license_symbol = LicenseSymbol(key='MIT') + + assert.ok(license_symbol == license_symbol) + assert.ok(license_symbol === license_symbol) + + assert.equal(license_symbol, license_symbol) + assert.deepEqual(license_symbol, license_symbol) + }) + + it('should have a .key property with the name of the license', function() { + let license_symbol = LicenseSymbol(key='MIT') + + assert.ok(license_symbol.key) + assert.equal('MIT', license_symbol.key) + }) + + it('should support license aliases (one)', function() { + let license_symbol = LicenseSymbol(key='MIT', aliases=['MIT license']) + + assert.ok(license_symbol.aliases) + assert.equal('MIT license', license_symbol.aliases) + }) + + it('should support license aliases (two)', function() { + let license_symbol = LicenseSymbol( + key='MIT', aliases=['MIT license', "Tim's license"] + ) + + assert.ok(license_symbol.aliases) + assert.ok(license_symbol.aliases.includes('MIT license')) + assert.ok(license_symbol.aliases.includes("Tim's license")) + assert.equal(false, license_symbol.aliases.includes('Not here')) + }) + + it('should support a license with is_exception being false', function() { + let license_symbol = LicenseSymbol(key='MIT') + + assert.equal(false, license_symbol.is_exception) + }) + + it('should support a license with is_exception being true', function() { + let license_symbol = LicenseSymbol(key='MIT', aliases=[], is_exception=true) + + assert.ok(license_symbol.is_exception) + }) + + it('should compare equal if the same license .key', function() { + let license_symbol0 = LicenseSymbol(key='MIT') + let license_symbol1 = LicenseSymbol(key='MIT') + + assert.ok(license_symbol0.__eq__(license_symbol1)) + }) + + it('should compare not equal if different licenses', function() { + let license_symbol0 = LicenseSymbol(key='MIT') + let license_symbol1 = LicenseSymbol(key='GPL') + + assert.ok(!license_symbol0.__eq__(license_symbol1)) + }) +}) + +describe('LicenseWithExceptionSymbol', function() { + it.skip('should complain if no arguments', function() { + let license_symbol_with_exception = LicenseWithExceptionSymbol() + }) + + it('should support two LicenseSymbol-like arguments', function() { + let license_symbol0 = LicenseSymbol(key='MIT', aliases=[], is_exeption=true) + let license_symbol1 = LicenseSymbol(key='GPL') + + let license_symbol2 = LicenseWithExceptionSymbol( + license_symbol = license_symbol0, exception_symbol = license_symbol1 + ) + + assert.ok(license_symbol2.license_symbol !== undefined) + assert.ok(license_symbol2.license_symbol.key === 'MIT') + assert.ok(license_symbol2.license_symbol.is_exception === true) + assert.ok(license_symbol2.license_symbol.as_exception === false) + + assert.ok(license_symbol2.exception_symbol !== undefined) + assert.ok(license_symbol2.exception_symbol.key === 'GPL') + assert.ok(license_symbol2.exception_symbol.is_exception === false) + assert.ok(license_symbol2.exception_symbol.as_exception === true) + }) + + it('should compare equal if the same license .key', function() { + let license_symbol0 = LicenseSymbol(key='MIT', aliases=[], is_exeption=true) + let license_symbol1 = LicenseSymbol(key='GPL') + + let license_symbol2 = LicenseWithExceptionSymbol( + license_symbol = license_symbol0, exception_symbol = license_symbol1 + ) + + let license_symbol3 = LicenseWithExceptionSymbol( + license_symbol = license_symbol0, exception_symbol = license_symbol1 + ) + + assert.ok(license_symbol2.__eq__(license_symbol3)) + }) +}) + +describe('Licensing', function() { + describe('tokenize', function() { + let licensing + + beforeEach(function() { + licensing = Licensing() + }) + + it('should tokenize a single license', function() { + tokens = [] + for (let token of licensing.tokenize('MIT')) { + tokens.push(token) + } + + assert.ok(tokens.length === 1) + assert.ok(tokens[0].length === 3) + + // token itself, token string and token position + let [tok, str, pos] = tokens[0] + + assert.equal('MIT', tok.key) + assert.equal('MIT', str) + assert.equal(0 , pos) + }) + + it('should tokenize a single OR expression', function() { + tokens = [] + for (let token of licensing.tokenize('mit or gpl')) { + tokens.push(token) + } + + assert.ok(tokens.length === 3) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal('mit', tokens[0][0].key) + assert.equal('mit', tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal(TOKEN_OR, tokens[1][0]) + assert.equal('or' , tokens[1][1]) + assert.equal(4 , tokens[1][2]) + + assert.equal('gpl', tokens[2][0].key) + assert.equal('gpl', tokens[2][1]) + assert.equal(7 , tokens[2][2]) + }) + + it('should tokenize a double OR expression', function() { + tokens = [] + for (let token of licensing.tokenize('mit or gpl or apache')) { + tokens.push(token) + } + + assert.ok(tokens.length === 5) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal('mit', tokens[0][0].key) + assert.equal('mit', tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal(TOKEN_OR, tokens[1][0]) + assert.equal('or' , tokens[1][1]) + assert.equal(4 , tokens[1][2]) + + assert.equal('gpl', tokens[2][0].key) + assert.equal('gpl', tokens[2][1]) + assert.equal(7 , tokens[2][2]) + + assert.equal(TOKEN_OR, tokens[3][0]) + assert.equal('or' , tokens[3][1]) + assert.equal(11 , tokens[3][2]) + + assert.equal('apache', tokens[4][0].key) + assert.equal('apache', tokens[4][1]) + assert.equal(14 , tokens[4][2]) + }) + + it('should tokenize a single AND expression', function() { + tokens = [] + for (let token of licensing.tokenize('mit and gpl')) { + tokens.push(token) + } + + assert.ok(tokens.length === 3) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal('mit', tokens[0][0].key) + assert.equal('mit', tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal(TOKEN_AND, tokens[1][0]) + assert.equal('and' , tokens[1][1]) + assert.equal(4 , tokens[1][2]) + + assert.equal('gpl', tokens[2][0].key) + assert.equal('gpl', tokens[2][1]) + assert.equal(8 , tokens[2][2]) + }) + + it('should tokenize a double AND expression', function() { + tokens = [] + for (let token of licensing.tokenize('mit and gpl and apache')) { + tokens.push(token) + } + + assert.ok(tokens.length === 5) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal('mit', tokens[0][0].key) + assert.equal('mit', tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal(TOKEN_AND, tokens[1][0]) + assert.equal('and' , tokens[1][1]) + assert.equal(4 , tokens[1][2]) + + assert.equal('gpl', tokens[2][0].key) + assert.equal('gpl', tokens[2][1]) + assert.equal(8 , tokens[2][2]) + + assert.equal(TOKEN_AND, tokens[3][0]) + assert.equal('and' , tokens[3][1]) + assert.equal(12 , tokens[3][2]) + + assert.equal('apache', tokens[4][0].key) + assert.equal('apache', tokens[4][1]) + assert.equal(16 , tokens[4][2]) + }) + + it('should tokenize a single license with parenthesis', function() { + tokens = [] + for (let token of licensing.tokenize('(MIT)')) { + tokens.push(token) + } + + assert.ok(tokens.length === 3) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal(TOKEN_LPAR, tokens[0][0]) + assert.equal('(' , tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal('MIT', tokens[1][0].key) + assert.equal('MIT', tokens[1][1]) + assert.equal(1 , tokens[1][2]) + + assert.equal(TOKEN_RPAR, tokens[2][0]) + assert.equal(')' , tokens[2][1]) + assert.equal(4 , tokens[2][2]) + }) + + it('should tokenize a single OR expression with parenthesis', function() { + tokens = [] + for (let token of licensing.tokenize('mit or ( gpl )')) { + tokens.push(token) + } + + assert.ok(tokens.length === 5) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal('mit', tokens[0][0].key) + assert.equal('mit', tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal(TOKEN_OR, tokens[1][0]) + assert.equal('or' , tokens[1][1]) + assert.equal(4 , tokens[1][2]) + + assert.equal(TOKEN_LPAR, tokens[2][0]) + assert.equal('(' , tokens[2][1]) + assert.equal(7 , tokens[2][2]) + + assert.equal('gpl', tokens[3][0].key) + assert.equal('gpl', tokens[3][1]) + assert.equal(9 , tokens[3][2]) + + assert.equal(TOKEN_RPAR, tokens[4][0]) + assert.equal(')' , tokens[4][1]) + assert.equal(13 , tokens[4][2]) + }) + + it('should tokenize a double OR expression with parenthesis', function() { + tokens = [] + for (let token of licensing.tokenize('mit or (gpl or apache)')) { + tokens.push(token) + } + + assert.ok(tokens.length === 7) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal('mit', tokens[0][0].key) + assert.equal('mit', tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal(TOKEN_OR, tokens[1][0]) + assert.equal('or' , tokens[1][1]) + assert.equal(4 , tokens[1][2]) + + assert.equal(TOKEN_LPAR, tokens[2][0]) + assert.equal('(' , tokens[2][1]) + assert.equal(7 , tokens[2][2]) + + assert.equal('gpl', tokens[3][0].key) + assert.equal('gpl', tokens[3][1]) + assert.equal(8 , tokens[3][2]) + + assert.equal(TOKEN_OR, tokens[4][0]) + assert.equal('or' , tokens[4][1]) + assert.equal(12 , tokens[4][2]) + + assert.equal('apache', tokens[5][0].key) + assert.equal('apache', tokens[5][1]) + assert.equal(15 , tokens[5][2]) + + assert.equal(TOKEN_RPAR, tokens[6][0]) + assert.equal(')' , tokens[6][1]) + assert.equal(21 , tokens[6][2]) + }) + + it('should tokenize a single AND expression with parenthesis', function() { + tokens = [] + for (let token of licensing.tokenize('( mit) and gpl')) { + tokens.push(token) + } + + assert.ok(tokens.length === 5) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal(TOKEN_LPAR, tokens[0][0]) + assert.equal('(' , tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal('mit', tokens[1][0].key) + assert.equal('mit', tokens[1][1]) + assert.equal(2 , tokens[1][2]) + + assert.equal(TOKEN_RPAR, tokens[2][0]) + assert.equal(')' , tokens[2][1]) + assert.equal(5 , tokens[2][2]) + + assert.equal(TOKEN_AND, tokens[3][0]) + assert.equal('and' , tokens[3][1]) + assert.equal(7 , tokens[3][2]) + + assert.equal('gpl', tokens[4][0].key) + assert.equal('gpl', tokens[4][1]) + assert.equal(11 , tokens[4][2]) + }) + + it('should tokenize a double AND expression with parenthsis', function() { + tokens = [] + for (let token of licensing.tokenize('( mit and gpl ) and apache')) { + tokens.push(token) + } + + assert.ok(tokens.length === 7) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal(TOKEN_LPAR, tokens[0][0]) + assert.equal('(' , tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal('mit', tokens[1][0].key) + assert.equal('mit', tokens[1][1]) + assert.equal(2 , tokens[1][2]) + + assert.equal(TOKEN_AND, tokens[2][0]) + assert.equal('and' , tokens[2][1]) + assert.equal(6 , tokens[2][2]) + + assert.equal('gpl', tokens[3][0].key) + assert.equal('gpl', tokens[3][1]) + assert.equal(10 , tokens[3][2]) + + assert.equal(TOKEN_RPAR, tokens[4][0]) + assert.equal(')' , tokens[4][1]) + assert.equal(14 , tokens[4][2]) + + assert.equal(TOKEN_AND, tokens[5][0]) + assert.equal('and' , tokens[5][1]) + assert.equal(16 , tokens[5][2]) + + assert.equal('apache', tokens[6][0].key) + assert.equal('apache', tokens[6][1]) + assert.equal(20 , tokens[6][2]) + }) + + it('should tokenize a mixed OR-AND expression', function() { + let tokens = [] + for (let token of licensing.tokenize('mit or (bsd and bsd)')) { + tokens.push(token) + } + + assert.ok(tokens.length === 7) + for (let token of tokens) { + assert.ok(token.length === 3) + } + + assert.equal('mit', tokens[0][0].key) + assert.equal('mit', tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal(TOKEN_OR, tokens[1][0]) + assert.equal('or' , tokens[1][1]) + assert.equal(4 , tokens[1][2]) + + assert.equal(TOKEN_LPAR, tokens[2][0]) + assert.equal('(' , tokens[2][1]) + assert.equal(7 , tokens[2][2]) + + assert.equal('bsd', tokens[3][0].key) + assert.equal('bsd', tokens[3][1]) + assert.equal(8 , tokens[3][2]) + + assert.equal(TOKEN_AND, tokens[4][0]) + assert.equal('and' , tokens[4][1]) + assert.equal(12 , tokens[4][2]) + + assert.equal('bsd', tokens[5][0].key) + assert.equal('bsd', tokens[5][1]) + assert.equal(16 , tokens[5][2]) + + assert.equal(TOKEN_RPAR, tokens[6][0]) + assert.equal(')' , tokens[6][1]) + assert.equal(19 , tokens[6][2]) + }) + + it.skip('should tokenize gpl with classpath (an exception)', function() { + let tokens = [] + for (let token of licensing.tokenize('gpl with classpath')) { + tokens.push(token) + } + }) + }) + + describe('tokenize with symbols', function() { + let gpl_20, gpl_20_plus + + beforeEach(function() { + gpl_20 = LicenseSymbol('GPL-2.0', ['The GNU GPL 20']) + gpl_20_plus = LicenseSymbol( + 'gpl-2.0+', [ + 'The GNU GPL 20 or later' + , 'GPL-2.0 or later' + , 'GPL v2.0 or later' + ] + ) + }) + + describe('should work with one predefined symbol (as a string)', function () { + let licensing, tokens + let expressions = ['mit', 'gpl-2.0'] + let operations = [' OR ', ' AND ', ' or ', ' and '] + let identifiers = [TOKEN_OR, TOKEN_AND, TOKEN_OR, TOKEN_AND] + + beforeEach(function() { + tokens = [] + licensing = Licensing(['gpl-2.0']) + }) + + for (let expression of expressions) { + it('should tokenize a single license: ' + expression, function() { + for (let token of licensing.tokenize(expression)) { + tokens.push(token) + } + + assert.equal(1, tokens.length) + for (let token of tokens) { + assert.equal(3, token.length) + } + + assert.equal(expression, tokens[0][0].key) + assert.equal(expression, tokens[0][1]) + assert.equal(0 , tokens[0][2]) + }) + } + + operations.forEach((operation, i) => { + let identifier = identifiers[i] + let expression = expressions[0] + operation + expressions[1] + + it('should tokenize a simple expression: ' + expression, function() { + for (let token of licensing.tokenize(expression)) { + tokens.push(token) + } + + assert.equal(3, tokens.length) + for (let token of tokens) { + assert.equal(3, token.length) + } + + let lft = expressions[0], rgt = expressions[1] + + assert.equal(lft, tokens[0][0].key) + assert.equal(lft, tokens[0][1]) + assert.equal(0 , tokens[0][2]) + + assert.equal(identifier, tokens[1][0]) + assert.equal(operation , tokens[1][1]) + assert.equal(lft.length, tokens[1][2]) + + assert.equal(rgt , tokens[2][0].key) + assert.equal(rgt , tokens[2][1]) + assert.equal(lft.length + operation.length, tokens[2][2]) + }) + }) + }) + + describe('should work with one predefined symbol (as a LicenseSymbol)', function() { + let licensing, tokens + let licenses = ['mit', 'gpl-2.0', 'The GNU GPL 20'] + let expected = ['mit', 'GPL-2.0', 'GPL-2.0'] + let operations = [' OR ', ' AND ', ' or ', ' and '] + let identifiers = [TOKEN_OR, TOKEN_AND, TOKEN_OR, TOKEN_AND] + + beforeEach(function() { + tokens = [], licensing = Licensing([gpl_20]) + }) + + licenses.forEach((license, i) => { + it('should tokenize a single license: ' + license, function() { + for (let token of licensing.tokenize(license)) { + tokens.push(token) + } + + assert.equal(1, tokens.length) + for (let token of tokens) { + assert.equal(3, token.length) + } + + assert.equal(expected[i], tokens[0][0].key) + assert.equal(license , tokens[0][1]) + assert.equal(0 , tokens[0][2]) + }) + }) + + for (let i = 0; i != licenses.length; ++i) { + for (let j = i + 1; j != licenses.length; ++j) { + operations.forEach((operation, k) => { + let expression = licenses[i] + operation + licenses[j] + + it('should tokenize a simple expression: ' + expression, function() { + for (let token of licensing.tokenize(expression)) { + tokens.push(token) + } + + assert(3, tokens.length) + for (let token of tokens) { + assert(3, token.length) + } + + assert(expected[i], tokens[0][0].key) + assert(licenses[i], tokens[0][1]) + + assert(identifiers[k], tokens[1][0]) + assert(operation , tokens[1][1]) + + assert(expected[j], tokens[2][0].key) + assert(licenses[j], tokens[2][1]) + }) + }) + } + } + }) + + describe('should work with several predefined symbols', function() { + let licensing, tokens + let operations = [' OR ', ' AND ', ' or ', ' and '] + let identifiers = [TOKEN_OR, TOKEN_AND, TOKEN_OR, TOKEN_AND] + + beforeEach(function() { + tokens = [] + licensing = Licensing([gpl_20, gpl_20_plus]) + }) + + operations.forEach((operation, i) => { + let expression = 'gpl-2.0' + operation + 'gpl-2.0+' + it('should tokenize a simple expression: ' + expression, function() { + for (let token of licensing.tokenize(expression)) { + tokens.push(token) + } + + assert(3, tokens.length) + assert(gpl_20.key , tokens[0][0].key) + assert(identifiers[i] , tokens[1][0]) + assert(gpl_20_plus.key, tokens[2][0].key) + }) + }) + + it('should tokenize a mixed OR-AND with parenthesis', function() { + let expression = '(gpl-2.0 or gpl-2.0) and mit' + + for (let token of licensing.tokenize(expression)) { + tokens.push(token) + } + + assert(7, tokens.length) + assert(TOKEN_LPAR , tokens[0][0]) + assert(gpl_20.key , tokens[1][0].key) + assert(TOKEN_OR , tokens[2][0]) + assert(gpl_20_plus.key, tokens[3][0].key) + assert(TOKEN_RPAR , tokens[4][0]) + assert(TOKEN_AND , tokens[5][0]) + assert('mit' , tokens[6][0].key) + }) + }) + }) + + describe('parse', function() { + let licensing; + + beforeEach(function() { + licensing = Licensing() + }) + + it('should parse an empty string', function() { + assert.equal(undefined, licensing.parse('')) + }) + + it('should parse a single license', function() { + assert.equal('MIT', licensing.parse('MIT').toString()) + }) + + it('should parse a single OR expression', function() { + let expression = licensing.parse('MIT or GPL') + + assert.ok(expression.__name__ === 'OR') + + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].key === 'MIT') + assert.ok(expression.args[1].key === 'GPL') + }) + + it('should parse a double OR expression', function() { + let expression = licensing.parse('mit or bsd or gpl') + + assert.ok(expression.__name__ === 'OR') + + assert.ok(expression.args.length === 3) + assert.ok(expression.args[0].key === 'mit') + assert.ok(expression.args[1].key === 'bsd') + assert.ok(expression.args[2].key === 'gpl') + }) + + it('should parse a single AND expression', function() { + let expression = licensing.parse('MIT and GPL') + + assert.ok(expression.__name__ === 'AND') + + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].key === 'MIT') + assert.ok(expression.args[1].key === 'GPL') + }) + + it('should parse a double AND expression', function() { + let expression = licensing.parse('mit and bsd and gpl') + + assert.ok(expression.__name__ === 'AND') + + assert.ok(expression.args.length === 3) + assert.ok(expression.args[0].key === 'mit') + assert.ok(expression.args[1].key === 'bsd') + assert.ok(expression.args[2].key === 'gpl') + }) + + it('should parse a single license with parenthesis', function() { + assert.equal('MIT', licensing.parse('(MIT)').toString()) + }) + + it('should parse a single OR expression with parenthesis', function() { + let expression = licensing.parse('(MIT)') + + assert.ok(expression.key === 'MIT') + }) + + it('should parse a double OR expression with parenthesis', function() { + let expression = licensing.parse('(MIT or GPL) or BSD') + + assert.ok(expression.__name__ === 'OR') + + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].__name__ === 'OR') + assert.ok(expression.args[1].key === 'BSD') + + expression = expression.args[0] + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].key === 'MIT') + assert.ok(expression.args[1].key === 'GPL') + }) + + it('should parse a single AND expression with parenthesis', function() { + let expression = licensing.parse('MIT and (GPL)') + + assert.ok(expression.__name__ === 'AND') + + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].key === 'MIT') + assert.ok(expression.args[1].key === 'GPL') + }) + + it('should parse a double AND expression with parenthesis', function() { + let expression = licensing.parse('mit and (gpl and bsd)') + + assert.ok(expression.__name__ === 'AND') + + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].key === 'mit') + assert.ok(expression.args[1].__name__ === 'AND') + + expression = expression.args[1] + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].key === 'gpl') + assert.ok(expression.args[1].key === 'bsd') + }) + + it('should parse a mixed OR-AND expression', function() { + let expression = licensing.parse('mit or (bsd and bsd)') + + assert.ok(expression.__name__ === 'OR') + + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].key === 'mit') + assert.ok(expression.args[1].__name__ === 'AND') + + expression = expression.args[1] + assert.ok(expression.args.length === 2) + assert.ok(expression.args[0].key === 'bsd') + assert.ok(expression.args[1].key === 'bsd') + }) + }) + + describe('parse with symbols', function() { + let gpl_20, gpl_20_plus + + beforeEach(function() { + gpl_20 = LicenseSymbol('GPL-2.0', ['The GNU GPL 20']) + gpl_20_plus = LicenseSymbol( + 'gpl-2.0+', [ + 'The GNU GPL 20 or later' + , 'GPL-2.0 or later' + , 'GPL v2.0 or later' + ] + ) + }) + + it('should parse with one predefined symbol (as a string)', function() { + let licensing = Licensing(['gpl-2.0']) + + let expression = licensing.parse('gpl-2.0') + + assert.equal('gpl-2.0', expression.key) + }) + + it('should parse with one predefined symbol (as a LicenseSymbol)', function() { + let licensing = Licensing([gpl_20]) + + let expression = licensing.parse('gpl-2.0') + + assert.equal(gpl_20.key, expression.key) + }) + + it('should parse with several predefined symbols and aliases', function() { + let licensing = Licensing([gpl_20, gpl_20_plus]) + + let expression = licensing.parse('gpl-2.0+ or gpl-2.0 or GPL-2.0 or later') + + assert.equal('OR', expression.__name__) + assert.equal(3, expression.args.length) + assert.equal(gpl_20_plus.key, expression.args[0].key) + assert.equal(gpl_20.key, expression.args[1].key) + assert.equal(gpl_20_plus.key, expression.args[2].key) + }) + }) +}) diff --git a/tests/test__pyahocorasick.py b/tests/test__pyahocorasick.py index 1d33921..870d082 100644 --- a/tests/test__pyahocorasick.py +++ b/tests/test__pyahocorasick.py @@ -141,7 +141,7 @@ def get_test_automaton(): test_string = "he she himan" t = get_test_automaton() - result = list(t.iter(test_string)) + result = list(t.iterate(test_string)) expected = [ Result(start=0, end=1, string='he', output=Output('he', 'he')), Result(start=3, end=5, string='she', output=Output('she', 'she')), @@ -166,7 +166,7 @@ def get_test_automaton(): test_string = '((l-a + AND l-b) OR (l -c+))' t = get_test_automaton() - result = list(t.iter(test_string)) + result = list(t.iterate(test_string)) expected = [ Result(0, 0, '(', Output('(', '(')), Result(1, 1, '(', Output('(', '(')), @@ -217,7 +217,7 @@ def test_iter_with_unmatched_simple(self): t.add('AND', 'AND') t.make_automaton() test_string = 'AND an a and' - result = list(t.iter(test_string)) + result = list(t.iterate(test_string)) assert 'ANDand' == ''.join(r.string for r in result) def test_iter_with_unmatched_simple2(self): @@ -225,6 +225,5 @@ def test_iter_with_unmatched_simple2(self): t.add('AND', 'AND') t.make_automaton() test_string = 'AND an a and' - result = list(t.iter(test_string)) + result = list(t.iterate(test_string)) assert 'ANDand' == ''.join(r.string for r in result) - diff --git a/tests/test_license_expression.py b/tests/test_license_expression.py index 6bc4b47..6e38c40 100644 --- a/tests/test_license_expression.py +++ b/tests/test_license_expression.py @@ -18,6 +18,8 @@ from __future__ import print_function from __future__ import unicode_literals +import pytest + from collections import OrderedDict from unittest import TestCase import sys @@ -386,6 +388,7 @@ def test_parse_errors_catch_invalid_expression_with_empty_parens(self): expected = {'error_code': PARSE_INVALID_EXPRESSION, 'position': 0, 'token_string': 'with', 'token_type': TOKEN_WITH} assert expected == _parse_error_as_dict(pe) + @pytest.mark.skip(reason='Transcrypt does not support bytes (Python builtin)') def test_parse_errors_catch_invalid_non_unicode_byte_strings_on_python3(self): py2 = sys.version_info[0] == 2 py3 = sys.version_info[0] == 3 diff --git a/thirdparty/dev/PyYAML-3.12.tar.gz b/thirdparty/dev/PyYAML-3.12.tar.gz new file mode 100644 index 0000000..aabee39 Binary files /dev/null and b/thirdparty/dev/PyYAML-3.12.tar.gz differ diff --git a/thirdparty/dev/PyYAML.ABOUT b/thirdparty/dev/PyYAML.ABOUT new file mode 100644 index 0000000..e1048ae --- /dev/null +++ b/thirdparty/dev/PyYAML.ABOUT @@ -0,0 +1,7 @@ +about_resource: PyYAML.tar.gz +download_url: https://pypi.python.org/packages/4a/85/db5a2df477072b2902b0eb892feb37d88ac635d36245a72a6a69b23b383a/PyYAML-3.12.tar.gz#md5=4c129761b661d181ebf7ff4eb2d79950 +version: 3.12 + +name: PyYAML +dje_license: mit +license_text_file: PyYAML.LICENSE diff --git a/thirdparty/dev/PyYAML.LICENSE b/thirdparty/dev/PyYAML.LICENSE new file mode 100644 index 0000000..050ced2 --- /dev/null +++ b/thirdparty/dev/PyYAML.LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/thirdparty/dev/Transcrypt-3.6.47-py2.py3-none-any.whl b/thirdparty/dev/Transcrypt-3.6.47-py2.py3-none-any.whl new file mode 100644 index 0000000..0fce9a2 Binary files /dev/null and b/thirdparty/dev/Transcrypt-3.6.47-py2.py3-none-any.whl differ diff --git a/thirdparty/dev/Transcrypt.ABOUT b/thirdparty/dev/Transcrypt.ABOUT new file mode 100644 index 0000000..5a5dff0 --- /dev/null +++ b/thirdparty/dev/Transcrypt.ABOUT @@ -0,0 +1,11 @@ +about_resource: Transcrypt-3.6.47-py2.py3-none-any.whl +download_url: https://github.com/all3fox/Transcrypt/tree/patched +version: 3.6.47 + +name: Transcrypt +description: Transpile Python 3 to JavaScript +home_url: http://www.transcrypt.org/ +owner: Jacques de Hooge +contact: jacques.de.hooge@qquick.org +dje_license: apache-2.0 +license_text_file: Transcrypt.LICENSE diff --git a/thirdparty/dev/Transcrypt.LICENSE b/thirdparty/dev/Transcrypt.LICENSE new file mode 100644 index 0000000..5d35ccc --- /dev/null +++ b/thirdparty/dev/Transcrypt.LICENSE @@ -0,0 +1,16 @@ +Licence +======= + +Copyright 2014, 2015, 2016 Jacques de Hooge, GEATEC engineering, www.geatec.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/thirdparty/dev/mypy-0.520.tar.gz b/thirdparty/dev/mypy-0.520.tar.gz new file mode 100644 index 0000000..ed22d06 Binary files /dev/null and b/thirdparty/dev/mypy-0.520.tar.gz differ diff --git a/thirdparty/dev/mypy.ABOUT b/thirdparty/dev/mypy.ABOUT new file mode 100644 index 0000000..7a02185 --- /dev/null +++ b/thirdparty/dev/mypy.ABOUT @@ -0,0 +1,7 @@ +about_resource: mypy-0.520.tar.gz +download_url: https://pypi.python.org/packages/e0/a1/b3dd0bc98097d101fb3e5c0712fd985cb42e76663a1cb93ce86b76dc1422/mypy-0.520.tar.gz#md5=c6be8fcdc6bd938e73213dcec99b2ce2 +version: 0.520 + +name: mypy +dje_license: mit and python +license_text_file: mypy.LICENSE diff --git a/thirdparty/dev/mypy.LICENSE b/thirdparty/dev/mypy.LICENSE new file mode 100644 index 0000000..afddd48 --- /dev/null +++ b/thirdparty/dev/mypy.LICENSE @@ -0,0 +1,227 @@ +Mypy is licensed under the terms of the MIT license, reproduced below. + += = = = = + +The MIT License + +Copyright (c) 2015-2016 Jukka Lehtosalo and contributors + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + += = = = = + +Portions of mypy are licensed under different licenses. The files +under stdlib-samples are licensed under the PSF 2 License, reproduced below. + += = = = = + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012 Python Software Foundation; All Rights Reserved" are retained in Python +alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + += = = = = \ No newline at end of file diff --git a/thirdparty/dev/typed-ast-1.0.4.tar.gz b/thirdparty/dev/typed-ast-1.0.4.tar.gz new file mode 100644 index 0000000..fde0131 Binary files /dev/null and b/thirdparty/dev/typed-ast-1.0.4.tar.gz differ diff --git a/thirdparty/dev/typed_ast.ABOUT b/thirdparty/dev/typed_ast.ABOUT new file mode 100644 index 0000000..3810cfc --- /dev/null +++ b/thirdparty/dev/typed_ast.ABOUT @@ -0,0 +1,7 @@ +about_resource: typed-ast-1.0.4.tar.gz +download_url: https://github.com/all3fox/Transcrypt/tree/vendored +version: 1.0.4 + +name: typed-ast +dje_license: apche-2.0 and mit and python +license_text_file: typed-ast.LICENSE diff --git a/thirdparty/dev/typed_ast.LICENSE b/thirdparty/dev/typed_ast.LICENSE new file mode 100644 index 0000000..2ff207b --- /dev/null +++ b/thirdparty/dev/typed_ast.LICENSE @@ -0,0 +1,290 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: typed-ast +Source: https://pypi.python.org/pypi/typed-ast + +Files: * +Copyright: © 2016 David Fisher +License: Apache-2.0 + +Files: * +Copyright: © 2016 David Fisher + © 2008 Armin Ronacher +Comment: The original CPython source is licensed under the + Python Software Foundation License Version 2 +License: Python + +Files: ast27/Parser/spark.py +Copyright: © 1998-2002 John Aycock +License: Expat + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +License: Apache-2.0 + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + . + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + . + 1. Definitions. + . + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + . + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + . + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + . + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + . + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + . + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + . + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + . + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + . + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + . + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + . + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + . + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + . + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + . + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + . + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + . + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + . + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + . + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + . + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + . + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + . + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + . + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + . + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + . + END OF TERMS AND CONDITIONS + . + APPENDIX: How to apply the Apache License to your work. + . + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + . + Copyright 2016 Dropbox, Inc. + . + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +License: Python + PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 + -------------------------------------------- + . + 1. This LICENSE AGREEMENT is between the Python Software Foundation + ("PSF"), and the Individual or Organization ("Licensee") accessing and + otherwise using this software ("Python") in source or binary form and + its associated documentation. + . + 2. Subject to the terms and conditions of this License Agreement, PSF hereby + grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, + analyze, test, perform and/or display publicly, prepare derivative works, + distribute, and otherwise use Python alone or in any derivative version, + provided, however, that PSF's License Agreement and PSF's notice of copyright, + i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011, 2012, 2013, 2014, 2015, 2016 Python Software Foundation; All Rights + Reserved" are retained in Python alone or in any derivative version prepared by + Licensee. + . + 3. In the event Licensee prepares a derivative work that is based on + or incorporates Python or any part thereof, and wants to make + the derivative work available to others as provided herein, then + Licensee hereby agrees to include in any such work a brief summary of + the changes made to Python. + . + 4. PSF is making Python available to Licensee on an "AS IS" + basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR + IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND + DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS + FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT + INFRINGE ANY THIRD PARTY RIGHTS. + . + 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON + FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS + A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, + OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + . + 6. This License Agreement will automatically terminate upon a material + breach of its terms and conditions. + . + 7. Nothing in this License Agreement shall be deemed to create any + relationship of agency, partnership, or joint venture between PSF and + Licensee. This License Agreement does not grant permission to use PSF + trademarks or trade name in a trademark sense to endorse or promote + products or services of Licensee, or any third party. + . + 8. By copying, installing or otherwise using Python, Licensee + agrees to be bound by the terms and conditions of this License + Agreement. \ No newline at end of file diff --git a/thirdparty/prod/boolean.py-3.4-py2.py3-none-any.whl b/thirdparty/prod/boolean.py-3.4-py2.py3-none-any.whl index c43bb7f..3248a68 100644 Binary files a/thirdparty/prod/boolean.py-3.4-py2.py3-none-any.whl and b/thirdparty/prod/boolean.py-3.4-py2.py3-none-any.whl differ diff --git a/transpile/transpile.py b/transpile/transpile.py new file mode 100755 index 0000000..d31373c --- /dev/null +++ b/transpile/transpile.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 + +import argparse +import logging +import logging.config +import shutil +import subprocess +import yaml +import sys + +from argparse import ArgumentParser +from pathlib import Path + + +logger = logging.getLogger(__name__) + +def configure_logging(parent, args): + """ + Configure logging (level, format, etc.) for this module + + Sensible defaults come from 'transpile.yml' and can be changed + by values that come from console. + + :param parent: -- directory where 'transpile.yml' resides + :param args: -- general arguments for transpile + """ + with open(str(Path(parent, 'transpile.yml')), 'r') as config: + params = yaml.load(config) + + logging.config.dictConfig(params['logging']) + + if args.verbose == 0: + pass # use level specified by the config file + elif args.verbose == 1: + logger.setLevel(logging.INFO) + else: + logger.setLevel(logging.DEBUG) + + if args.quiet: + logging.disable(logging.CRITICAL) + # Message below should not reach the user + logger.critical('logging is active despite --quiet') + + logger.debug('configure_logging() done') + +def create_transcrypt_cmd(args, transcrypt_args): + """ + Create a subprocess command that calls transcrypt + + :param args: -- general arguments for transpile + :param transcrypt_args: -- arguments specific to transcrypt + + :returns: a command line suitable for subprocess.run() + """ + logger.debug('create_transcrypt_cmd() call') + + # Assume transcrypt executable is ../bin/transcrypt, full path: + cmd = [str(Path( + Path(__file__).resolve().parent.parent, 'bin', 'transcrypt' + ))] + + # You can specify '--' on the command line to pass parameters + # directly to transcrypt, example: transpile -- --help + # In this case '--' is also passed first, so need to remove it: + if transcrypt_args and transcrypt_args[0] == '--': + transcrypt_args = transcrypt_args[1:] + + cmd += transcrypt_args + + # If you are manually passing transcrypt arguments, please specify + # them all yourself. Otherwise, let me provide sensible defaults: + if not transcrypt_args: + # Force transpiling from scratch + cmd.append('-b') + # Force compatibility with Python truth-value testing. + # There is a warning that this switch will slow everything down a lot. + # This forces empty dictionaries, lists, and tuples to compare as false. + cmd.append('-t') + # Force EcmaScript 6 to enable generators + cmd.append('-e') + cmd.append('6') + + if args.browser: + logger.debug('transpile license_expression for the browser') + + pass + else: + logger.debug('transpile license_expression for node.js') + # Drop global 'window' object and prepare for node.js runtime instead + cmd.append("-p") + cmd.append("module.exports") + + # Supply path to the python file to be transpiled + cmd.append(str(args.src[0])) + + logger.info('constructed the following command') + logger.info(str(cmd)) + + logger.debug('create_transcrypt_cmd() done') + return cmd + +def transpile(): + """ + Call transcrypt to transpile license-expression into JavaScript + """ + + fpath = Path(__file__).resolve() + parent = fpath.parent + + parser = ArgumentParser( + prog=fpath.name, + description="Transpile license-expression into JavaScript" + ) + + # source path, directory where license_expression resides + spath = Path(parent.parent, 'src', 'license_expression') + + # license_expression path + lpath = Path(spath, '__init__.py') + parser.add_argument( + '--src', nargs=1, default=[lpath], + help='start transpilation from here' + ) + + # javascript path, for output + jpath = Path(spath.parent, 'license_expression.js', '__javascript__') + parser.add_argument( + '--dst', nargs=1, default=[jpath], + help='store produced javascript here' + ) + + parser.add_argument( + '-v', '--verbose', action='count', + help='print more output information' + ) + + parser.add_argument( + '--browser', action='store_true', + help='transpile license_expression for the browser' + ) + + parser.add_argument( + '-q', '--quiet', action='store_true', + help='print nothing (transcrypt might print though)' + ) + + args, transcrypt_args = parser.parse_known_args() + + configure_logging(parent, args) + + # User can specify '--quiet' to suppress output. So, delay any + # logging calls until we know the desired verbosity level. + logger.debug('transpile() call') + + logger.debug('.parse_known_args() call') + logger.debug('src: ' + str(args.src)) + logger.debug('dst: ' + str(args.dst)) + logger.debug('transcrypt_args: ' + str(transcrypt_args)) + logger.debug('.parse_known_args() done') + + cmd = create_transcrypt_cmd(args, transcrypt_args) + + logger.debug('subprocess.run() call') + process = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + logger.debug('subprocess.run() done') + + if process.returncode != 0: + logger.warning('Transcrypt failed:') + + for line in str(process.stdout).split('\\n'): + logger.warning(line) + for line in str(process.stderr).split('\\n'): + logger.warning(line) + + sys.exit(1) + + # Transcrypt always puts the transpiled result into __javascript__, + # move it to ./src/license_expression.js, create directories if necessary + stdout = [line for line in str(process.stdout).split('\\n') if line] + lines = list( + filter(lambda line: line.startswith('Saving result in:'), stdout) + ) + + if len(lines) != 1: + logger.warning('Transcrypt output format changed!') + logger.warning('Expected a path to __javascript__ result, instead got:') + + for line in lines: + logger.warning(line) + + src = Path(lines[0].split(': ')[1]).parent + dst = args.dst[0] + + if src != dst: + logger.debug('Copy original __javascript__') + logger.debug('copy src: ' + str(src)) + logger.debug('copy dst: ' + str(dst)) + + if dst.exists(): + logger.debug('Remove previous __javascript__') + shutil.rmtree(str(dst)) + + shutil.copytree(str(src), str(dst)) + + if src.exists(): + logger.debug('Remove original __javascript__') + shutil.rmtree(str(src)) + + logger.debug('transpile() done') + +if __name__ == "__main__": + transpile() diff --git a/transpile/transpile.yml b/transpile/transpile.yml new file mode 100644 index 0000000..1cd1da0 --- /dev/null +++ b/transpile/transpile.yml @@ -0,0 +1,15 @@ +logging: + version: 1 + formatters: + simple: + format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + handlers: + console: + class: logging.StreamHandler + level: DEBUG + formatter: simple + loggers: + __main__: + level: DEBUG + handlers: [console] + propagate: no