Skip to content

Commit 10461a4

Browse files
authored
Merge pull request #27 from nexB/contains-symbol
Ensure that "Contains" between expressions work with plain symbol
2 parents 2e1b2e4 + 0c6dd52 commit 10461a4

5 files changed

Lines changed: 44 additions & 9 deletions

File tree

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
setup(
1919
name='license-expression',
20-
version='0.96',
20+
version='0.97',
2121
license='apache-2.0',
2222
description=desc,
2323
long_description=desc,

src/license_expression/__init__.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
The main entry point is the Licensing object.
2828
"""
2929

30-
3130
from __future__ import absolute_import
3231
from __future__ import unicode_literals
3332
from __future__ import print_function
@@ -36,10 +35,10 @@
3635
try:
3736
# Python 2
3837
unicode
39-
str = unicode
38+
str = unicode # NOQA
4039
except NameError:
4140
# Python 3
42-
unicode = str
41+
unicode = str # NOQA
4342

4443
import collections
4544
from copy import copy
@@ -71,7 +70,6 @@
7170
from license_expression._pyahocorasick import Output
7271
from license_expression._pyahocorasick import Result
7372

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

120+
122121
class Licensing(boolean.BooleanAlgebra):
123122
"""
124123
Define a mini language to parse, validate and compare license expressions.
@@ -157,6 +156,7 @@ class Licensing(boolean.BooleanAlgebra):
157156
>>> assert l.license_symbols(parsed)[1].as_exception
158157
159158
"""
159+
160160
def __init__(self, symbols=tuple(), quiet=True):
161161
"""
162162
Initialize a Licensing with an optional `symbols` sequence of LicenseSymbol
@@ -571,6 +571,7 @@ class Renderable(object):
571571
"""
572572
An interface for renderable objects.
573573
"""
574+
574575
def render(self, template='{symbol.key}', *args, **kwargs):
575576
"""
576577
Return a formatted string rendering for this expression using the `template`
@@ -598,16 +599,29 @@ def decompose(self):
598599
"""
599600
raise NotImplementedError
600601

602+
def __contains__(self, other):
603+
"""
604+
Test if expr is contained in this symbol.
605+
"""
606+
if not isinstance(other, BaseSymbol):
607+
return False
608+
if self == other:
609+
return True
610+
611+
return any(mine == other for mine in self.decompose())
612+
601613

602614
# validate license keys
603615
is_valid_license_key = re.compile(r'^[-\w\s\.\+]+$', re.UNICODE).match
604616

617+
605618
#FIXME: we need to implement comparison!!!!
606619
@total_ordering
607620
class LicenseSymbol(BaseSymbol):
608621
"""
609622
A LicenseSymbol represents a license as used in a license expression.
610623
"""
624+
611625
def __init__(self, key, aliases=tuple(), is_exception=False, *args, **kwargs):
612626
if not key:
613627
raise ExpressionError(
@@ -708,6 +722,7 @@ class LicenseSymbolLike(LicenseSymbol):
708722
A LicenseSymbolLike object wraps a symbol-like object to expose a LicenseSymbol
709723
behavior.
710724
"""
725+
711726
def __init__(self, symbol_like, *args, **kwargs):
712727
if not self.symbol_like(symbol_like):
713728
raise ExpressionError(
@@ -744,6 +759,7 @@ class LicenseWithExceptionSymbol(BaseSymbol):
744759
license proper and one for the right-hand exception to this license and deals
745760
with the specifics of resolution, validation and representation.
746761
"""
762+
747763
def __init__(self, license_symbol, exception_symbol, strict=False, *args, **kwargs):
748764
"""
749765
Initialize a new LicenseWithExceptionSymbol from a `license_symbol` and a
@@ -861,6 +877,7 @@ class AND(RenderableFunction, boolean.AND):
861877
"""
862878
Custom representation for the AND operator to uppercase.
863879
"""
880+
864881
def __init__(self, *args):
865882
super(AND, self).__init__(*args)
866883
self.operator = ' AND '
@@ -870,6 +887,7 @@ class OR(RenderableFunction, boolean.OR):
870887
"""
871888
Custom representation for the OR operator to uppercase.
872889
"""
890+
873891
def __init__(self, *args):
874892
super(OR, self).__init__(*args)
875893
self.operator = ' OR '

src/license_expression/_pyahocorasick.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
logger = logging.getLogger(__name__)
2626

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

@@ -42,6 +43,7 @@ class Trie(object):
4243
A Trie and Aho-Corasick automaton. This behaves more or less like a mapping of
4344
key->value. This is the main entry point.
4445
"""
46+
4547
def __init__(self, ignore_case=True):
4648
"""
4749
Initialize a new Trie.

tests/test__pyahocorasick.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727

2828
class TestTrie(unittest.TestCase):
29+
2930
def testAddedWordShouldBeCountedAndAvailableForRetrieval(self):
3031
t = Trie()
3132
t.add('python', 'value')
@@ -94,7 +95,6 @@ def testItemsShouldReturnAllItemsAlreadyAddedToTheTrie(self):
9495
self.assertIn(('pascal', 4), result)
9596
self.assertIn(('php', 5), result)
9697

97-
9898
def testKeysShouldReturnAllKeysAlreadyAddedToTheTrie(self):
9999
t = Trie()
100100

@@ -111,7 +111,6 @@ def testKeysShouldReturnAllKeysAlreadyAddedToTheTrie(self):
111111
self.assertIn('pascal', result)
112112
self.assertIn('php', result)
113113

114-
115114
def testValuesShouldReturnAllValuesAlreadyAddedToTheTrie(self):
116115
t = Trie()
117116

@@ -155,6 +154,7 @@ def get_test_automaton():
155154
assert expected == result
156155

157156
def test_iter_vs_scan(self):
157+
158158
def get_test_automaton():
159159
words = "( AND ) OR".split()
160160
t = Trie()
@@ -198,6 +198,7 @@ def get_test_automaton():
198198
assert expected == result
199199

200200
def test_scan_with_unmatched(self):
201+
201202
def get_test_automaton():
202203
words = "( AND ) OR".split()
203204
t = Trie()

tests/test_license_expression.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
1414
# specific language governing permissions and limitations under the License.
1515

16-
1716
from __future__ import absolute_import
1817
from __future__ import print_function
1918
from __future__ import unicode_literals
@@ -22,7 +21,6 @@
2221
from unittest import TestCase
2322
import sys
2423

25-
2624
from boolean.boolean import PARSE_UNBALANCED_CLOSING_PARENS
2725
from boolean.boolean import PARSE_INVALID_SYMBOL_SEQUENCE
2826

@@ -535,6 +533,22 @@ def test_simplify_and_equivalent_and_contains(self):
535533

536534
assert l.contains(expr2, expr4)
537535

536+
def test_contains_works_with_plain_symbol(self):
537+
l = Licensing()
538+
assert not l.contains('mit', 'mit and LGPL-2.1')
539+
assert l.contains('mit and LGPL-2.1', 'mit')
540+
assert l.contains('mit', 'mit')
541+
assert not l.contains(l.parse('mit'), l.parse('mit and LGPL-2.1'))
542+
assert l.contains(l.parse('mit and LGPL-2.1'), l.parse('mit'))
543+
544+
assert l.contains('mit with GPL', 'GPL')
545+
assert l.contains('mit with GPL', 'mit')
546+
assert l.contains('mit with GPL', 'mit with GPL')
547+
assert not l.contains('mit with GPL', 'GPL with mit')
548+
assert not l.contains('mit with GPL', 'GPL and mit')
549+
assert not l.contains('GPL', 'mit with GPL')
550+
assert l.contains('mit with GPL and GPL and BSD', 'GPL and BSD')
551+
538552
def test_create_from_python(self):
539553
# Expressions can be built from Python expressions, using bitwise operators
540554
# between Licensing objects, but use with caution. The behavior is not as

0 commit comments

Comments
 (0)