diff --git a/README.md b/README.md index 4794ff6f..4cb191fc 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,15 @@ docker run -i -t ghcr.io/tekknolagi/scrapscript:trunk apply "1 + 2" docker run -i -t ghcr.io/tekknolagi/scrapscript:trunk repl ``` +## Advanced usage + +```bash +# Create a scrapyard +python3 scrapscript.py yard init /desired/path/to/your/scrapyard +# Evaluates the file and stores the serialized scrap object to the yard +python3 scrapscript.py yard commit /desired/path/to/your/scrapyard SCRAP_NAME SCRAPSCRIPT_FILE +``` + ## Running Tests ```bash diff --git a/poetry.lock b/poetry.lock index d6b29c90..5a348640 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,6 +14,70 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "colorama" version = "0.4.6" @@ -191,6 +255,56 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pygit2" +version = "1.13.3" +description = "Python bindings for libgit2." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygit2-1.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a3053850e29c1e102b1ab759d90b0dcc6402d7a434cbe810cfd2792294cf0ba6"}, + {file = "pygit2-1.13.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d2461db082c27231e2565e24e7ec3d6a60c7ceea5cda7364cb6eb81a6aedebd"}, + {file = "pygit2-1.13.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2216dc34edbe44e37c5cabc5f1530266445b87c66038fc8b3e0e7be64b3d4edb"}, + {file = "pygit2-1.13.3-cp310-cp310-win32.whl", hash = "sha256:5bc8c173ead087a4200e8763fad92105b4c9d40d03e007b9d9bbe47793716d31"}, + {file = "pygit2-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:c305adf3a86e02db8bcd89bb92e33e896a2ff36f58a5ad7ff15675491ab6a751"}, + {file = "pygit2-1.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8de720ca137624d8f98c8b8d57cdb1e461034adf3e158762ee5c3085620c8075"}, + {file = "pygit2-1.13.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a85521b8e218afd4111d5bd4e106d77ffaac7ecd9a1ed8c1eea9f8d9568d287"}, + {file = "pygit2-1.13.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c5cb78cc0a88f5cb2910e85996f33fcac99994251117f00f2e344f84d2616a"}, + {file = "pygit2-1.13.3-cp311-cp311-win32.whl", hash = "sha256:81686e3e06132f23eab32c6718c21930e8adda795c2ca76617f7562bff7b6b66"}, + {file = "pygit2-1.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:93cc7ffb403688c2ec3e169096f34b2b99bc709adc54487e9d11165cfd070948"}, + {file = "pygit2-1.13.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:07b17f766c88ce1d05d264b5819e75ad261f3b60e33e4105a71f02467d0f6d39"}, + {file = "pygit2-1.13.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81bfe9ca394cdc896b632f18cd5f9c656a5f6c03c61deb1570b9081f2406776b"}, + {file = "pygit2-1.13.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c83e6e5ac357a9e87698c1eb25f430846f208bce12362d2209e7c9ac214e00c"}, + {file = "pygit2-1.13.3-cp312-cp312-win32.whl", hash = "sha256:de481a2cee7ef98143109bd9d2b30690022aeb8ba849feeba082a3b48a53c214"}, + {file = "pygit2-1.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:0353b55f8bed1742dab15083ee9ee508ed91feb5c2563e2a612af277317030c6"}, + {file = "pygit2-1.13.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:60152bb30bc2ab880d3c82f113be33aac7ced571d1148c51720ccefff9dfc9ce"}, + {file = "pygit2-1.13.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcf827ebe392e2181a50ebaf724947e30a1da076a74d8a6f9cec784158faced1"}, + {file = "pygit2-1.13.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73cb821acc5cc8ad62a96154d030ff47127073b56f71157e7c65b2e7ebb4d52f"}, + {file = "pygit2-1.13.3-cp38-cp38-win32.whl", hash = "sha256:112c4efd421c3c8b4bb4406d3cd4a3a6a18a925c1f8b08d8a6dd0b591c6c6049"}, + {file = "pygit2-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:21f73fd2863b6b21b4fbfed11a42af0ac1036472bb3716944b42a9719beaf07e"}, + {file = "pygit2-1.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e94f2598bdf68340609bb21fd4d21213812913b40b73e5fcba67f4fb01f4fba4"}, + {file = "pygit2-1.13.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5846aadb7b72802b3e4cb981f956965e92bc1692e7514ff4491bd7e24b20b358"}, + {file = "pygit2-1.13.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2823d097b103740d52e600ef2079e23966583edbde08ac122279f1ab3b2c3979"}, + {file = "pygit2-1.13.3-cp39-cp39-win32.whl", hash = "sha256:72fda35f88a3f5549eb9683c47ac73a6b674df943fc2490d93d539b46f518cbd"}, + {file = "pygit2-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:2087fd130181e4ba6b8599fcd406920781555a52a3f142bd1dec4de21b9c5792"}, + {file = "pygit2-1.13.3.tar.gz", hash = "sha256:0257c626011e4afb99bdb20875443f706f84201d4c92637f02215b98eac13ded"}, +] + +[package.dependencies] +cffi = ">=1.16.0" +setuptools = {version = "*", markers = "python_version >= \"3.12\""} + [[package]] name = "pylint" version = "3.0.2" @@ -269,6 +383,22 @@ files = [ {file = "ruff-0.1.7.tar.gz", hash = "sha256:dffd699d07abf54833e5f6cc50b85a6ff043715da8788c4a79bcd4ab4734d306"}, ] +[[package]] +name = "setuptools" +version = "69.0.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "tomli" version = "2.0.1" @@ -305,4 +435,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "3bbc8f248c88f7352ac9fe82b3b7385aa7e309e719a26c80bc72db7dac7d1166" +content-hash = "c717003701674fc908621a7ceb7d070ed96309ce34c788898d29c1d60479eac8" diff --git a/pyproject.toml b/pyproject.toml index 41c96742..08425655 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ version = "0.1.0" [tool.poetry.dependencies] python = ">=3.8" +pygit2 = "^1.13.3" [tool.poetry.group.dev.dependencies] mypy = "^1.7.0" diff --git a/scrapscript.py b/scrapscript.py index cb54adc0..f0a56c04 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -9,13 +9,14 @@ import os import re import sys +import tempfile import typing import unittest import urllib.request from dataclasses import dataclass from enum import auto from types import ModuleType, FunctionType -from typing import Any, Callable, Dict, Mapping, Optional, Union +from typing import Any, Callable, Dict, Mapping, Optional, Tuple, Union readline: Optional[ModuleType] try: @@ -338,8 +339,13 @@ def parse(tokens: typing.List[Token], p: float = 0) -> "Object": if isinstance(token, NumLit): l = Int(token.value) elif isinstance(token, Name): - # TODO: Handle kebab case vars - l = Var(token.value) + sha_prefix = "$sha1'" + if token.value.startswith(sha_prefix): + hash = token.value[len(sha_prefix) :] + l = HashVar(hash) + else: + # TODO: Handle kebab case vars + l = Var(token.value) elif isinstance(token, BytesLit): base = token.base if base == 85: @@ -562,6 +568,20 @@ def deserialize(msg: Dict[str, object]) -> "Var": return Var(msg["name"]) +@dataclass(eq=True, frozen=True, unsafe_hash=True) +class HashVar(Object): + name: str + + def serialize(self) -> Dict[bytes, object]: + return {b"type": b"HashVar", b"name": self.name.encode("utf-8")} + + @staticmethod + def deserialize(msg: Dict[str, object]) -> "HashVar": + assert msg["type"] == "HashVar" + assert isinstance(msg["name"], str) + return HashVar(msg["name"]) + + @dataclass(eq=True, frozen=True, unsafe_hash=True) class Bool(Object): value: bool @@ -1013,6 +1033,20 @@ def eval_exp(env: Env, exp: Object) -> Object: clo_inner = eval_exp(env, exp.inner) clo_outer = eval_exp(env, exp.outer) return Closure({}, Function(Var("x"), Apply(clo_outer, Apply(clo_inner, Var("x"))))) + if isinstance(exp, HashVar): + yard_location = env["$$scrapyard"] + assert isinstance(yard_location, String) + git = _ensure_pygit2() + repo_path = git.discover_repository(yard_location.value) + if repo_path is None: + raise ScrapError(f"Please create a scrapyard; {yard_location.value!r} is not initialized") + repo = git.Repository(repo_path, git.GIT_REPOSITORY_OPEN_BARE) + obj = repo.get(exp.name) + if obj is None: + raise ScrapError(f"Could not find object $sha1'{exp.name}") + if not isinstance(obj, git.Blob): + raise ScrapError(f"Object $sha1'{exp.name} is not a blob") + return deserialize(obj.data.decode("utf-8")) raise NotImplementedError(f"eval_exp not implemented for {exp}") @@ -1448,8 +1482,8 @@ def test_parse_negative_int_binds_tighter_than_apply(self) -> None: def test_parse_var_returns_var(self) -> None: self.assertEqual(parse([Name("abc_123")]), Var("abc_123")) - def test_parse_sha_var_returns_var(self) -> None: - self.assertEqual(parse([Name("$sha1'abc")]), Var("$sha1'abc")) + def test_parse_sha_var_returns_hash_var(self) -> None: + self.assertEqual(parse([Name("$sha1'abc")]), HashVar("abc")) def test_parse_sha_var_without_quote_returns_var(self) -> None: self.assertEqual(parse([Name("$sha1abc")]), Var("$sha1abc")) @@ -1931,6 +1965,15 @@ def test_parse_match_with_right_apply(self) -> None: ) +def _dont_have_pygit2() -> bool: + try: + import pygit2 + + return False + except ImportError: + return True + + class EvalTests(unittest.TestCase): def test_eval_int_returns_int(self) -> None: exp = Int(5) @@ -2338,6 +2381,17 @@ def test_boolean_or_on_int_raises_type_error(self) -> None: with self.assertRaisesRegex(TypeError, re.escape("expected Bool, got Int")): eval_exp({}, exp) + @unittest.skipIf(_dont_have_pygit2(), "Can't run test without pygit2") + def test_hash_var_looks_up_in_scrapyard(self) -> None: + # TODO(max): Do this in-memory instead of on-disk + with tempfile.TemporaryDirectory() as tempdir: + yard_init(tempdir) + # TODO(max): Use object id + yard_commit(tempdir, "a_test_object", Int(100)) + env = {"$$scrapyard": String(tempdir)} + result = eval_exp(env, HashVar("a8788e4ad848bac02d1c6ad76375aad65b7c5387")) + self.assertEqual(result, Int(100)) + class EndToEndTests(unittest.TestCase): def _run(self, text: str, env: Optional[Env] = None) -> Object: @@ -2752,6 +2806,19 @@ def test_boolean_and_binds_tighter_than_or(self) -> None: def test_compare_binds_tighter_than_boolean_and(self) -> None: self.assertEqual(self._run("1 < 2 && 2 < 1"), Bool(False)) + def test_hash_var_looks_up_in_scrapyard(self) -> None: + func = self._run("fac = | 0 -> 1 | n -> n * fac (n-1)") + print(func) + # TODO(max): Do this in-memory instead of on-disk + with tempfile.TemporaryDirectory() as tempdir: + yard_init(tempdir) + # TODO(max): Use object id + _, obj_id = yard_commit(tempdir, "a_test_object", func) + result = eval_exp(env, HashVar(obj_id)) + self.assertEqual(result, Int(100)) + env = {"$$scrapyard": String(tempdir)} + self.assertEqual(self._run(f"$sha1'{obj_id} 5", env), Int(120)) + class BencodeTests(unittest.TestCase): def test_bencode_int(self) -> None: @@ -3203,6 +3270,79 @@ def test_command(args: argparse.Namespace) -> None: unittest.main(argv=[__file__, *args.unittest_args]) +class ScrapError(Exception): + pass + + +def _ensure_pygit2() -> ModuleType: + try: + import pygit2 + + assert isinstance(pygit2, ModuleType) + return pygit2 + except ImportError: + raise ScrapError("Please install pygit2 to work with scrapyards") + + +def yard_init(path: str) -> None: + git = _ensure_pygit2() + branch_name = "trunk" + repo = git.init_repository(path, initial_head=branch_name, bare=True) + try: + repo.head + except git.GitError: + pass + else: + raise ScrapError("Scrapyard already initialized; cannot init scrapyard again") + # Make an initial commit so that all other commands can do the normal + # commit flow + ref = "HEAD" + author = repo.default_signature + # Make an empty tree + tree_id = repo.TreeBuilder().write() + message = "Initialize scrapyard" + parents: object = [] + commit = repo.create_commit(ref, author, author, message, tree_id, parents) + assert commit == repo.branches[branch_name].target + assert commit == repo.head.target + + +def yard_init_command(args: argparse.Namespace) -> None: + yard_init(args.yard) + + +def yard_commit(path: str, scrap_name: str, obj: Object) -> Tuple[str, str]: + git = _ensure_pygit2() + # Find the scrapyard + repo_path = git.discover_repository(path) + if repo_path is None: + raise ScrapError(f"Please create a scrapyard; {path!r} is not initialized") + repo = git.Repository(repo_path, git.GIT_REPOSITORY_OPEN_BARE) + serialized = serialize(obj) + # Make a git tree starting from previous commit's tree + prev_commit = repo.get(repo.head.target) + root = repo.TreeBuilder(prev_commit.tree) + obj_id = repo.create_blob(serialized) + # TODO(max): Figure out how to handle names like a/b; make directories? + root.insert(scrap_name, obj_id, git.GIT_FILEMODE_BLOB) + tree_id = root.write() + # Commit the tree + ref = repo.head.name + author = repo.default_signature + message = f"Update {scrap_name}" + parents = [repo.head.target] + commit = repo.create_commit(ref, author, author, message, tree_id, parents) + assert commit == repo.branches["trunk"].target + assert commit == repo.head.target + return scrap_name, obj_id + + +def yard_commit_command(args: argparse.Namespace) -> None: + obj = eval_exp(STDLIB, parse(tokenize(args.program_file.read()))) + scrap_name, obj_id = yard_commit(args.yard, args.scrap_name, obj) + print(args.scrap_name, obj_id) + + def main() -> None: parser = argparse.ArgumentParser(prog="scrapscript") subparsers = parser.add_subparsers(dest="command") @@ -3226,6 +3366,16 @@ def main() -> None: apply.add_argument("program") apply.add_argument("--debug", action="store_true") + yard = subparsers.add_parser("yard").add_subparsers(dest="command", required=True) + yard_init = yard.add_parser("init") + yard_init.set_defaults(func=yard_init_command) + yard_init.add_argument("yard") + yard_commit = yard.add_parser("commit") + yard_commit.set_defaults(func=yard_commit_command) + yard_commit.add_argument("yard") + yard_commit.add_argument("scrap_name") + yard_commit.add_argument("program_file", type=argparse.FileType("r")) + args = parser.parse_args() if not args.command: args.debug = False