Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ install:
test:
pytest test_pybash.py -vv -rs

dev:
python run.py

debug:
python -m ideas demo -a pybash -s

clean:
rm -rf build/ dist/ *.egg-info .pytest_cache
find . -name '*.pyc' -type f -exec rm -rf {} +
Expand All @@ -30,4 +36,7 @@ lint:
flake8 $(SOURCE_FILES) --count --show-source --statistics
flake8 $(SOURCE_FILES) --count --exit-zero --statistics

shell:
source $(poetry env info --path)/bin/activate

.PHONY: test clean
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,30 @@ WHENDY WORLD
$ls .github/*
```

### 10. Interpolation
Denoted by {{variable_or_function_call_here}}. Only static interpolation is supported currently so no quotes, spaces or expressions within the {{}} or in the string being injected.

```python
# GOOD
command = "status"
def get_option(command):
return "-s" if command == "status" else "-v"
>git {{command}} {{get_option(command)}}

display_type = "labels"
>k get pods --show-{{display_type}}=true

# BAD
option = "-s -v"
>git status {{option}}

options = ['-s', '-v']
>git status {{" ".join(options)}}

options = {'version': '-v'}
>git status {{options['version']}}
```

#### Also works inside methods!
```python
# PYBASH DEMO #
Expand Down
6 changes: 6 additions & 0 deletions demo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# PYBASH DEMO #
def run():
# command interpolation
git_command = "status"
option = "-v"

>git {{git_command}} {{option}}

#TODO:
#a = >echo "hello" >> test.txt
#a = >sort < test.txt | grep "HELLO"
Expand Down
14 changes: 7 additions & 7 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 49 additions & 19 deletions pybash.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import re
import shlex

import token_utils
from ideas import import_hook


@staticmethod
def source_init():
"""Adds subprocess import"""
import_subprocess = "import subprocess"
return import_subprocess


@staticmethod
def add_hook(**_kwargs):
"""Creates and automatically adds the import hook in sys.meta_path"""
hook = import_hook.create_hook(
hook_name=__name__, transform_source=Transformer.transform_source, source_init=source_init
)
return hook


class InvalidInterpolation(Exception):
pass


class Processor:
command_char = ">"

Expand All @@ -19,6 +40,26 @@ def parse(self):
def transform(self) -> token_utils.Token:
raise NotImplementedError

@staticmethod
def interpolate(string: str) -> str:
"""Process {{ interpolations }} and substitute.
Interpolations are denotated by a {{ }} with a variable or a function call inside.

Args:
string (str): String to interpolate

Returns:
str: Interpolated string
"""

# validate interpolation
invalid_chars = [' ', '"', "'"]
matches = re.findall(r'{{(.+?)}}', string)
if matches and any(any(bad_char in match for bad_char in invalid_chars) for match in matches):
raise InvalidInterpolation

return re.sub(r'{{(.+?)}}', r'" + \1 + "', string)


class Shelled(Processor):
# $ls .github/*
Expand All @@ -32,7 +73,6 @@ def transform(self) -> token_utils.Token:

class Execed(Processor):
# >ls -la

def transform(self) -> token_utils.Token:
pipeline_command = Pipeline(self.command).parse_command()
if pipeline_command != self.command:
Expand Down Expand Up @@ -90,8 +130,8 @@ class Transformer:
tokenizers = {"$": Shelled, ">": Execed}
greedy_tokenizers = {"= >": Variablized, "(>": Wrapped}

@classmethod
def transform_source(cls, source, **_kwargs):
@staticmethod
def transform_source(source, **_kwargs):
"""Convert >bash commands to subprocess calls"""
new_tokens = []
for line in token_utils.get_lines(source):
Expand All @@ -101,16 +141,20 @@ def transform_source(cls, source, **_kwargs):
continue

# matches exact token
token_match = [tokenizer for match, tokenizer in cls.tokenizers.items() if token == match]
token_match = [tokenizer for match, tokenizer in Transformer.tokenizers.items() if token == match]
if token_match:
token = token_match[0](token).transform()
token.string = Processor.interpolate(token.string)
new_tokens.append(token)
continue

# matches anywhere in line
greedy_match = [tokenizer for match, tokenizer in cls.greedy_tokenizers.items() if match in token.line]
greedy_match = [
tokenizer for match, tokenizer in Transformer.greedy_tokenizers.items() if match in token.line
]
if greedy_match:
token = greedy_match[0](token).transform()
token.string = Processor.interpolate(token.string)
new_tokens.append(token)
continue

Expand Down Expand Up @@ -385,17 +429,3 @@ def build_subprocess_list_cmd(method: str, args: list, **kwargs) -> str:
command += f", {k}={v}"
command += ")"
return command


def source_init():
"""Adds subprocess import"""
import_subprocess = "import subprocess"
return import_subprocess


def add_hook(**_kwargs):
"""Creates and automatically adds the import hook in sys.meta_path"""
hook = import_hook.create_hook(
hook_name=__name__, transform_source=Transformer.transform_source, source_init=source_init
)
return hook
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[tool.poetry]
name = "PyBash"
version = "0.2.1"
version = "0.2.2"
description = ">execute bash commands from python easily"
authors = ["Jay"]
readme = "README.md"
packages = [{include = "pybash.py"}]

[tool.poetry.dependencies]
python = "^3.9"
ideas = "^0.1.3"
ideas = "^0.1.5"

[tool.poetry.group.dev.dependencies]
black = "^22.12.0"
Expand Down
21 changes: 21 additions & 0 deletions test_pybash.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

import pybash

run_bash = pybash.Transformer.transform_source
Expand Down Expand Up @@ -95,6 +97,25 @@ def test_shell_commands():
assert run_bash("$ls .github/*") == 'subprocess.run("ls .github/*", shell=True)\n'


def test_interpolate():
assert run_bash(">git {{command}} {{option}}") == 'subprocess.run(["git","" + command + "","" + option + ""])\n'
assert (
run_bash(">git {{command}} {{process(option)}}")
== 'subprocess.run(["git","" + command + "","" + process(option) + ""])\n'
)
assert (
run_bash(">k get pods --show-{{display_type}}=true")
== 'subprocess.run(["k","get","pods","--show-" + display_type + "=true"])\n'
)


def test_invalid_interpolate():
with pytest.raises(pybash.InvalidInterpolation):
assert run_bash(">git {{command}} {{ option }}")
assert run_bash(">git {{command}} {{'\"'.join(option)}}")
assert run_bash(">git {{command}} {{a['key']}}")


def test_no_parse():
assert run_bash('if 5 > 4:') == 'if 5 > 4:'
assert run_bash('if (pred1 and pred2) > 0:') == 'if (pred1 and pred2) > 0:'