diff --git a/.travis.yml b/.travis.yml
index bb006db..c471dc8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,10 +2,15 @@ language: python
 env: # These should match the tox env list
     - TOXENV=py26
     - TOXENV=py27
+    - TOXENV=py34
 install:
-    - pip install coveralls tox --use-mirrors
+    - pip install coveralls tox
     - npm install reflect
 script: tox
 after_success:
     - coveralls
 sudo: false
+cache:
+    directories:
+        - $HOME/.cache/pip
+        - $HOME/.pre-commit
diff --git a/refactorlib/cheetah/node.py b/refactorlib/cheetah/node.py
index 755fbfa..48e163f 100644
--- a/refactorlib/cheetah/node.py
+++ b/refactorlib/cheetah/node.py
@@ -1,20 +1,28 @@
 """
 cheetah-specific additions to the lxml element node class.
 """
+import six
+
 from lxml import etree
 from refactorlib.node import RefactorLibNodeBase, one
 
 
 class CheetahNodeBase(RefactorLibNodeBase):
     def find_calls(self, func_name):
+        if isinstance(func_name, bytes):
+            func_name = func_name.decode('UTF-8')
         return self.xpath(
             './/Placeholder'
             '[./CheetahVarNameChunks/CallArgString]'
-            '[./CheetahVarNameChunks/DottedName="%s"]' % func_name
+            '[./CheetahVarNameChunks/DottedName="{0}"]'.format(
+                func_name,
+            )
         ) + self.xpath(
             './/CheetahVar'
             '[./CheetahVarBody/CheetahVarNameChunks/CallArgString]'
-            '[./CheetahVarBody/CheetahVarNameChunks/DottedName="%s"]' % func_name
+            '[./CheetahVarBody/CheetahVarNameChunks/DottedName="{0}"]'.format(
+                func_name,
+            )
         )
 
     def find_decorators(self, dec_name):
@@ -72,6 +80,8 @@ class CheetahNodeBase(RefactorLibNodeBase):
         return comment
 
     def is_in_context(self, directive_string):
+        if isinstance(directive_string, bytes):
+            directive_string = directive_string.decode('UTF-8')
         try:
             directive_name, var = directive_string.split(None, 1)
         except ValueError:
@@ -86,7 +96,7 @@ class CheetahNodeBase(RefactorLibNodeBase):
                     directive.name == directive_name and
                     (
                         directive.var is None and var is None or
-                        directive.var.totext(with_tail=False) == var
+                        directive.var.totext(with_tail=False).decode('UTF-8') == var
                     )
             ):
                 return True
@@ -158,7 +168,7 @@ class CheetahVariable(CheetahNodeBase):
         args = self.args
 
         if not args:  # no arguments.
-            assert args_container.totext().strip('(\n\t )') == '', args_container.totext()
+            assert args_container.totext().strip(b'(\n\t )') == b'', args_container.totext()
             self.remove_self()
             return
 
@@ -225,7 +235,7 @@ class CheetahDecorator(CheetahNodeBase):
 
 class CheetahDirective(CheetahNodeBase):
     def replace_directive(self, other):
-        if isinstance(other, basestring):
+        if isinstance(other, six.string_types):
             var = self.makeelement('CheetahVar')
             try:
                 directive, var.text = other.split(None, 1)
@@ -251,7 +261,7 @@ class CheetahDirective(CheetahNodeBase):
     @property
     def is_multiline_directive(self):
         return (
-            self.totext().strip().endswith(':') or
+            self.totext().strip().endswith(b':') or
             not self.xpath(
                 './self::Directive[starts-with(., "#end")] or '
                 './SimpleExprDirective or '
diff --git a/refactorlib/cheetah/parse.py b/refactorlib/cheetah/parse.py
index ac908b1..2c86bbc 100644
--- a/refactorlib/cheetah/parse.py
+++ b/refactorlib/cheetah/parse.py
@@ -153,7 +153,7 @@ def detect_encoding(source):
 
 
 def parse(cheetah_content, encoding=None):
-    # yelp_cheetah requires unicode
+    # yelp_cheetah requires text
     if type(cheetah_content) is bytes:
         cheetah_content = cheetah_content.decode('UTF-8')
 
diff --git a/refactorlib/cli/xmlstrip.py b/refactorlib/cli/xmlstrip.py
index 376d498..79c4e0b 100644
--- a/refactorlib/cli/xmlstrip.py
+++ b/refactorlib/cli/xmlstrip.py
@@ -7,7 +7,7 @@ This is the inverse operation of `xmlfrom`.
 
 def xmlstrip(filename):
     from lxml.etree import XML, tostring
-    tree = XML(open(filename).read()).getroottree()
+    tree = XML(open(filename, 'rb').read()).getroottree()
     encoding = tree.docinfo.encoding
     return tostring(tree, method='text', encoding=encoding)
 
diff --git a/refactorlib/javascript/parse.py b/refactorlib/javascript/parse.py
index 73038bf..107eb65 100644
--- a/refactorlib/javascript/parse.py
+++ b/refactorlib/javascript/parse.py
@@ -1,3 +1,5 @@
+import six
+
 from refactorlib.util import static
 
 
@@ -5,8 +7,7 @@ DEBUG = False
 
 
 def parse(javascript_contents, encoding='ascii'):
-    """
-    Given some javascript contents, as a unicode string, return the lxml representation.
+    """Given some javascript contents, as a text string, return the lxml representation.
     "reflectjs" below refers to the Mozilla Reflect protocol:
         * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
         * https://npmjs.org/package/reflect
@@ -36,7 +37,10 @@ def reflectjs_parse(javascript_contents):
     from refactorlib.util import Popen, PIPE
     from os.path import join
     from simplejson import loads
-    from simplejson.ordered_dict import OrderedDict
+    try:
+        from collections import OrderedDict
+    except ImportError:
+        from ordereddict import OrderedDict
     reflectjs_script = join(TOP, 'javascript/reflectjs.js')
 
     reflectjs = Popen([find_nodejs(), reflectjs_script], stdin=PIPE, stdout=PIPE)
@@ -56,7 +60,6 @@ def reflectjs_to_dictnode(tree):
     text or tail, and may have some overlap issues.
     """
     from refactorlib.dictnode import DictNode
-    from types import NoneType
 
     root_dictnode = DictNode(parent=None)
     stack = [(tree, root_dictnode)]
@@ -78,15 +81,17 @@ def reflectjs_to_dictnode(tree):
                 else:
                     attrs[val['type']] = val['name']
             elif attr == 'value':
-                attrs[attr] = unicode(val)
+                attrs[attr] = six.text_type(val)
                 # We would normally lose this type information, as lxml
                 # wants everything to be a string.
                 attrs['type'] = type(val).__name__
-            elif isinstance(val, unicode):
+            elif isinstance(val, six.text_type):
                 attrs[attr] = val
-            elif isinstance(val, (bool, NoneType, str)):
+            elif isinstance(val, bytes):
+                attrs[attr] = val.decode('UTF-8')
+            elif isinstance(val, (bool, type(None))):
                 # TODO: figure out what happens with non-ascii data.
-                attrs[attr] = unicode(val)
+                attrs[attr] = six.text_type(val)
             else:  # Should never happen
                 assert False
 
@@ -97,5 +102,5 @@ def reflectjs_to_dictnode(tree):
             children=[DictNode(parent=dictnode) for child in children],
             attrs=attrs,
         ))
-        stack.extend(reversed(zip(children, dictnode['children'])))
+        stack.extend(reversed(list(zip(children, dictnode['children']))))
     return root_dictnode
diff --git a/refactorlib/parse.py b/refactorlib/parse.py
index 8e93315..d641074 100644
--- a/refactorlib/parse.py
+++ b/refactorlib/parse.py
@@ -5,18 +5,15 @@ def parse(filename, filetype=None, encoding=None):
     from refactorlib.filetypes import FILETYPES
     filetype = FILETYPES.detect_filetype(filename, filetype)
 
-    source = open(filename).read()
+    source = open(filename, 'rb').read()
 
     # If no encoding was explicitly specified, see if we can parse
     # it out from the contents of the file.
     if encoding is None:
         encoding = filetype.encoding_detector(source)
 
-    if encoding:
-        source = unicode(source, encoding)
-    else:
-        # I don't see why encoding=None is different from not specifying the encoding.
-        source = unicode(source)
+    encoding = encoding if encoding else 'UTF-8'
+    source = source.decode(encoding)
 
     return filetype.parser(source, encoding)
 
@@ -36,10 +33,7 @@ def dictnode_to_lxml(tree, node_lookup=None, encoding=None):
     if not node_lookup:
         from refactorlib.node import node_lookup
 
-    from lxml.etree import XMLParser, fromstring
-    lxml_parser_object = XMLParser(encoding=encoding)
-    lxml_parser_object.set_element_class_lookup(node_lookup)
-    Element = lxml_parser_object.makeelement
+    from lxml.etree import Element, XMLParser
 
     root = None
     stack = [(tree, root)]
@@ -50,7 +44,10 @@ def dictnode_to_lxml(tree, node_lookup=None, encoding=None):
         if parent is None:
             # We use this roundabout method becuase the encoding is always set
             # to 'UTF8' if we use parser.makeelement()
-            lxmlnode = fromstring('<trash></trash>', parser=lxml_parser_object)
+            parser = XMLParser(encoding=encoding)
+            parser.set_element_class_lookup(node_lookup)
+            parser.feed(b'<trash></trash>')
+            lxmlnode = parser.close()
             lxmlnode.tag = node['name']
             lxmlnode.attrib.update(node.get('attrs', {}))
             root = lxmlnode
diff --git a/refactorlib/python/parse.py b/refactorlib/python/parse.py
index 28a477b..a3cbc27 100644
--- a/refactorlib/python/parse.py
+++ b/refactorlib/python/parse.py
@@ -3,7 +3,7 @@ import re
 
 
 # regex taken from inducer/pudb's detect_encoding
-pythonEncodingDirectiveRE = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)")
+encoding_re = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)".encode('UTF-8'))
 
 
 def detect_encoding(source):
@@ -11,20 +11,19 @@ def detect_encoding(source):
     Given some python contents as a byte string, return the name of the encoding, or else None.
     """
     # According to the PEP0263, the encoding directive must appear on one of the first two lines of the file
-    top_lines = source.split('\n', 2)[:2]
+    top_lines = source.split(b'\n', 2)[:2]
 
     for line in top_lines:
-        encodingMatch = pythonEncodingDirectiveRE.search(line)
-        if encodingMatch:
-            return encodingMatch.group(1)
+        encoding_match = encoding_re.search(line)
+        if encoding_match:
+            return encoding_match.group(1).decode('UTF-8')
 
     # We didn't find anything.
     return None
 
 
 def parse(python_contents, encoding):
-    """
-    Given some python contents as a unicode string, return the lxml representation.
+    """Given some python contents as a text string, return the lxml representation.
     """
     lib2to3_python = lib2to3_parse(python_contents)
     dictnode_python = lib2to3_to_dictnode(lib2to3_python)
diff --git a/refactorlib/util.py b/refactorlib/util.py
index 9b9d983..1c5e01d 100644
--- a/refactorlib/util.py
+++ b/refactorlib/util.py
@@ -42,7 +42,7 @@ class Popen(_Popen):
         CalledProcessError object will have the return code in the returncode
         attribute and output in the output attribute.
         """
-        output, _ = self.communicate(stdin)
+        output, _ = self.communicate(stdin.encode('UTF-8'))
         retcode = self.poll()
         if retcode:
             cmd = self.args
diff --git a/setup.py b/setup.py
index 8397031..98690bc 100755
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,8 @@ setuptools.setup(
     install_requires=[
         'cached-property',
         'lxml>=2.2',  # We run with 2.2.4.0
+        'ordereddict',
+        'six',
     ],
     extras_require={
         'javascript': ['simplejson'],
@@ -48,6 +50,8 @@ setuptools.setup(
         'Programming Language :: Python :: 2',
         'Programming Language :: Python :: 2.6',
         'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.4',
         'Topic :: Software Development :: Libraries :: Python Modules',
     ],
 )
diff --git a/tox.ini b/tox.ini
index 9bd7b2c..2737f17 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,10 @@
 [tox]
 project = refactorlib
 # These should match the travis env list
-envlist = py26,py27
+envlist = py26,py27,py34
 skipsdist = true
 
 [testenv]
-install_command = pip install --use-wheel {opts} {packages}
 deps = -rrequirements-dev.txt
 commands =
     coverage erase

Support python3
diff --git a/testing/util.py b/testing/util.py
index 0170f44..fa9afce 100644
--- a/testing/util.py
+++ b/testing/util.py
@@ -1,6 +1,8 @@
 """
 A home for the 'yellow code' of testing.
 """
+from __future__ import unicode_literals
+
 from os.path import join
 
 
@@ -16,7 +18,6 @@ def example_dir(func):
 
 
 def get_examples(func):
-
     from os import listdir
     from os.path import isfile
 
@@ -77,13 +78,13 @@ def parametrize(arg_finder):
 def assert_same_content(old_file, new_content, extra_suffix=''):
     new_file = ''.join((old_file, extra_suffix, FAILURE_SUFFIX))
     try:
-        open(new_file, 'w').write(new_content)
+        open(new_file, 'wb').write(new_content)
     except IOError as e:
         if e.errno == 2:  # No such file.
             from os import makedirs
             from os.path import dirname
             makedirs(dirname(new_file))
-            open(new_file, 'w').write(new_content)
+            open(new_file, 'wb').write(new_content)
         else:
             raise
 
@@ -91,15 +92,16 @@ def assert_same_content(old_file, new_content, extra_suffix=''):
 
 
 def assert_same_file_content(old_file, new_file):
-    old_content = open(old_file).readlines()
-    new_content = open(new_file).readlines()
+    old_content = open(old_file, 'rb').readlines()
+    new_content = open(new_file, 'rb').readlines()
 
     diffs = diff(old_content, new_content)
 
     if diffs:
         diffs = 'Results differ:\n--- %s\n+++ %s\n%s' % (old_file, new_file, diffs)
-        # py.test derps on non-utf8 bytes, so I force unicode like so:
-        diffs = diffs.decode('UTF-8', 'replace')
+        # py.test derps on non-utf8 bytes, so I force text like so:
+        if isinstance(diffs, bytes):
+            diffs = diffs.decode('UTF-8', 'replace')
         raise AssertionError(diffs)
     else:
         from os import unlink
@@ -112,7 +114,7 @@ def diff(old_content, new_content, n=3):
     diffdata = tuple(diff(old_content, new_content))
     difflines = set()
     for lineno, line in enumerate(diffdata):
-        if not line.startswith('  '):  # Ignore the similar lines.
+        if not line.startswith(str('  ')):  # Ignore the similar lines.
             difflines.update(range(lineno - n, lineno + n + 1))
 
     return '\n'.join(
diff --git a/tests/cheetah/add_comment_test.py b/tests/cheetah/add_comment_test.py
index 15ecc6c..33f78c5 100644
--- a/tests/cheetah/add_comment_test.py
+++ b/tests/cheetah/add_comment_test.py
@@ -30,7 +30,7 @@ def test_can_add_comments():
     calls[0].add_comment('2 esc')
     calls[0].add_comment('3 esc')
 
-    assert '''
+    assert b'''
         #def foo()
             ## 1 esc
             ## 2 esc
diff --git a/tests/cheetah/directive_test.py b/tests/cheetah/directive_test.py
index 2adf1fe..01071b0 100644
--- a/tests/cheetah/directive_test.py
+++ b/tests/cheetah/directive_test.py
@@ -14,19 +14,19 @@ def test_find_end_directive(example, output):
     new_output = []
     for directive in lxmlnode.xpath('//Directive'):
         new_output.append(
-            'Directive: %s' % tree.getpath(directive),
+            b'Directive: ' + tree.getpath(directive).encode('UTF-8'),
         )
         if directive.is_multiline_directive:
             new_output.append(
-                'End: %s' % tree.getpath(directive.get_end_directive()),
+                b'End: ' + tree.getpath(directive.get_end_directive()).encode('UTF-8'),
             )
         else:
             new_output.append(
-                'Single-line: %s' % directive.totext()
+                b'Single-line: ' + directive.totext()
             )
-        new_output.append('')
+        new_output.append(b'')
 
-    new_output = '\n'.join(new_output)
+    new_output = b'\n'.join(new_output)
     assert_same_content(output, new_output)
 
 
@@ -40,7 +40,7 @@ def test_replace_directive(example, output):
         if directive.var is None:
             directive.replace_directive('#{{{%s}}}' % directive.name)
         else:
-            directive.replace_directive('#{{{%s}}} [%s]' % (directive.name, directive.var.totext(with_tail=False)))
+            directive.replace_directive('#{{{%s}}} [%s]' % (directive.name, directive.var.totext(with_tail=False).decode('UTF-8')))
 
     new_output = lxmlnode.totext()
     assert_same_content(output, new_output)
@@ -67,11 +67,11 @@ def test_get_enclosing_blocks(example, output):
     new_output = []
     for context, directive in sorted(unique_contexts.items()):
         new_output.append(
-            'Directive: %s' % tree.getpath(directive)
+            b'Directive: ' + tree.getpath(directive).encode('UTF-8')
         )
         for c in context:
-            new_output.append('  ' + c)
-        new_output.append('')
+            new_output.append(b'  ' + c.encode('UTF-8'))
+        new_output.append(b'')
 
-    new_output = '\n'.join(new_output)
+    new_output = b'\n'.join(new_output)
     assert_same_content(output, new_output)
diff --git a/tests/cheetah/is_in_context_test.py b/tests/cheetah/is_in_context_test.py
index 456c9ba..0a127ae 100644
--- a/tests/cheetah/is_in_context_test.py
+++ b/tests/cheetah/is_in_context_test.py
@@ -1,3 +1,5 @@
+import six
+
 from testing.util import parametrize, get_output, assert_same_content
 from . import xfailif_no_cheetah
 
@@ -11,9 +13,9 @@ def test_is_in_context(example, output):
 
     top_level_directives = lxmlnode.xpath('/cheetah/*/*[1][self::Directive]')
     top_level_directives = [
-        "#%s %s" % (d.name, d.var.totext(with_tail=False))
-        if d.var else
-        "#%s" % d.name
+        b'#' + d.name.encode('UTF-8') + b' ' + d.var.totext(with_tail=False)
+        if d.var is not None else
+        b'#' + d.name.encode('UTF-8')
         for d in top_level_directives
     ]
 
@@ -23,13 +25,14 @@ def test_is_in_context(example, output):
     new_output = []
     for placeholder in lxmlnode.xpath('//Placeholder'):
         new_output.append(
-            'Placeholder: %s' % placeholder.totext(with_tail=False)
+            b'Placeholder: ' + placeholder.totext(with_tail=False)
         )
         for d in top_level_directives:
             new_output.append(
-                '    %s %s' % (d, placeholder.is_in_context(d))
+                b'    ' + d + b' ' +
+                six.text_type(placeholder.is_in_context(d)).encode('UTF-8')
             )
-        new_output.append('')
+        new_output.append(b'')
 
-    new_output = '\n'.join(new_output)
+    new_output = b'\n'.join(new_output)
     assert_same_content(output, new_output)
diff --git a/tests/cheetah/parse_test.py b/tests/cheetah/parse_test.py
index 418865e..059804a 100644
--- a/tests/cheetah/parse_test.py
+++ b/tests/cheetah/parse_test.py
@@ -6,7 +6,7 @@ from . import xfailif_no_cheetah
 @xfailif_no_cheetah
 @parametrize(get_examples)
 def test_can_make_round_trip(example):
-    text = open(example).read()
+    text = open(example, 'rb').read()
     example = parse(example)
     assert text == example.totext()
 
diff --git a/tests/cheetah/remove_call_test.py b/tests/cheetah/remove_call_test.py
index 8f36629..1084902 100644
--- a/tests/cheetah/remove_call_test.py
+++ b/tests/cheetah/remove_call_test.py
@@ -12,7 +12,7 @@ def test_can_find_calls():
 
     calls = example.find_calls('foo')
     assert len(calls) == 1
-    assert calls[0].totext() == '$foo()'
+    assert calls[0].totext() == b'$foo()'
 
 
 @xfailif_no_cheetah
diff --git a/tests/javascript/parse_test.py b/tests/javascript/parse_test.py
index 2c86c0d..2122e8c 100644
--- a/tests/javascript/parse_test.py
+++ b/tests/javascript/parse_test.py
@@ -27,7 +27,7 @@ else:
 def test_can_make_round_trip(example):
     text = open(example).read()
     example = parse(example)
-    assert text == example.totext()
+    assert text == example.totext().decode('UTF-8')
 
 
 @xfailif_no_js
diff --git a/tests/python/parse_test.py b/tests/python/parse_test.py
index a0158b6..5677255 100644
--- a/tests/python/parse_test.py
+++ b/tests/python/parse_test.py
@@ -4,7 +4,7 @@ from refactorlib.parse import parse
 
 @parametrize(get_examples)
 def test_can_make_round_trip(example):
-    text = open(example).read()
+    text = open(example, 'rb').read()
     example = parse(example)
     assert text == example.totext()
 
@@ -12,7 +12,7 @@ def test_can_make_round_trip(example):
 @parametrize(get_examples)
 def test_encoding_detection(example):
     from refactorlib.python.parse import detect_encoding
-    text = open(example).read()
+    text = open(example, 'rb').read()
     example = parse(example)
     detected_encoding = detect_encoding(text)
 

pip install -e .[cheetah,javascript]
parse_log_pytest
requirements.txt
pytest
3.9
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
astroid==3.3.9
cached-property==2.0.1
cfgv==3.4.0
coverage==7.8.0
dill==0.3.9
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.18.0
flake8==7.2.0
identify==2.6.9
iniconfig==2.1.0
isort==6.0.1
lxml==5.3.1
MarkupSafe==3.0.2
mccabe==0.7.0
mock==5.2.0
nodeenv==1.9.1
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
pre_commit==4.2.0
pycodestyle==2.13.0
pyflakes==3.3.1
pylint==3.3.6
pytest==8.3.5
PyYAML==6.0.2
-e git+https://github.com/bukzor/RefactorLib.git@181ff5525a5904eb7cf31653e80723450b7fc45e#egg=refactorlib
simplejson==3.20.1
six==1.17.0
tomli==2.2.1
tomlkit==0.13.2
typing_extensions==4.13.0
virtualenv==20.29.3
yelp_cheetah==0.12.1

name: RefactorLib
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - argparse==1.4.0
      - astroid==3.3.9
      - cached-property==2.0.1
      - cfgv==3.4.0
      - coverage==7.8.0
      - dill==0.3.9
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==7.2.0
      - identify==2.6.9
      - iniconfig==2.1.0
      - isort==6.0.1
      - lxml==5.3.1
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - mock==5.2.0
      - nodeenv==1.9.1
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pre-commit==4.2.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.1
      - pylint==3.3.6
      - pytest==8.3.5
      - pyyaml==6.0.2
      - simplejson==3.20.1
      - six==1.17.0
      - tomli==2.2.1
      - tomlkit==0.13.2
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
      - yelp-cheetah==0.12.1
prefix: /opt/conda/envs/RefactorLib

tests/cheetah/directive_test.py::test_find_end_directive[tests/cheetah/directive_data/single-line.tmpl-tests/cheetah/directive_data/test_find_end_directive/single-line.txt] tests/cheetah/directive_test.py::test_find_end_directive[tests/cheetah/directive_data/simple.tmpl-tests/cheetah/directive_data/test_find_end_directive/simple.txt] tests/cheetah/directive_test.py::test_find_end_directive[tests/cheetah/directive_data/nested.tmpl-tests/cheetah/directive_data/test_find_end_directive/nested.txt] tests/cheetah/directive_test.py::test_replace_directive[tests/cheetah/directive_data/single-line.tmpl-tests/cheetah/directive_data/test_replace_directive/single-line.tmpl] tests/cheetah/directive_test.py::test_replace_directive[tests/cheetah/directive_data/simple.tmpl-tests/cheetah/directive_data/test_replace_directive/simple.tmpl] tests/cheetah/directive_test.py::test_replace_directive[tests/cheetah/directive_data/nested.tmpl-tests/cheetah/directive_data/test_replace_directive/nested.tmpl] tests/cheetah/is_in_context_test.py::test_is_in_context[tests/cheetah/is_in_context_data/simple.tmpl-tests/cheetah/is_in_context_data/test_is_in_context/simple.txt] tests/cheetah/is_in_context_test.py::test_is_in_context[tests/cheetah/is_in_context_data/indirect.tmpl-tests/cheetah/is_in_context_data/test_is_in_context/indirect.txt] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/example1.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/continuation.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/all_directives.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/unicode.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/single-line.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/simple_call.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/simple.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/nested_call.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/nested2.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/nested.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/multiline.tmpl] tests/cheetah/parse_test.py::test_can_make_round_trip[tests/cheetah/parse_data/complex_call.tmpl] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/example1.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/example1.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/continuation.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/continuation.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/all_directives.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/all_directives.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/unicode.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/unicode.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/single-line.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/single-line.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/simple_call.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/simple_call.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/simple.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/simple.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/nested_call.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/nested_call.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/nested2.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/nested2.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/nested.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/nested.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/multiline.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/multiline.xml] tests/cheetah/parse_test.py::test_matches_known_good_parsing[tests/cheetah/parse_data/complex_call.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/complex_call.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/example1.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/example1.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/continuation.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/continuation.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/all_directives.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/all_directives.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/unicode.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/unicode.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/single-line.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/single-line.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/simple_call.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/simple_call.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/simple.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/simple.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/nested_call.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/nested_call.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/nested2.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/nested2.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/nested.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/nested.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/multiline.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/multiline.xml] tests/cheetah/parse_test.py::test_cli_output[tests/cheetah/parse_data/complex_call.tmpl-tests/cheetah/parse_data/test_matches_known_good_parsing/complex_call.xml] tests/cheetah/remove_call_test.py::test_can_remove_calls[tests/cheetah/remove_call_data/simple.tmpl-tests/cheetah/remove_call_data/test_can_remove_calls/simple.tmpl] tests/cheetah/remove_call_test.py::test_can_remove_calls[tests/cheetah/remove_call_data/multiline.tmpl-tests/cheetah/remove_call_data/test_can_remove_calls/multiline.tmpl] tests/python/parse_test.py::test_can_make_round_trip[tests/python/parse_data/unicode2.py] tests/python/parse_test.py::test_can_make_round_trip[tests/python/parse_data/unicode.py] tests/python/parse_test.py::test_can_make_round_trip[tests/python/parse_data/example2_utf8.py] tests/python/parse_test.py::test_can_make_round_trip[tests/python/parse_data/example1.py] tests/python/parse_test.py::test_can_make_round_trip[tests/python/parse_data/comment_only.py] tests/python/parse_test.py::test_encoding_detection[tests/python/parse_data/unicode2.py] tests/python/parse_test.py::test_encoding_detection[tests/python/parse_data/unicode.py] tests/python/parse_test.py::test_encoding_detection[tests/python/parse_data/example1.py] tests/python/parse_test.py::test_matches_known_good_parsing[tests/python/parse_data/unicode2.py-tests/python/parse_data/test_matches_known_good_parsing/unicode2.xml] tests/python/parse_test.py::test_matches_known_good_parsing[tests/python/parse_data/unicode.py-tests/python/parse_data/test_matches_known_good_parsing/unicode.xml] tests/python/parse_test.py::test_matches_known_good_parsing[tests/python/parse_data/example2_utf8.py-tests/python/parse_data/test_matches_known_good_parsing/example2_utf8.xml] tests/python/parse_test.py::test_matches_known_good_parsing[tests/python/parse_data/example1.py-tests/python/parse_data/test_matches_known_good_parsing/example1.xml] tests/python/parse_test.py::test_matches_known_good_parsing[tests/python/parse_data/comment_only.py-tests/python/parse_data/test_matches_known_good_parsing/comment_only.xml] tests/python/parse_test.py::test_cli_output[tests/python/parse_data/unicode2.py-tests/python/parse_data/test_matches_known_good_parsing/unicode2.xml] tests/python/parse_test.py::test_cli_output[tests/python/parse_data/unicode.py-tests/python/parse_data/test_matches_known_good_parsing/unicode.xml] tests/python/parse_test.py::test_cli_output[tests/python/parse_data/example2_utf8.py-tests/python/parse_data/test_matches_known_good_parsing/example2_utf8.xml] tests/python/parse_test.py::test_cli_output[tests/python/parse_data/example1.py-tests/python/parse_data/test_matches_known_good_parsing/example1.xml] tests/python/parse_test.py::test_cli_output[tests/python/parse_data/comment_only.py-tests/python/parse_data/test_matches_known_good_parsing/comment_only.xml]
tests/python/parse_test.py::test_encoding_detection[tests/python/parse_data/example2_utf8.py] tests/python/parse_test.py::test_encoding_detection[tests/python/parse_data/comment_only.py]
tests/cheetah/add_comment_test.py::test_can_add_comments tests/cheetah/directive_test.py::test_get_enclosing_blocks[tests/cheetah/directive_data/single-line.tmpl-tests/cheetah/directive_data/test_get_enclosing_blocks/single-line.txt] tests/cheetah/directive_test.py::test_get_enclosing_blocks[tests/cheetah/directive_data/simple.tmpl-tests/cheetah/directive_data/test_get_enclosing_blocks/simple.txt] tests/cheetah/directive_test.py::test_get_enclosing_blocks[tests/cheetah/directive_data/nested.tmpl-tests/cheetah/directive_data/test_get_enclosing_blocks/nested.txt] tests/cheetah/remove_call_test.py::test_can_find_calls tests/cheetah/remove_call_test.py::test_can_remove_calls[tests/cheetah/remove_call_data/nested2.tmpl-tests/cheetah/remove_call_data/test_can_remove_calls/nested2.tmpl] tests/cheetah/remove_call_test.py::test_can_remove_calls[tests/cheetah/remove_call_data/nested.tmpl-tests/cheetah/remove_call_data/test_can_remove_calls/nested.tmpl]
refactorlib/util.py refactorlib/javascript/parse.py refactorlib/parse.py setup.py refactorlib/cli/xmlstrip.py .travis.yml tox.ini refactorlib/cheetah/node.py refactorlib/cheetah/parse.py refactorlib/python/parse.py
refactorlib/util.py refactorlib/javascript/parse.py refactorlib/parse.py setup.py refactorlib/cli/xmlstrip.py .travis.yml tox.ini refactorlib/cheetah/node.py refactorlib/cheetah/parse.py refactorlib/python/parse.py