Skip to main content

Add a function to append value to a JavaScript array

ID
84a8f91
date
2024-08-17 07:04:46+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
815cb70
message
Add a function to append value to a JavaScript array
changed files
3 files, 69 additions, 1 deletion

Changed files

README.md (573) → README.md (664)

diff --git a/README.md b/README.md
index 530f4d8..c16bab5 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ Think of this module as the JSON module, but for JavaScript files.
 
 *   You can read a JavaScript file with `read_js(path, varname)`
 *   You can write a JavaScript file with `write_js(path, value, varname)`
+*   You can append an item to a JavaScript array with `append_js_array_value(path, value)`
 
 ## Installation
 

src/javascript/__init__.py (1442) → src/javascript/__init__.py (2393)

diff --git a/src/javascript/__init__.py b/src/javascript/__init__.py
index aa95ed5..9e41dd6 100644
--- a/src/javascript/__init__.py
+++ b/src/javascript/__init__.py
@@ -1,4 +1,5 @@
 import json
+import os
 import pathlib
 import typing
 
@@ -52,3 +53,32 @@ def write_js(p: pathlib.Path | str, *, value: typing.Any, varname: str) -> None:
 
     with open(p, "w") as out_file:
         out_file.write(js_string)
+
+
+def append_to_js_array(p: pathlib.Path | str, *, value: typing.Any) -> None:
+    """
+    Append a single value to an array in a JavaScript "data file".
+
+    Example:
+
+        >>> write_js('food.js', value=['apple', 'banana', 'coconut'], varname='fruit')
+        >>> append_to_js_array('food.js', value='damson')
+        >>> read_js('food.js', varname='fruit')
+        ['apple', 'banana', 'coconut', 'damson']
+
+    If you have a large file, this is usually faster than reading,
+    appending, and re-writing the entire file.
+
+    """
+    file_size = os.stat(p).st_size
+
+    json_to_append = b",\n" + json.dumps(value).encode("utf8") + b"\n];\n"
+
+    with open(p, "rb+") as out_file:
+        out_file.seek(file_size - 4)
+
+        if out_file.read(4) == b"\n];\n":
+            out_file.seek(file_size - 4)
+            out_file.write(json_to_append)
+        else:
+            raise ValueError(f"End of file {p!r} does not look like an array")

tests/test_javascript.py (2546) → tests/test_javascript.py (3796)

diff --git a/tests/test_javascript.py b/tests/test_javascript.py
index 97a6d93..d9b35cf 100644
--- a/tests/test_javascript.py
+++ b/tests/test_javascript.py
@@ -2,7 +2,7 @@ import pathlib
 
 import pytest
 
-from javascript import read_js, write_js
+from javascript import append_to_js_array, read_js, write_js
 
 
 class TestReadJs:
@@ -72,3 +72,40 @@ class TestWriteJs:
             js_path.read_text()
             == 'const redPentagon = {\n  "sides": 5,\n  "colour": "red"\n};\n'
         )
+
+
+class TestAppendToArray:
+    def test_can_mix_types(self, tmp_path: pathlib.Path) -> None:
+        js_path = tmp_path / "food.js"
+
+        write_js(js_path, value=["apple", "banana", "coconut"], varname="fruit")
+        append_to_js_array(js_path, value=["damson"])
+        assert read_js(js_path, varname="fruit") == [
+            "apple",
+            "banana",
+            "coconut",
+            ["damson"],
+        ]
+
+    def test_error_if_file_doesnt_look_like_array(self, tmp_path: pathlib.Path) -> None:
+        js_path = tmp_path / "shape.js"
+        red_pentagon = {"sides": 5, "colour": "red"}
+
+        write_js(js_path, value=red_pentagon, varname="redPentagon")
+
+        with pytest.raises(ValueError, match="does not look like an array"):
+            append_to_js_array(js_path, value=["yellow"])
+
+
+class TestRoundTrip:
+    def test_can_append_to_file(self, tmp_path: pathlib.Path) -> None:
+        js_path = tmp_path / "food.js"
+
+        write_js(js_path, value=["apple", "banana", "coconut"], varname="fruit")
+        append_to_js_array(js_path, value="damson")
+        assert read_js(js_path, varname="fruit") == [
+            "apple",
+            "banana",
+            "coconut",
+            "damson",
+        ]