initial commit
This commit is contained in:
parent
a60ada0ba3
commit
4015de11f7
|
@ -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"])
|
|
@ -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()
|
|
@ -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)
|
|
@ -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"
|
Loading…
Reference in New Issue