From 28be20deff0e3e077c131d9431113f055655c8e7 Mon Sep 17 00:00:00 2001 From: Jay Date: Sat, 13 May 2023 20:37:41 -0400 Subject: [PATCH] Standalone support (#5) * standalone support --- Makefile | 5 +- README.md | 22 ++- demo.py | 4 +- poetry.lock | 253 ++++++++++++++--------------- pybash/__init__.py | 0 pybash/hook.py | 22 +++ pybash.py => pybash/transformer.py | 90 +++++----- pyproject.toml | 12 +- run.py | 8 +- test_pybash.py | 7 +- 10 files changed, 213 insertions(+), 210 deletions(-) create mode 100644 pybash/__init__.py create mode 100644 pybash/hook.py rename pybash.py => pybash/transformer.py (89%) diff --git a/Makefile b/Makefile index b98ea4b..23b02d5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ check: lint test -SOURCE_FILES=pybash.py test_pybash.py run.py +SOURCE_FILES=pybash test_pybash.py run.py install: pip install -e . @@ -39,4 +39,7 @@ lint: shell: source $(poetry env info --path)/bin/activate +debug: + python -m ideas demo -a pybash.hook -s + .PHONY: test clean \ No newline at end of file diff --git a/README.md b/README.md index cd5f587..877c992 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,25 @@ Streamline bash-command execution from python with a new syntax. It combines the For security and performance reasons, PyBash will NOT execute as shell, unless explicitly specified with a `$` instead of a single `>` before the command. While running commands as shell can be convenient, it can also spawn security risks if you're not too careful. If you're curious about the transformations, look at the [unit tests](test_pybash.py) for some quick examples. -Note: this is a mainly experimental library. Consider the risks and test before using in prod. +Note: this is a mainly experimental library. -# Installation +# Setup + +## As standalone transformer `pip install pybash` -# Setup hook + ```python -import pybash -pybash.add_hook() +from pybash.transformer import transform + +transform(">echo hello world") # returns the python code for the bash command as string ``` +## As ideas hook +`pip install "pybash[ideas]"` + +See [run.py](run.py) for an example. + # Usage ### 1. Simple execution with output @@ -149,5 +157,5 @@ cp_test() #### Demo `python run.py` -#### Debugging -`python -m ideas demo -a pybash -s` to view the transformed source code +#### Debug +`make debug` to view the transformed source code diff --git a/demo.py b/demo.py index cca24f4..d53dbd2 100644 --- a/demo.py +++ b/demo.py @@ -2,10 +2,10 @@ # dynamic interpolation options = {'version': '-v', 'help': '-h'} ->git {{{options['help']}}} +>git f{options['help']} namespace = "coffee" ->kubectl get pods {{{"--" + "-".join(['show', 'labels'])}}} --namespace {{{namespace}}} +>kubectl get pods f{"--" + "-".join(['show', 'labels'])} --namespace f{namespace} # static interpolation git_command = "status" diff --git a/poetry.lock b/poetry.lock index c7f5ad2..c5be4a7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,25 +1,16 @@ -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "autoflake" -version = "2.0.0" +version = "2.1.1" description = "Removes unused imports and unused variables" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "autoflake-2.1.1-py3-none-any.whl", hash = "sha256:94e330a2bcf5ac01384fb2bf98bea60c6383eaa59ea62be486e376622deba985"}, + {file = "autoflake-2.1.1.tar.gz", hash = "sha256:75524b48d42d6537041d91f17573b8a98cb645642f9f05c7fcc68de10b1cade3"}, +] [package.dependencies] pyflakes = ">=3.0.0" @@ -32,6 +23,20 @@ description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] [package.dependencies] click = ">=8.0.0" @@ -54,6 +59,10 @@ description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -65,14 +74,22 @@ description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "exceptiongroup" -version = "1.1.0" +version = "1.1.1" description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] [package.extras] test = ["pytest (>=6)"] @@ -84,6 +101,10 @@ description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, + {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, +] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" @@ -95,8 +116,12 @@ name = "ideas" version = "0.1.5" description = "Easy creation of import hooks to test ideas." category = "main" -optional = false +optional = true python-versions = ">=3.6" +files = [ + {file = "ideas-0.1.5-py3-none-any.whl", hash = "sha256:894f9db904fd15eb7735d06ecdf77b268f658df778760f8e1ce7bb1e08ac81ad"}, + {file = "ideas-0.1.5.tar.gz", hash = "sha256:20edb786a6f1028e0659e9e51497b395014359baac7ffe74f1d3a89a42414cc0"}, +] [package.dependencies] token-utils = "*" @@ -108,18 +133,26 @@ description = "brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] [[package]] name = "isort" -version = "5.11.4" +version = "5.12.0" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] @@ -130,42 +163,62 @@ description = "McCabe checker, plugin for flake8" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] [[package]] name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] [[package]] name = "packaging" -version = "23.0" +version = "23.1" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] [[package]] name = "pathspec" -version = "0.11.0" +version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] [[package]] name = "platformdirs" -version = "2.6.2" +version = "3.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.5.0-py3-none-any.whl", hash = "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4"}, + {file = "platformdirs-3.5.0.tar.gz", hash = "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"}, +] [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -174,6 +227,10 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] [package.extras] dev = ["pre-commit", "tox"] @@ -186,6 +243,10 @@ description = "Python style guide checker" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, + {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, +] [[package]] name = "pyflakes" @@ -194,17 +255,24 @@ description = "passive checker of Python programs" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, + {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, +] [[package]] name = "pytest" -version = "7.2.1" +version = "7.3.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, + {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, +] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" @@ -213,7 +281,7 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "token-utils" @@ -222,6 +290,10 @@ description = "Small utility to work with Python tokens" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "token-utils-0.1.8.tar.gz", hash = "sha256:7f4e2ff973822ce1e1829ba70e17d00b315a570c5be625b01a3f361933c2f86a"}, + {file = "token_utils-0.1.8-py3-none-any.whl", hash = "sha256:8bdca11ae8aacab3e47dacbb21165c2d118167d2a0a59b93e61aa8af03f62481"}, +] [[package]] name = "tomli" @@ -230,116 +302,27 @@ description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.5.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, +] + +[extras] +ideas = ["ideas"] [metadata] -lock-version = "1.1" +lock-version = "2.0" python-versions = "^3.9" -content-hash = "1851ad91ce9e8498716141ae6c414eeb20e6a13733cbf3b4e25523736f884e3a" - -[metadata.files] -attrs = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] -autoflake = [ - {file = "autoflake-2.0.0-py3-none-any.whl", hash = "sha256:d58ed4187c6b4f623a942b9a90c43ff84bf6a266f3682f407b42ca52073c9678"}, - {file = "autoflake-2.0.0.tar.gz", hash = "sha256:7185b596e70d8970c6d4106c112ef41921e472bd26abf3613db99eca88cc8c2a"}, -] -black = [ - {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, - {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, - {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, - {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, - {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, - {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, - {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, - {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, - {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, - {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, - {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, - {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, -] -flake8 = [ - {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, - {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, -] -ideas = [ - {file = "ideas-0.1.5-py3-none-any.whl", hash = "sha256:894f9db904fd15eb7735d06ecdf77b268f658df778760f8e1ce7bb1e08ac81ad"}, - {file = "ideas-0.1.5.tar.gz", hash = "sha256:20edb786a6f1028e0659e9e51497b395014359baac7ffe74f1d3a89a42414cc0"}, -] -iniconfig = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] -isort = [ - {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"}, - {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"}, -] -mccabe = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -packaging = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, -] -pathspec = [ - {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, - {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, -] -platformdirs = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pycodestyle = [ - {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, - {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, -] -pyflakes = [ - {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, - {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, -] -pytest = [ - {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, - {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, -] -token-utils = [ - {file = "token-utils-0.1.8.tar.gz", hash = "sha256:7f4e2ff973822ce1e1829ba70e17d00b315a570c5be625b01a3f361933c2f86a"}, - {file = "token_utils-0.1.8-py3-none-any.whl", hash = "sha256:8bdca11ae8aacab3e47dacbb21165c2d118167d2a0a59b93e61aa8af03f62481"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] +content-hash = "1a821c8672a6a61a07e63c61bd66cdd5cf586e782e43e5dd70ae2e697b33b80f" diff --git a/pybash/__init__.py b/pybash/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pybash/hook.py b/pybash/hook.py new file mode 100644 index 0000000..75fd81f --- /dev/null +++ b/pybash/hook.py @@ -0,0 +1,22 @@ +from ideas import import_hook + +from pybash.transformer import transform + + +def source_init(): + """Adds subprocess import""" + return "import subprocess" + + +def add_hook(**_kwargs): + """Creates and automatically adds the import hook in sys.meta_path""" + return import_hook.create_hook( + hook_name=__name__, + transform_source=transform_source, + source_init=source_init, + ) + + +def transform_source(source, **_kwargs): + """Convert >bash commands to subprocess calls""" + return transform(source, **_kwargs) diff --git a/pybash.py b/pybash/transformer.py similarity index 89% rename from pybash.py rename to pybash/transformer.py index 885b307..06c2160 100644 --- a/pybash.py +++ b/pybash/transformer.py @@ -3,21 +3,6 @@ from typing import Callable, Union import token_utils -from ideas import import_hook - - -def source_init(): - """Adds subprocess import""" - return "import subprocess" - - -def add_hook(**_kwargs): - """Creates and automatically adds the import hook in sys.meta_path""" - return import_hook.create_hook( - hook_name=__name__, - transform_source=Transformer.transform_source, - source_init=source_init, - ) class InvalidInterpolation(Exception): @@ -47,7 +32,8 @@ def interpolate(self) -> None: def fstring_interpolate(token_string: str, parsed_command: str) -> str: """Process f{ dynamic interpolations } and substitute. Dynamic interpolations are denotated by a f{ } with any expression inside. - Substitution in the parsed command string happens relative to the order of the interpolations in the original command string. + Substitution in the parsed command string happens relative to the order of the + interpolations in the original command string. Args: token_string (str): Original command string @@ -71,7 +57,8 @@ def fstring_interpolate(token_string: str, parsed_command: str) -> str: def direct_interpolate(string: str) -> str: """Process {{ static interpolations }} and substitute. Static interpolations are denotated by a {{ }} with a variable or a function call inside. - Substitution happens directly on the parsed command string. Therefore, certain characters cannot be interpolated as they get parsed out before substitution. + Substitution happens directly on the parsed command string. Therefore, certain characters + cannot be interpolated as they get parsed out before substitution. Args: string (str): String to interpolate @@ -152,42 +139,6 @@ def transform(self) -> token_utils.Token: return self.token -class Transformer: - tokenizers = {"$": Shelled, ">": Execed} - greedy_tokenizers = {"= >": Variablized, "(>": Wrapped} - - @staticmethod - def transform_source(source, **_kwargs): - """Convert >bash commands to subprocess calls""" - new_tokens = [] - for line in token_utils.get_lines(source): - token = token_utils.get_first(line) - if not token: - new_tokens.extend(line) - continue - - if token_match := [tokenizer for match, tokenizer in Transformer.tokenizers.items() if token == match]: - parser = token_match[0](token) - parser.transform() - parser.interpolate() - new_tokens.append(parser.token) - continue - - if greedy_match := [ - tokenizer for match, tokenizer in Transformer.greedy_tokenizers.items() if match in token.line - ]: - parser = greedy_match[0](token) - parser.transform() - parser.interpolate() - new_tokens.append(parser.token) - continue - - # no match - new_tokens.extend(line) - - return token_utils.untokenize(new_tokens) - - class Pipers: """Handles the logic of chaining operators""" @@ -459,3 +410,36 @@ def build_subprocess_list_cmd(method: str, args: list, **kwargs) -> str: command += f", {k}={v}" command += ")" return command + + +TOKENIZERS = {"$": Shelled, ">": Execed} +GREEDY_TOKENIZERS = {"= >": Variablized, "(>": Wrapped} + + +def transform(source, **_kwargs): + """Convert >bash commands to subprocess calls""" + new_tokens = [] + for line in token_utils.get_lines(source): + token = token_utils.get_first(line) + if not token: + new_tokens.extend(line) + continue + + if token_match := [tokenizer for match, tokenizer in TOKENIZERS.items() if token == match]: + parser = token_match[0](token) + parser.transform() + parser.interpolate() + new_tokens.append(parser.token) + continue + + if greedy_match := [tokenizer for match, tokenizer in GREEDY_TOKENIZERS.items() if match in token.line]: + parser = greedy_match[0](token) + parser.transform() + parser.interpolate() + new_tokens.append(parser.token) + continue + + # no match + new_tokens.extend(line) + + return token_utils.untokenize(new_tokens) diff --git a/pyproject.toml b/pyproject.toml index 7d434c7..99a7d2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,18 @@ [tool.poetry] name = "PyBash" -version = "0.2.5" +version = "0.3.0" description = ">execute bash commands from python easily" -authors = ["Jay"] +authors = ["Jay "] readme = "README.md" -packages = [{include = "pybash.py"}] +packages = [{include = "pybash"}] [tool.poetry.dependencies] python = "^3.9" -ideas = "^0.1.5" +ideas = { version = "^0.1.5", optional = true } +token-utils = "^0.1.8" + +[tool.poetry.extras] +ideas = ["ideas"] [tool.poetry.group.dev.dependencies] black = "^22.12.0" diff --git a/run.py b/run.py index 7856e4b..71c6991 100755 --- a/run.py +++ b/run.py @@ -1,12 +1,12 @@ ## # Setup hook -import pybash +from pybash.hook import add_hook -pybash.add_hook() +add_hook() ## -# Import demo script and run +# Import demo script to run if __name__ == "__main__": import demo - demo.run() + print(f"running {demo}") diff --git a/test_pybash.py b/test_pybash.py index 68a1004..c369fef 100644 --- a/test_pybash.py +++ b/test_pybash.py @@ -1,8 +1,7 @@ import pytest -import pybash - -run_bash = pybash.Transformer.transform_source +from pybash.transformer import InvalidInterpolation +from pybash.transformer import transform as run_bash def test_single_exec(): @@ -118,7 +117,7 @@ def test_fstring_interpolate(): def test_invalid_interpolate(): - with pytest.raises(pybash.InvalidInterpolation): + with pytest.raises(InvalidInterpolation): assert run_bash(">git {{command}} {{ option }}") assert run_bash(">git {{command}} {{'\"'.join(option)}}") assert run_bash(">git {{command}} {{a['key']}}")