We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Vocabulary functions do not strip whitespace from CSV
I had the misfortune the last few days to believe that the vocabulary csv files were not operating correctly.

Apparently whitespace is not stripped around fields. So "test, test2" leads a row which is parsed, but not turned into a functioning command.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `code/user_settings.py`
Content:
```
1 from talon import Module, fs, Context
2 import os
3 import csv
4 from pathlib import Path
5 from typing import Dict, List, Tuple
6 import threading
7 
8 
9 # NOTE: This method requires this module to be one folder below the top-level
10 #   knausj folder.
11 SETTINGS_DIR = Path(__file__).parents[1] / "settings"
12 
13 if not SETTINGS_DIR.is_dir():
14     os.mkdir(SETTINGS_DIR)
15 
16 mod = Module()
17 ctx = Context()
18 
19 
20 def _load_csv_dict(
21     file_name: str, headers=Tuple[str, str], default: Dict[str, str] = {}
22 ) -> Dict[str, str]:
23     """Load a word mapping from a CSV file. If it doesn't exist, create it."""
24     assert file_name.endswith(".csv")
25     path = SETTINGS_DIR / file_name
26 
27     # Create the file if it doesn't exist
28     if not SETTINGS_DIR.is_dir():
29         os.mkdir(SETTINGS_DIR)
30     if not path.is_file():
31         with open(path, "w", encoding="utf-8") as file:
32             writer = csv.writer(file)
33             writer.writerow(headers)
34             for key, value in default.items():
35                 writer.writerow([key] if key == value else [value, key])
36 
37     # Now read from disk
38     with open(path, "r", encoding="utf-8") as file:
39         rows = list(csv.reader(file))
40 
41     mapping = {}
42     if len(rows) >= 2:
43         actual_headers = rows[0]
44         if not actual_headers == list(headers):
45             print(
46                 f'"{file_name}": Malformed headers - {actual_headers}.'
47                 + f" Should be {list(headers)}. Ignoring row."
48             )
49         for row in rows[1:]:
50             if len(row) == 0:
51                 # Windows newlines are sometimes read as empty rows. :champagne:
52                 continue
53             if len(row) == 1:
54                 mapping[row[0]] = row[0]
55             else:
56                 mapping[row[1]] = row[0]
57                 if len(row) > 2:
58                     print(
59                         f'"{file_name}": More than two values in row: {row}.'
60                         + " Ignoring the extras."
61                     )
62     return mapping
63 
64 
65 _mapped_lists = {}
66 _settings_lock = threading.Lock()
67 _word_map_params = None
68 
69 
70 def _update_list(list_name: str, *csv_params):
71     """Update list with `list_name` from a csv on disk.
72 
73     `csv_params` will be passed to `_load_csv_dict`.
74 
75     """
76     global ctx
77     ctx.lists[list_name] = _load_csv_dict(*csv_params)
78 
79 
80 def _update_word_map(*csv_params):
81     """Update `dictate.word_map` from disk.
82 
83     `csv_params` will be passed to `_load_csv_dict`.
84 
85     """
86     global ctx
87     ctx.settings["dictate.word_map"] = _load_csv_dict(*csv_params)
88 
89 
90 def _update_lists(*_):
91     """Update all CSV lists from disk."""
92     print("Updating CSV lists...")
93     with _settings_lock:
94         for list_name, csv_params in _mapped_lists.items():
95             try:
96                 _update_list(list_name, *csv_params)
97             except Exception as e:
98                 print(f'Error loading list "{list_name}": {e}')
99         # Special case - `dictate.word_map` isn't a list.
100         if _word_map_params:
101             try:
102                 _update_word_map(*_word_map_params)
103             except Exception as e:
104                 print(f'Error updating "dictate.word_map": {e}')
105 
106 
107 def bind_list_to_csv(
108     list_name: str,
109     csv_name: str,
110     csv_headers: Tuple[str, str],
111     default_values: Dict[str, str] = {},
112 ) -> None:
113     """Register a Talon list that should be updated from a CSV on disk.
114 
115     The CSV file will be created automatically in the "settings" dir if it
116     doesn't exist. This directory can be tracked independently to
117     `knausj_talon`, allowing the user to specify things like private vocab
118     separately.
119 
120     Note the list must be declared separately.
121 
122     """
123     global _mapped_lists
124     with _settings_lock:
125         _update_list(list_name, csv_name, csv_headers, default_values)
126         # If there were no errors, we can register it permanently.
127         _mapped_lists[list_name] = (csv_name, csv_headers, default_values)
128 
129 
130 def bind_word_map_to_csv(
131     csv_name: str, csv_headers: Tuple[str, str], default_values: Dict[str, str] = {}
132 ) -> None:
133     """Like `bind_list_to_csv`, but for the `dictate.word_map` setting.
134 
135     Since it is a setting, not a list, it has to be handled separately.
136 
137     """
138     global _word_map_params
139     # TODO: Maybe a generic system for binding the dicts to settings? Only
140     #   implement if it's needed.
141     with _settings_lock:
142         _update_word_map(csv_name, csv_headers, default_values)
143         # If there were no errors, we can register it permanently.
144         _word_map_params = (csv_name, csv_headers, default_values)
145 
146 
147 fs.watch(str(SETTINGS_DIR), _update_lists)
148
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/code/user_settings.py b/code/user_settings.py
--- a/code/user_settings.py
+++ b/code/user_settings.py
@@ -51,14 +51,17 @@
                 # Windows newlines are sometimes read as empty rows. :champagne:
                 continue
             if len(row) == 1:
-                mapping[row[0]] = row[0]
+                output = spoken_form = row[0]
             else:
-                mapping[row[1]] = row[0]
+                output, spoken_form = row[:2]
                 if len(row) > 2:
                     print(
                         f'"{file_name}": More than two values in row: {row}.'
                         + " Ignoring the extras."
                     )
+            # Leading/trailing whitespace in spoken form can prevent recognition.
+            spoken_form = spoken_form.strip()
+            mapping[spoken_form] = output
     return mapping

{"golden_diff": "diff --git a/code/user_settings.py b/code/user_settings.py\n--- a/code/user_settings.py\n+++ b/code/user_settings.py\n@@ -51,14 +51,17 @@\n                 # Windows newlines are sometimes read as empty rows. :champagne:\n                 continue\n             if len(row) == 1:\n-                mapping[row[0]] = row[0]\n+                output = spoken_form = row[0]\n             else:\n-                mapping[row[1]] = row[0]\n+                output, spoken_form = row[:2]\n                 if len(row) > 2:\n                     print(\n                         f'\"{file_name}\": More than two values in row: {row}.'\n                         + \" Ignoring the extras.\"\n                     )\n+            # Leading/trailing whitespace in spoken form can prevent recognition.\n+            spoken_form = spoken_form.strip()\n+            mapping[spoken_form] = output\n     return mapping\n", "issue": "Vocabulary functions do not strip whitespace from CSV\nI had the misfortune the last few days to believe that the vocabulary csv files were not operating correctly.\r\n\r\nApparently whitespace is not stripped around fields. So \"test, test2\" leads a row which is parsed, but not turned into a functioning command.\n", "before_files": [{"content": "from talon import Module, fs, Context\nimport os\nimport csv\nfrom pathlib import Path\nfrom typing import Dict, List, Tuple\nimport threading\n\n\n# NOTE: This method requires this module to be one folder below the top-level\n#   knausj folder.\nSETTINGS_DIR = Path(__file__).parents[1] / \"settings\"\n\nif not SETTINGS_DIR.is_dir():\n    os.mkdir(SETTINGS_DIR)\n\nmod = Module()\nctx = Context()\n\n\ndef _load_csv_dict(\n    file_name: str, headers=Tuple[str, str], default: Dict[str, str] = {}\n) -> Dict[str, str]:\n    \"\"\"Load a word mapping from a CSV file. If it doesn't exist, create it.\"\"\"\n    assert file_name.endswith(\".csv\")\n    path = SETTINGS_DIR / file_name\n\n    # Create the file if it doesn't exist\n    if not SETTINGS_DIR.is_dir():\n        os.mkdir(SETTINGS_DIR)\n    if not path.is_file():\n        with open(path, \"w\", encoding=\"utf-8\") as file:\n            writer = csv.writer(file)\n            writer.writerow(headers)\n            for key, value in default.items():\n                writer.writerow([key] if key == value else [value, key])\n\n    # Now read from disk\n    with open(path, \"r\", encoding=\"utf-8\") as file:\n        rows = list(csv.reader(file))\n\n    mapping = {}\n    if len(rows) >= 2:\n        actual_headers = rows[0]\n        if not actual_headers == list(headers):\n            print(\n                f'\"{file_name}\": Malformed headers - {actual_headers}.'\n                + f\" Should be {list(headers)}. Ignoring row.\"\n            )\n        for row in rows[1:]:\n            if len(row) == 0:\n                # Windows newlines are sometimes read as empty rows. :champagne:\n                continue\n            if len(row) == 1:\n                mapping[row[0]] = row[0]\n            else:\n                mapping[row[1]] = row[0]\n                if len(row) > 2:\n                    print(\n                        f'\"{file_name}\": More than two values in row: {row}.'\n                        + \" Ignoring the extras.\"\n                    )\n    return mapping\n\n\n_mapped_lists = {}\n_settings_lock = threading.Lock()\n_word_map_params = None\n\n\ndef _update_list(list_name: str, *csv_params):\n    \"\"\"Update list with `list_name` from a csv on disk.\n\n    `csv_params` will be passed to `_load_csv_dict`.\n\n    \"\"\"\n    global ctx\n    ctx.lists[list_name] = _load_csv_dict(*csv_params)\n\n\ndef _update_word_map(*csv_params):\n    \"\"\"Update `dictate.word_map` from disk.\n\n    `csv_params` will be passed to `_load_csv_dict`.\n\n    \"\"\"\n    global ctx\n    ctx.settings[\"dictate.word_map\"] = _load_csv_dict(*csv_params)\n\n\ndef _update_lists(*_):\n    \"\"\"Update all CSV lists from disk.\"\"\"\n    print(\"Updating CSV lists...\")\n    with _settings_lock:\n        for list_name, csv_params in _mapped_lists.items():\n            try:\n                _update_list(list_name, *csv_params)\n            except Exception as e:\n                print(f'Error loading list \"{list_name}\": {e}')\n        # Special case - `dictate.word_map` isn't a list.\n        if _word_map_params:\n            try:\n                _update_word_map(*_word_map_params)\n            except Exception as e:\n                print(f'Error updating \"dictate.word_map\": {e}')\n\n\ndef bind_list_to_csv(\n    list_name: str,\n    csv_name: str,\n    csv_headers: Tuple[str, str],\n    default_values: Dict[str, str] = {},\n) -> None:\n    \"\"\"Register a Talon list that should be updated from a CSV on disk.\n\n    The CSV file will be created automatically in the \"settings\" dir if it\n    doesn't exist. This directory can be tracked independently to\n    `knausj_talon`, allowing the user to specify things like private vocab\n    separately.\n\n    Note the list must be declared separately.\n\n    \"\"\"\n    global _mapped_lists\n    with _settings_lock:\n        _update_list(list_name, csv_name, csv_headers, default_values)\n        # If there were no errors, we can register it permanently.\n        _mapped_lists[list_name] = (csv_name, csv_headers, default_values)\n\n\ndef bind_word_map_to_csv(\n    csv_name: str, csv_headers: Tuple[str, str], default_values: Dict[str, str] = {}\n) -> None:\n    \"\"\"Like `bind_list_to_csv`, but for the `dictate.word_map` setting.\n\n    Since it is a setting, not a list, it has to be handled separately.\n\n    \"\"\"\n    global _word_map_params\n    # TODO: Maybe a generic system for binding the dicts to settings? Only\n    #   implement if it's needed.\n    with _settings_lock:\n        _update_word_map(csv_name, csv_headers, default_values)\n        # If there were no errors, we can register it permanently.\n        _word_map_params = (csv_name, csv_headers, default_values)\n\n\nfs.watch(str(SETTINGS_DIR), _update_lists)\n", "path": "code/user_settings.py"}], "after_files": [{"content": "from talon import Module, fs, Context\nimport os\nimport csv\nfrom pathlib import Path\nfrom typing import Dict, List, Tuple\nimport threading\n\n\n# NOTE: This method requires this module to be one folder below the top-level\n#   knausj folder.\nSETTINGS_DIR = Path(__file__).parents[1] / \"settings\"\n\nif not SETTINGS_DIR.is_dir():\n    os.mkdir(SETTINGS_DIR)\n\nmod = Module()\nctx = Context()\n\n\ndef _load_csv_dict(\n    file_name: str, headers=Tuple[str, str], default: Dict[str, str] = {}\n) -> Dict[str, str]:\n    \"\"\"Load a word mapping from a CSV file. If it doesn't exist, create it.\"\"\"\n    assert file_name.endswith(\".csv\")\n    path = SETTINGS_DIR / file_name\n\n    # Create the file if it doesn't exist\n    if not SETTINGS_DIR.is_dir():\n        os.mkdir(SETTINGS_DIR)\n    if not path.is_file():\n        with open(path, \"w\", encoding=\"utf-8\") as file:\n            writer = csv.writer(file)\n            writer.writerow(headers)\n            for key, value in default.items():\n                writer.writerow([key] if key == value else [value, key])\n\n    # Now read from disk\n    with open(path, \"r\", encoding=\"utf-8\") as file:\n        rows = list(csv.reader(file))\n\n    mapping = {}\n    if len(rows) >= 2:\n        actual_headers = rows[0]\n        if not actual_headers == list(headers):\n            print(\n                f'\"{file_name}\": Malformed headers - {actual_headers}.'\n                + f\" Should be {list(headers)}. Ignoring row.\"\n            )\n        for row in rows[1:]:\n            if len(row) == 0:\n                # Windows newlines are sometimes read as empty rows. :champagne:\n                continue\n            if len(row) == 1:\n                output = spoken_form = row[0]\n            else:\n                output, spoken_form = row[:2]\n                if len(row) > 2:\n                    print(\n                        f'\"{file_name}\": More than two values in row: {row}.'\n                        + \" Ignoring the extras.\"\n                    )\n            # Leading/trailing whitespace in spoken form can prevent recognition.\n            spoken_form = spoken_form.strip()\n            mapping[spoken_form] = output\n    return mapping\n\n\n_mapped_lists = {}\n_settings_lock = threading.Lock()\n_word_map_params = None\n\n\ndef _update_list(list_name: str, *csv_params):\n    \"\"\"Update list with `list_name` from a csv on disk.\n\n    `csv_params` will be passed to `_load_csv_dict`.\n\n    \"\"\"\n    global ctx\n    ctx.lists[list_name] = _load_csv_dict(*csv_params)\n\n\ndef _update_word_map(*csv_params):\n    \"\"\"Update `dictate.word_map` from disk.\n\n    `csv_params` will be passed to `_load_csv_dict`.\n\n    \"\"\"\n    global ctx\n    ctx.settings[\"dictate.word_map\"] = _load_csv_dict(*csv_params)\n\n\ndef _update_lists(*_):\n    \"\"\"Update all CSV lists from disk.\"\"\"\n    print(\"Updating CSV lists...\")\n    with _settings_lock:\n        for list_name, csv_params in _mapped_lists.items():\n            try:\n                _update_list(list_name, *csv_params)\n            except Exception as e:\n                print(f'Error loading list \"{list_name}\": {e}')\n        # Special case - `dictate.word_map` isn't a list.\n        if _word_map_params:\n            try:\n                _update_word_map(*_word_map_params)\n            except Exception as e:\n                print(f'Error updating \"dictate.word_map\": {e}')\n\n\ndef bind_list_to_csv(\n    list_name: str,\n    csv_name: str,\n    csv_headers: Tuple[str, str],\n    default_values: Dict[str, str] = {},\n) -> None:\n    \"\"\"Register a Talon list that should be updated from a CSV on disk.\n\n    The CSV file will be created automatically in the \"settings\" dir if it\n    doesn't exist. This directory can be tracked independently to\n    `knausj_talon`, allowing the user to specify things like private vocab\n    separately.\n\n    Note the list must be declared separately.\n\n    \"\"\"\n    global _mapped_lists\n    with _settings_lock:\n        _update_list(list_name, csv_name, csv_headers, default_values)\n        # If there were no errors, we can register it permanently.\n        _mapped_lists[list_name] = (csv_name, csv_headers, default_values)\n\n\ndef bind_word_map_to_csv(\n    csv_name: str, csv_headers: Tuple[str, str], default_values: Dict[str, str] = {}\n) -> None:\n    \"\"\"Like `bind_list_to_csv`, but for the `dictate.word_map` setting.\n\n    Since it is a setting, not a list, it has to be handled separately.\n\n    \"\"\"\n    global _word_map_params\n    # TODO: Maybe a generic system for binding the dicts to settings? Only\n    #   implement if it's needed.\n    with _settings_lock:\n        _update_word_map(csv_name, csv_headers, default_values)\n        # If there were no errors, we can register it permanently.\n        _word_map_params = (csv_name, csv_headers, default_values)\n\n\nfs.watch(str(SETTINGS_DIR), _update_lists)\n", "path": "code/user_settings.py"}]}