Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python 3 compatibility #163

Merged
merged 9 commits into from
Sep 15, 2017
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: 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