Skip to main content

Break out the decoder logic in a separate file

ID
283ea11
date
2025-05-03 07:45:47+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
0634780
message
Break out the decoder logic in a separate file
changed files
3 files, 31 additions, 14 deletions

Changed files

src/javascript_data_files/__init__.py (6907) → src/javascript_data_files/__init__.py (6664)

diff --git a/src/javascript_data_files/__init__.py b/src/javascript_data_files/__init__.py
index f4079f1..96467dc 100644
--- a/src/javascript_data_files/__init__.py
+++ b/src/javascript_data_files/__init__.py
@@ -14,11 +14,11 @@ Think of this like the JSON module, but for JavaScript files.
 import io
 import json
 import pathlib
-import re
 import textwrap
 import typing
 import uuid
 
+from .decoder import decode_from_js
 from .encoder import encode_as_js, encode_as_json
 
 
@@ -44,18 +44,7 @@ def read_js(p: pathlib.Path | str, *, varname: str) -> typing.Any:
     """
     p = pathlib.Path(p)
 
-    contents = p.read_text()
-
-    m = re.compile(r"^(?:const |var )?%s = " % varname)
-
-    if not m.match(contents):
-        raise ValueError(
-            f"File {p} does not start with JavaScript `const` declaration!"
-        )
-
-    json_string = m.sub(repl="", string=contents).rstrip().rstrip(";")
-
-    return json.loads(json_string)
+    return decode_from_js(js_string=p.read_text(), varname=varname)
 
 
 def read_typed_js[T](p: pathlib.Path | str, *, varname: str, model: type[T]) -> T:

src/javascript_data_files/decoder.py (0) → src/javascript_data_files/decoder.py (760)

diff --git a/src/javascript_data_files/decoder.py b/src/javascript_data_files/decoder.py
new file mode 100644
index 0000000..5c9eb70
--- /dev/null
+++ b/src/javascript_data_files/decoder.py
@@ -0,0 +1,28 @@
+"""
+This file contains pure functions for converting JSON strings
+to Python values.
+
+Because I expect some of this JSON to be written by me, and I can
+make copy-paste mistakes, there are a couple of ways it tries
+to catch errors.
+"""
+
+import json
+import re
+import typing
+
+
+def decode_from_js(js_string: str, *, varname: str) -> typing.Any:
+    """
+    Parse a string as a JavaScript value.
+    """
+    # Matches 'const varname = ' or 'var varname = ' at the start
+    # of a string.
+    m = re.compile(r"^(?:const |var )?%s = " % varname)
+
+    if not m.match(js_string):
+        raise ValueError("Does not start with JavaScript `const` declaration!")
+
+    json_string = m.sub(repl="", string=js_string).rstrip().rstrip(";")
+
+    return json.loads(json_string)

tests/test_javascript_data_files.py (17712) → tests/test_javascript_data_files.py (17712)

diff --git a/tests/test_javascript_data_files.py b/tests/test_javascript_data_files.py
index 34eecd3..3446a02 100644
--- a/tests/test_javascript_data_files.py
+++ b/tests/test_javascript_data_files.py
@@ -92,7 +92,7 @@ class TestReadJs:
         )
 
         with pytest.raises(
-            ValueError, match="does not start with JavaScript `const` declaration"
+            ValueError, match="Does not start with JavaScript `const` declaration"
         ):
             read_js(js_path, varname="blueTriangle")