initial commit

main
NotAFile 2021-09-29 16:09:45 +02:00
parent a60ada0ba3
commit 4015de11f7
4 changed files with 191 additions and 0 deletions

11
hellpipe/convert.py Normal file
View File

@ -0,0 +1,11 @@
from typing import Optional
from . import mappers
MAPPERS = {"curl": mappers.CurlMapper,
"xargs": mappers.XargsMapper}
def convert_command(command: list[str], input_name: Optional[str]):
mapper_cls = MAPPERS.get(command[0], mappers.ShellMapper)
mapper = mapper_cls(command)
return mapper.generate(input_name, ["bytes"])

58
hellpipe/main.py Normal file
View File

@ -0,0 +1,58 @@
import shlex
from textwrap import indent
import sys
from . import convert
FILE_START = """\
# generated with hellpipe
def main():
"""
FILE_END = """
if __name__ == "__main__":
main()"""
INPUTCMD = """curl "https://httpbin.org/get?test=123" | jq ".headers|keys[]" -r | xargs -L 1 echo"""
def split_commands(tokens):
cur_command = []
for tok in tokens:
if tok == "|":
yield cur_command
cur_command = []
continue
cur_command.append(tok)
yield cur_command
def main():
command = sys.argv[1] if len(sys.argv) > 1 else INPUTCMD
oneliner = shlex.shlex(INPUTCMD, posix=True, punctuation_chars=True)
stages = []
last_stage = None
for com in split_commands(oneliner):
if not last_stage:
input_name = None
else:
input_name = last_stage.output_name
stage = convert.convert_command(com, input_name)
stages.append(stage)
last_stage = stage
for stage in stages:
print("\n".join(stage.imports))
print(FILE_START)
for stage in stages:
print(indent(stage.code, " "))
print(FILE_END)
if __name__ == "__main__":
main()

108
hellpipe/mappers.py Normal file
View File

@ -0,0 +1,108 @@
import argparse
import abc
from typing import List
from dataclasses import dataclass
from textwrap import dedent
from urllib import parse as urlparse
@dataclass
class PipeEnd:
name: str
format: str
@dataclass
class ShellMapping:
code: str
output_name: str
output_format: str
input_format: str
imports: List[str]
class AbstractMapper(abc.ABC):
@abc.abstractmethod
def __init__(self, command: List[str]):
pass
@abc.abstractmethod
def get_input_types(self):
pass
@abc.abstractmethod
def generate(self, input_name: str, output_formats: List[str]) -> ShellMapping:
pass
class CurlMapper(AbstractMapper):
def __init__(self, command):
parser = argparse.ArgumentParser("curl", exit_on_error=False)
parser.add_argument("url")
self._parsed = parser.parse_args(command[1:])
def get_input_types(self):
return []
def generate(self, input_name: str, output_formats: List[str]) -> ShellMapping:
url = urlparse.urlparse(self._parsed.url)
query = urlparse.parse_qs(url.query)
url_no_qs = urlparse.urlunparse(url._replace(query=None))
code = dedent(f"""\
params = {query!r}
res = requests.get({url_no_qs!r}, params=params).text
""")
return ShellMapping(code=code, output_name="res", output_format="str", input_format="", imports=["import requests"])
class ShellMapper(AbstractMapper):
def __init__(self, command):
self.command = command
def get_input_types(self):
return ["bytes"]
def generate(self, input_name: str, output_formats: List[str]) -> ShellMapping:
out_var = f"out_{self.command[0]}" # name var after command
if input_name:
code = dedent(f"""\
proc = subprocess.Popen({self.command!r}, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr)
(stdout, _) = proc.communicate({input_name}.encode("utf-8"))
{out_var} = stdout.decode("utf-8")
""")
else:
code = f"""{out_var} = subprocess.run({self.command!r}, check=True, stdout=subprocess.PIPE, stderr=sys.stderr).stdout"""
return ShellMapping(
code=code,
imports=["import subprocess", "import sys"],
input_format="bytes",
output_format="bytes",
output_name=out_var,
)
class XargsMapper(AbstractMapper):
def __init__(self, command):
parser = argparse.ArgumentParser("xargs", exit_on_error=False)
parser.add_argument("command", nargs="+")
parser.add_argument("-L", type=int)
self._parsed = parser.parse_args(command[1:])
self.input_types = ["list", "bytes"]
def get_input_types(self):
return ["list", "bytes"]
def generate(self, input_name: str, output_formats: List[str]) -> ShellMapping:
imports = []
if self._parsed.L >= 1:
command_str = self._parsed.command
run_command = "print(i) # TODO"
if True:
input_name = f"shlex.split({input_name}, posix=True)"
imports.append("import shlex")
code = dedent(
f"""\
for i in {input_name}:
{run_command}"""
)
else:
code = "# todo: shell stuff"
return ShellMapping(code=code, output_name="", output_format="", input_format="list", imports=imports)

14
pyproject.toml Normal file
View File

@ -0,0 +1,14 @@
[tool.poetry]
name = "hellpipe"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.9"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"