Skip to content

Commit

Permalink
Merge pull request #163 from ubi-agni/python3
Browse files Browse the repository at this point in the history
python 3 compatibility
  • Loading branch information
codebot authored Sep 15, 2017
2 parents 1e37f49 + ff574c9 commit 00596f7
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 49 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ sudo: enabled
language: python
python:
- "2.7"
- "3.5"
- "3.6"

# command to install dependencies
install:
Expand Down
43 changes: 20 additions & 23 deletions src/xacro/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,17 +203,13 @@ def __init__(self, parent=None):
@staticmethod
def _eval_literal(value):
if isinstance(value, _basestr):
try:
# try to evaluate as literal, e.g. number, boolean, etc.
# this is needed to handle numbers in property definitions as numbers, not strings
evaluated = ast.literal_eval(value.strip())
# However, (simple) list, tuple, dict expressions will be evaluated as such too,
# which would break expected behaviour. Thus we only accept the evaluation otherwise.
if not isinstance(evaluated, (list, dict, tuple)):
return evaluated
except:
pass

# try to evaluate as number literal or boolean
# this is needed to handle numbers in property definitions as numbers, not strings
for f in [int, float, lambda x: get_boolean_value(x, None)]: # order of types is important!
try:
return f(value)
except:
pass
return value

def _resolve_(self, key):
Expand Down Expand Up @@ -577,10 +573,10 @@ def grab_properties(elt, table):
elt = next


LEXER = QuickLexer(DOLLAR_DOLLAR_BRACE=r"\$\$+\{",
EXPR=r"\$\{[^\}]*\}",
EXTENSION=r"\$\([^\)]*\)",
TEXT=r"([^\$]|\$[^{(]|\$$)+")
LEXER = QuickLexer(DOLLAR_DOLLAR_BRACE=r"^\$\$+(\{|\()", # multiple $ in a row, followed by { or (
EXPR=r"^\$\{[^\}]*\}", # stuff starting with ${
EXTENSION=r"^\$\([^\)]*\)", # stuff starting with $(
TEXT=r"[^$]+|\$[^{($]+|\$$") # any text w/o $ or $ following any chars except {($ or single $


# evaluate text and return typed value
Expand All @@ -600,13 +596,14 @@ def handle_extension(s):
lex = QuickLexer(LEXER)
lex.lex(text)
while lex.peek():
if lex.peek()[0] == lex.EXPR:
id = lex.peek()[0]
if id == lex.EXPR:
results.append(handle_expr(lex.next()[1][2:-1]))
elif lex.peek()[0] == lex.EXTENSION:
elif id == lex.EXTENSION:
results.append(handle_extension(lex.next()[1][2:-1]))
elif lex.peek()[0] == lex.TEXT:
elif id == lex.TEXT:
results.append(lex.next()[1])
elif lex.peek()[0] == lex.DOLLAR_DOLLAR_BRACE:
elif id == lex.DOLLAR_DOLLAR_BRACE:
results.append(lex.next()[1][1:])
# return single element as is, i.e. typed
if len(results) == 1:
Expand Down Expand Up @@ -750,9 +747,9 @@ def get_boolean_value(value, condition):
"""
try:
if isinstance(value, _basestr):
if value == 'true': return True
elif value == 'false': return False
else: return ast.literal_eval(value)
if value == 'true' or value == 'True': return True
elif value == 'false' or value == 'False': return False
else: return bool(int(value))
else:
return bool(value)
except:
Expand Down Expand Up @@ -950,7 +947,7 @@ def process_doc(doc,
if do_check_order and symbols.redefined:
warning("Document is incompatible to --inorder processing.")
warning("The following properties were redefined after usage:")
for k, v in symbols.redefined.iteritems():
for k, v in symbols.redefined.items():
message(k, "redefined in", v, color='yellow')


Expand Down
2 changes: 1 addition & 1 deletion src/xacro/xmlutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
# Authors: Stuart Glaser, William Woodall, Robert Haschke
# Maintainer: Morgan Quigley <morgan@osrfoundation.org>

import xml
import xml.dom.minidom
from .color import warning

def first_child_element(elt):
Expand Down
84 changes: 60 additions & 24 deletions test/test_xacro.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,70 @@
import shutil
import subprocess
import re
from cStringIO import StringIO
import ast
try:
from cStringIO import StringIO # Python 2.x
except ImportError:
from io import StringIO # Python 3.x
from contextlib import contextmanager


# regex to match whitespace
whitespace = re.compile(r'\s+')

def text_values_match(a, b):
# generic comparison
if whitespace.sub(' ', a).strip() == whitespace.sub(' ', b).strip():
return True

try: # special handling of dicts: ignore order
a_dict = ast.literal_eval(a)
b_dict = ast.literal_eval(b)
if (isinstance(a_dict, dict) and isinstance(b_dict, dict) and a_dict == b_dict):
return True
except: # Attribute values aren't dicts
pass

# on failure, try to split a and b at whitespace and compare snippets
def match_splits(a_, b_):
if len(a_) != len(b_): return False
for a, b in zip(a_, b_):
if a == b: continue
try: # compare numeric values only up to some accuracy
if abs(float(a) - float(b)) > 1.0e-9:
return False
except ValueError: # values aren't numeric and not identical
return False
return True

return match_splits(a.split(), b.split())


def all_attributes_match(a, b):
if len(a.attributes) != len(b.attributes):
print("Different number of attributes")
return False
a_atts = [(a.attributes.item(i).name, a.attributes.item(i).value) for i in range(len(a.attributes))]
b_atts = [(b.attributes.item(i).name, b.attributes.item(i).value) for i in range(len(b.attributes))]
a_atts = a.attributes.items()
b_atts = b.attributes.items()
a_atts.sort()
b_atts.sort()

for i in range(len(a_atts)):
if a_atts[i][0] != b_atts[i][0]:
print("Different attribute names: %s and %s" % (a_atts[i][0], b_atts[i][0]))
for a, b in zip(a_atts, b_atts):
if a[0] != b[0]:
print("Different attribute names: %s and %s" % (a[0], b[0]))
return False
if not text_values_match(a[1], b[1]):
print("Different attribute values: %s and %s" % (a[1], b[1]))
return False
try:
if abs(float(a_atts[i][1]) - float(b_atts[i][1])) > 1.0e-9:
print("Different attribute values: %s and %s" % (a_atts[i][1], b_atts[i][1]))
return False
except ValueError: # Attribute values aren't numeric
if a_atts[i][1] != b_atts[i][1]:
print("Different attribute values: %s and %s" % (a_atts[i][1], b_atts[i][1]))
return False

return True


def text_matches(a, b):
a_norm = whitespace.sub(' ', a)
b_norm = whitespace.sub(' ', b)
if a_norm.strip() == b_norm.strip(): return True
if text_values_match(a, b): return True
print("Different text values: '%s' and '%s'" % (a, b))
return False


def nodes_match(a, b, ignore_nodes):
if not a and not b:
return True
Expand Down Expand Up @@ -138,6 +164,16 @@ def test_normalize_whitespace_text(self):
def test_normalize_whitespace_trim(self):
self.assertTrue(text_matches(" foo bar ", "foo \t\n\r bar"))

def test_match_similar_numbers(self):
self.assertTrue(text_matches("0.123456789", "0.123456788"))
def test_mismatch_different_numbers(self):
self.assertFalse(text_matches("0.123456789", "0.1234567879"))

def test_match_unordered_dicts(self):
self.assertTrue(text_matches("{'a': 1, 'b': 2, 'c': 3}", "{'c': 3, 'b': 2, 'a': 1}"))
def test_mismatch_different_dicts(self):
self.assertFalse(text_matches("{'a': 1, 'b': 2, 'c': 3}", "{'c': 3, 'b': 2, 'a': 0}"))

def test_empty_node_vs_whitespace(self):
self.assertTrue(xml_matches('''<foo/>''', '''<foo> \t\n\r </foo>'''))
def test_whitespace_vs_empty_node(self):
Expand Down Expand Up @@ -167,8 +203,8 @@ def test_is_valid_name(self):
def test_resolve_macro(self):
# define three nested macro dicts with the same macro names (keys)
content = {'xacro:simple': 'simple'}
ns2 = dict({k: v+'2' for k,v in content.iteritems()})
ns1 = dict({k: v+'1' for k,v in content.iteritems()})
ns2 = dict({k: v+'2' for k,v in content.items()})
ns1 = dict({k: v+'1' for k,v in content.items()})
ns1.update(ns2=ns2)
macros = dict(content)
macros.update(ns1=ns1)
Expand Down Expand Up @@ -411,13 +447,13 @@ def test_substitution_args_arg(self):

def test_escaping_dollar_braces(self):
self.assert_matches(
self.quick_xacro('''<a b="$${foo}" c="$$${foo}" />'''),
'''<a b="${foo}" c="$${foo}" />''')
self.quick_xacro('''<a b="$${foo}" c="$$${foo}" d="text $${foo}" e="text $$${foo}" f="$$(pwd)" />'''),
'''<a b="${foo}" c="$${foo}" d="text ${foo}" e="text $${foo}" f="$(pwd)" />''')

def test_just_a_dollar_sign(self):
self.assert_matches(
self.quick_xacro('''<a b="$" />'''),
'''<a b="$" />''')
self.quick_xacro('''<a b="$" c="text $" d="text $ text"/>'''),
'''<a b="$" c="text $" d="text $ text"/>''')

def test_multiple_insert_blocks(self):
self.assert_matches(
Expand Down
2 changes: 1 addition & 1 deletion xacro.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
this_dir_cwd = os.getcwd()
os.chdir(cur_dir)
# Remove this dir from path
sys.path = filter(lambda a: a not in [this_dir, this_dir_cwd], sys.path)
sys.path = [a for a in sys.path if a not in [this_dir, this_dir_cwd]]

import xacro
from xacro.color import warning
Expand Down

0 comments on commit 00596f7

Please sign in to comment.