Repository: sdispater/tomlkit

Issue:
Escape characters lost after concatenating string values
When working with AWS samconfig.toml, you have to specify parameter overrides as literal strings:
```
[default.deploy.parameters]
parameter_overrides = "<parameter_name>=\"<parameter_value>\""
```
When loading this `.toml` with `tomlkit`, the backslashes are lost in translation. So a roundtrip: `tomlkit.dump(tomlkit.load(config))` results in:
```
[default.deploy.parameters]
parameter_overrides = "<parameter_name>="<parameter_value>""
```
And this breaks the `parameter_overrides` string, since the intermediate `"` now break apart the string.

How is it possible to load strings as literal strings using `tomlkit`?

Discussion:

Patch:
diff --git a/tomlkit/items.py b/tomlkit/items.py
index 1d6fca0..48bdfc6 100644
--- a/tomlkit/items.py
+++ b/tomlkit/items.py
@@ -1779,18 +1779,16 @@ class String(str, Item):
     def as_string(self) -> str:
         return f"{self._t.value}{decode(self._original)}{self._t.value}"
 
-    def __add__(self, other):
+    def __add__(self: ItemT, other: str) -> ItemT:
+        if not isinstance(other, str):
+            return NotImplemented
         result = super().__add__(other)
+        original = self._original + getattr(other, "_original", other)
 
-        return self._new(result)
+        return self._new(result, original)
 
-    def __sub__(self, other):
-        result = super().__sub__(other)
-
-        return self._new(result)
-
-    def _new(self, result):
-        return String(self._t, result, result, self._trivia)
+    def _new(self, result: str, original: str) -> "String":
+        return String(self._t, result, original, self._trivia)
 
     def _getstate(self, protocol=3):
         return self._t, str(self), self._original, self._trivia


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ae62eb5..cb8c77c 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -27,12 +27,12 @@ jobs:
         python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           submodules: "recursive"
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -45,7 +45,7 @@ jobs:
       - name: Install Poetry
         shell: bash
         run: |
-          curl -fsSL https://install.python-poetry.org | python - -y
+          curl -fsSL https://install.python-poetry.org | python - -y --version 1.1.15
       - name: Update PATH
         if: ${{ matrix.os != 'Windows' }}
         run: echo "$HOME/.local/bin" >> $GITHUB_PATH
@@ -59,7 +59,7 @@ jobs:
         run: |
           poetry config virtualenvs.in-project true
       - name: Set up cache
-        uses: actions/cache@v2
+        uses: actions/cache@v3
         id: cache
         with:
           path: .venv
diff --git a/tests/test_items.py b/tests/test_items.py
index e6d7df7..e3f93c7 100644
--- a/tests/test_items.py
+++ b/tests/test_items.py
@@ -708,6 +708,14 @@ def test_strings_behave_like_strs():
     assert doc.as_string() == 'str = "foo bar" # Comment'
 
 
+def test_string_add_preserve_escapes():
+    i = api.value('"foo\\"bar"')
+    i += " baz"
+
+    assert i == 'foo"bar baz'
+    assert i.as_string() == '"foo\\"bar baz"'
+
+
 def test_tables_behave_like_dicts():
     t = item({"foo": "bar"})