Repository: sayanarijit/expandvars

Issue:
String "\" is replaced by ""
```
>>> import expandvars
>>> expandvars.expandvars('D:\\test')
'D:test'
```

Discussion:
Toilal: I tried to improve the build, but it fails with older python 3 version on TravisCI.
codecov[bot]: # [Codecov](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=h1) Report
> Merging [#2](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=desc) into [master](https://codecov.io/gh/sayanarijit/expandvars/commit/ed58c0df5461b5b45d98ac377c3ffde0e334d468?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/sayanarijit/expandvars/pull/2/graphs/tree.svg?width=650&token=LaBTTAtjk0&height=150&src=pr)](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master     #2   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           1      1           
  Lines         158    160    +2     
=====================================
+ Hits          158    160    +2
```


| [Impacted Files](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [expandvars.py](https://codecov.io/gh/sayanarijit/expandvars/pull/2/diff?src=pr&el=tree#diff-ZXhwYW5kdmFycy5weQ==) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=footer). Last update [ed58c0d...16cd13d](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

codecov[bot]: # [Codecov](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=h1) Report
> Merging [#2](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=desc) into [master](https://codecov.io/gh/sayanarijit/expandvars/commit/ed58c0df5461b5b45d98ac377c3ffde0e334d468?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/sayanarijit/expandvars/pull/2/graphs/tree.svg?width=650&token=LaBTTAtjk0&height=150&src=pr)](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master     #2   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files           1      1           
  Lines         158    160    +2     
=====================================
+ Hits          158    160    +2
```


| [Impacted Files](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [expandvars.py](https://codecov.io/gh/sayanarijit/expandvars/pull/2/diff?src=pr&el=tree#diff-ZXhwYW5kdmFycy5weQ==) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=footer). Last update [ed58c0d...16cd13d](https://codecov.io/gh/sayanarijit/expandvars/pull/2?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

sayanarijit: It would be best if you could just fix the target issue in this PR and create a separate issue and PR with the other proposed changes. It's cleaner this way and easy to revert in case we need to.
Toilal: I've removed changes from setup.py, but it still fails on python 3.3 for some reason I'm not sure I want to investigate more :)
sayanarijit: Dropped 3.3 in https://github.com/sayanarijit/expandvars/issues/4
Do the following :
```bash
git remote add prod https://github.com/sayanarijit/expandvars
git pull --rebase prod master
git push origin Toilal:setuptools
```
I'll be able to merge it then.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 08ae868..333a0fb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,8 +9,9 @@ matrix:
       dist: xenial
       sudo: true
 install:
-  - pip install -e '.[testing]'
+  - pip install --upgrade setuptools
+  - pip uninstall -y pytest
 script:
-  - pytest --cov=expandvars
+  - python setup.py test
 after_success:
   - bash <(curl -s https://codecov.io/bash)
diff --git a/dev-requirements.txt b/dev-requirements.txt
new file mode 100644
index 0000000..678e8e5
--- /dev/null
+++ b/dev-requirements.txt
@@ -0,0 +1,1 @@
+-e .[dev,testing]
\ No newline at end of file
diff --git a/expandvars.py b/expandvars.py
index c93fb3a..d1e763a 100644
--- a/expandvars.py
+++ b/expandvars.py
@@ -11,6 +11,9 @@ __license__ = "MIT"
 __all__ = ["Expander", "expandvars"]
 
 
+ESCAPE_CHAR = "\\"
+
+
 def _valid_char(char):
     return char.isalnum() or char == "_"
 
@@ -45,7 +48,7 @@ class Expander(object):
             return
         variter = iter(vars_)
         c = next(variter)
-        if c == "\\":
+        if c == ESCAPE_CHAR:
             self.escape(variter)
             return
         if c == "$":
@@ -66,10 +69,12 @@ class Expander(object):
         try:
             c = next(variter)
         except StopIteration:
-            raise ValueError("escape chracter is not escaping anything")
+            raise ValueError("escape character is not escaping anything")
         if c == "$":
             self._result.append(c)
             c = self._next_or_done(variter)
+        else:
+            self._result.append(ESCAPE_CHAR)
         self.expand_val(variter, c)
 
     def process_buffr(self):
@@ -150,7 +155,7 @@ class Expander(object):
         if not c:
             self._result.append("$")
             return
-        if c == "\\":
+        if c == ESCAPE_CHAR:
             self.expand_val(variter, "$\\")
             return
 
@@ -161,7 +166,7 @@ class Expander(object):
         while _valid_char(c):
             self._buffr.append(c)
             c = self._next_or_done(variter)
-            if c == "\\":
+            if c == ESCAPE_CHAR:
                 self.escape(variter)
                 return
             if not c:
@@ -198,7 +203,7 @@ class Expander(object):
         while c and c != "$":
             self._result.append(c)
             c = self._next_or_done(variter)
-            if c == "\\":
+            if c == ESCAPE_CHAR:
                 self.escape(variter)
                 return
         if c:
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..ecf975e
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,1 @@
+-e .
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..a1a2277
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[aliases]
+test=pytest
+
+[tools.pytest]
+addopts=-s --ignore=setup.py --cov=expandvars
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 99b7ab9..6d5f61f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,4 @@
+import sys
 from codecs import open
 from os import path
 
@@ -12,6 +13,16 @@ here = path.abspath(path.dirname(__file__))
 with open(path.join(here, "README.md"), encoding="utf-8") as f:
     long_description = f.read()
 
+setup_requires = [
+    'pytest-runner'
+]
+
+tests_require = ['pytest', 'pytest-cov']
+
+dev_requires = ['tox']
+
+install_requires = []
+
 setup(
     name="expandvars",
     version=__version__,
@@ -49,6 +60,8 @@ setup(
     platforms=["Any"],
     keywords="expand system variables",
     packages=find_packages(exclude=["contrib", "docs", "tests", "examples"]),
-    install_requires=[],
-    extras_require={"testing": ["pytest>=4.4.1", 'pytest-cov>=2.7.1']},
+    install_requires=install_requires,
+    setup_requires=setup_requires,
+    tests_require=tests_require,
+    extras_require={"testing": tests_require, "dev": dev_requires},
 )
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..1e40975
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,6 @@
+[tox]
+envlist = py27,py33,py34,py35,py36,py37
+
+[testenv]
+commands =
+    {envpython} setup.py test
\ No newline at end of file


Test patch:
diff --git a/tests/test_expandvars.py b/tests/test_expandvars.py
index c871055..93729f4 100644
--- a/tests/test_expandvars.py
+++ b/tests/test_expandvars.py
@@ -85,12 +85,16 @@ def test_offset_length():
 
 
 def test_escape():
-    os.environ.update({"FOO": "foo"})
-    assert expandvars("$FOO\\" + "$bar") == "foo$bar"
-    assert expandvars("$FOO\\" + "\\" + "\\" + "$bar") == "foo" + "\\" + "$bar"
-    assert expandvars("$FOO\\" + "$") == "foo$"
-    assert expandvars("$\\" + "FOO") == "$\\" + "FOO"
+    os.environ.update({"FOO": "foo", "BAR": "bar"})
+    assert expandvars("$FOO" + "$BAR") == "foobar"
+    assert expandvars("\\" + "$FOO" + "\\" + "$BAR") == "$FOO$BAR"
+    assert expandvars("$FOO" + "\\" + "$BAR") == "foo$BAR"
+    assert expandvars("\\" + "$FOO" + "$BAR") == "$FOObar"
+    assert expandvars("$FOO" + "\\" + "\\" + "\\" + "$BAR") == "foo" + "\\" + "\\" + "$BAR"
+    assert expandvars("$FOO" + "\\" + "$") == "foo$"
+    assert expandvars("$" + "\\" + "FOO") == "$" + "\\" + "FOO"
     assert expandvars("\\" + "$FOO") == "$FOO"
+    assert expandvars("D:\\some\\windows\\path") == "D:\\some\\windows\\path"
 
 
 def test_corner_cases():
@@ -103,7 +107,7 @@ def test_escape_not_followed_err():
     os.environ.update({"FOO": "foo"})
     with pytest.raises(ValueError) as e:
         expandvars("$FOO\\")
-    assert str(e.value) == "escape chracter is not escaping anything"
+    assert str(e.value) == "escape character is not escaping anything"
 
 
 def test_invalid_length_err():