Skip to main content

Add a function for reading JavaScript files

ID
8be8cda
date
2024-08-16 21:16:16+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
95da871
message
Add a function for reading JavaScript files
changed files
3 files, 87 additions, 3 deletions

Changed files

README.md (324) → README.md (486)

diff --git a/README.md b/README.md
index c51d079..1b3e83b 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,19 @@
 # python-js-files
 
-This is a small collection of Python functions for manipulating JavaScript "data files" -- that is, JavaScript files that have a single variable that defines a JSON value.
-For example:
+This is a collection of Python functions for manipulating JavaScript "data files" -- that is, JavaScript files that have a single variable that defines a JSON value.
+
+This is an example of a JavaScript data file:
 
 ```javascript
 const shape = { "sides": 5, "colour": "red" };
 ```
 
+Think of this module as the JSON module, but for JavaScript files.
+
 ## API
 
+*   You can read a JavaScript file with `read_js(path, varname)`
+
 ## Why not use JSON files?
 
 ## License

src/javascript/__init__.py (22) → src/javascript/__init__.py (940)

diff --git a/src/javascript/__init__.py b/src/javascript/__init__.py
index 0b2f79d..ad71cd6 100644
--- a/src/javascript/__init__.py
+++ b/src/javascript/__init__.py
@@ -1 +1,36 @@
-__version__ = "1.1.3"
+import json
+import pathlib
+import typing
+
+
+__version__ = "1.0.0"
+
+
+def read_js(p: pathlib.Path | str, *, varname: str) -> typing.Any:
+    """
+    Read a JavaScript "data file".
+
+    For example, if you have a file `shape.js` with the following contents:
+
+        const redPentagon = { "sides": 5, "colour": "red" };
+
+    Then you can read it using this function:
+
+        >>> read_js('shape.js', varname='redPentagon')
+        {'sides': 5, 'colour': 'red'}
+
+    """
+    with open(p) as in_file:
+        contents = in_file.read()
+
+    if not contents.startswith(f"const {varname} = "):
+        raise ValueError(
+            f"File {p} does not start with JavaScript `const` declaration!"
+        )
+
+    if not contents.rstrip().endswith(";"):
+        raise ValueError(f"File {p} does not end with a trailing semicolon!")
+
+    json_string = contents.replace(f"const {varname} = ", "").rstrip().rstrip(";")
+
+    return json.loads(json_string)

tests/test_javascript.py (0) → tests/test_javascript.py (1569)

diff --git a/tests/test_javascript.py b/tests/test_javascript.py
new file mode 100644
index 0000000..afb03a8
--- /dev/null
+++ b/tests/test_javascript.py
@@ -0,0 +1,44 @@
+import pathlib
+
+import pytest
+
+from javascript import read_js
+
+
+class TestReadJs:
+    def test_can_read_file(self, tmp_path: pathlib.Path) -> None:
+        js_path = tmp_path / "shape.js"
+        js_path.write_text(
+            'const redPentagon = {\n  "sides": 5,\n  "colour": "red"\n};\n'
+        )
+
+        assert read_js(js_path, varname="redPentagon") == {"sides": 5, "colour": "red"}
+
+    def test_non_existent_file_is_error(self) -> None:
+        with pytest.raises(FileNotFoundError):
+            read_js("doesnotexist.js", varname="shape")
+
+    def test_non_json_value_is_error(self, tmp_path: pathlib.Path) -> None:
+        js_path = tmp_path / "total.js"
+        js_path.write_text("const sum = 1 + 1 + 1;")
+
+        with pytest.raises(ValueError):
+            read_js(js_path, varname="sum")
+
+    def test_incorrect_varname_is_error(self, tmp_path: pathlib.Path) -> None:
+        js_path = tmp_path / "shape.js"
+        js_path.write_text(
+            'const redPentagon = {\n  "sides": 5,\n  "colour": "red"\n};\n'
+        )
+
+        with pytest.raises(
+            ValueError, match="does not start with JavaScript `const` declaration"
+        ):
+            read_js(js_path, varname="blueTriangle")
+
+    def test_no_trailing_semicolon_is_error(self, tmp_path: pathlib.Path) -> None:
+        js_path = tmp_path / "shape.js"
+        js_path.write_text('const redPentagon = {\n  "sides": 5,\n  "colour": "red"\n}')
+
+        with pytest.raises(ValueError, match="does not end with a trailing semicolon"):
+            read_js(js_path, varname="redPentagon")