Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

setup(
name='license-expression',
version='0.96',
version='0.97',
license='apache-2.0',
description=desc,
long_description=desc,
Expand Down
26 changes: 22 additions & 4 deletions src/license_expression/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
The main entry point is the Licensing object.
"""


from __future__ import absolute_import
from __future__ import unicode_literals
from __future__ import print_function
Expand All @@ -36,10 +35,10 @@
try:
# Python 2
unicode
str = unicode
str = unicode # NOQA
except NameError:
# Python 3
unicode = str
unicode = str # NOQA

import collections
from copy import copy
Expand Down Expand Up @@ -71,7 +70,6 @@
from license_expression._pyahocorasick import Output
from license_expression._pyahocorasick import Result


# append new error codes to PARSE_ERRORS by monkey patching
PARSE_EXPRESSION_NOT_UNICODE = 100
if PARSE_EXPRESSION_NOT_UNICODE not in PARSE_ERRORS:
Expand Down Expand Up @@ -119,6 +117,7 @@ class ExpressionError(Exception):
KEYWORDS = tuple(kw.value for kw in _KEYWORDS)
KEYWORDS_STRIPPED = tuple(k.strip() for k in KEYWORDS)


class Licensing(boolean.BooleanAlgebra):
"""
Define a mini language to parse, validate and compare license expressions.
Expand Down Expand Up @@ -157,6 +156,7 @@ class Licensing(boolean.BooleanAlgebra):
>>> assert l.license_symbols(parsed)[1].as_exception

"""

def __init__(self, symbols=tuple(), quiet=True):
"""
Initialize a Licensing with an optional `symbols` sequence of LicenseSymbol
Expand Down Expand Up @@ -571,6 +571,7 @@ class Renderable(object):
"""
An interface for renderable objects.
"""

def render(self, template='{symbol.key}', *args, **kwargs):
"""
Return a formatted string rendering for this expression using the `template`
Expand Down Expand Up @@ -598,16 +599,29 @@ def decompose(self):
"""
raise NotImplementedError

def __contains__(self, other):
"""
Test if expr is contained in this symbol.
"""
if not isinstance(other, BaseSymbol):
return False
if self == other:
return True

return any(mine == other for mine in self.decompose())


# 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):
if not key:
raise ExpressionError(
Expand Down Expand Up @@ -708,6 +722,7 @@ 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):
raise ExpressionError(
Expand Down Expand Up @@ -744,6 +759,7 @@ class LicenseWithExceptionSymbol(BaseSymbol):
license proper and one for the right-hand exception to this license and deals
with the specifics of resolution, validation and representation.
"""

def __init__(self, license_symbol, exception_symbol, strict=False, *args, **kwargs):
"""
Initialize a new LicenseWithExceptionSymbol from a `license_symbol` and a
Expand Down Expand Up @@ -861,6 +877,7 @@ class AND(RenderableFunction, boolean.AND):
"""
Custom representation for the AND operator to uppercase.
"""

def __init__(self, *args):
super(AND, self).__init__(*args)
self.operator = ' AND '
Expand All @@ -870,6 +887,7 @@ class OR(RenderableFunction, boolean.OR):
"""
Custom representation for the OR operator to uppercase.
"""

def __init__(self, *args):
super(OR, self).__init__(*args)
self.operator = ' OR '
Expand Down
2 changes: 2 additions & 0 deletions src/license_expression/_pyahocorasick.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

logger = logging.getLogger(__name__)


def logger_debug(*args):
return logger.debug(' '.join(isinstance(a, str) and a or repr(a) for a in args))

Expand All @@ -42,6 +43,7 @@ class Trie(object):
A Trie and Aho-Corasick automaton. This behaves more or less like a mapping of
key->value. This is the main entry point.
"""

def __init__(self, ignore_case=True):
"""
Initialize a new Trie.
Expand Down
5 changes: 3 additions & 2 deletions tests/test__pyahocorasick.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@


class TestTrie(unittest.TestCase):

def testAddedWordShouldBeCountedAndAvailableForRetrieval(self):
t = Trie()
t.add('python', 'value')
Expand Down Expand Up @@ -94,7 +95,6 @@ def testItemsShouldReturnAllItemsAlreadyAddedToTheTrie(self):
self.assertIn(('pascal', 4), result)
self.assertIn(('php', 5), result)


def testKeysShouldReturnAllKeysAlreadyAddedToTheTrie(self):
t = Trie()

Expand All @@ -111,7 +111,6 @@ def testKeysShouldReturnAllKeysAlreadyAddedToTheTrie(self):
self.assertIn('pascal', result)
self.assertIn('php', result)


def testValuesShouldReturnAllValuesAlreadyAddedToTheTrie(self):
t = Trie()

Expand Down Expand Up @@ -155,6 +154,7 @@ def get_test_automaton():
assert expected == result

def test_iter_vs_scan(self):

def get_test_automaton():
words = "( AND ) OR".split()
t = Trie()
Expand Down Expand Up @@ -198,6 +198,7 @@ def get_test_automaton():
assert expected == result

def test_scan_with_unmatched(self):

def get_test_automaton():
words = "( AND ) OR".split()
t = Trie()
Expand Down
18 changes: 16 additions & 2 deletions tests/test_license_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.


from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
Expand All @@ -22,7 +21,6 @@
from unittest import TestCase
import sys


from boolean.boolean import PARSE_UNBALANCED_CLOSING_PARENS
from boolean.boolean import PARSE_INVALID_SYMBOL_SEQUENCE

Expand Down Expand Up @@ -535,6 +533,22 @@ def test_simplify_and_equivalent_and_contains(self):

assert l.contains(expr2, expr4)

def test_contains_works_with_plain_symbol(self):
l = Licensing()
assert not l.contains('mit', 'mit and LGPL-2.1')
assert l.contains('mit and LGPL-2.1', 'mit')
assert l.contains('mit', 'mit')
assert not l.contains(l.parse('mit'), l.parse('mit and LGPL-2.1'))
assert l.contains(l.parse('mit and LGPL-2.1'), l.parse('mit'))

assert l.contains('mit with GPL', 'GPL')
assert l.contains('mit with GPL', 'mit')
assert l.contains('mit with GPL', 'mit with GPL')
assert not l.contains('mit with GPL', 'GPL with mit')
assert not l.contains('mit with GPL', 'GPL and mit')
assert not l.contains('GPL', 'mit with GPL')
assert l.contains('mit with GPL and GPL and BSD', 'GPL and BSD')

def test_create_from_python(self):
# Expressions can be built from Python expressions, using bitwise operators
# between Licensing objects, but use with caution. The behavior is not as
Expand Down