From 4015de11f732f44349cc327a85c89cf4eac5f275 Mon Sep 17 00:00:00 2001 From: NotAFile Date: Wed, 29 Sep 2021 16:09:45 +0200 Subject: [PATCH] initial commit --- hellpipe/convert.py | 11 +++++ hellpipe/main.py | 58 ++++++++++++++++++++++++ hellpipe/mappers.py | 108 ++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 14 ++++++ 4 files changed, 191 insertions(+) create mode 100644 hellpipe/convert.py create mode 100644 hellpipe/main.py create mode 100644 hellpipe/mappers.py create mode 100644 pyproject.toml diff --git a/hellpipe/convert.py b/hellpipe/convert.py new file mode 100644 index 0000000..352d8e4 --- /dev/null +++ b/hellpipe/convert.py @@ -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"]) diff --git a/hellpipe/main.py b/hellpipe/main.py new file mode 100644 index 0000000..244dbc3 --- /dev/null +++ b/hellpipe/main.py @@ -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() diff --git a/hellpipe/mappers.py b/hellpipe/mappers.py new file mode 100644 index 0000000..deeffa6 --- /dev/null +++ b/hellpipe/mappers.py @@ -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) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ca29d48 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "hellpipe" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.9" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api"