morbin.morbin

  1import argparse
  2import shlex
  3import subprocess
  4from contextlib import contextmanager
  5from dataclasses import dataclass
  6
  7from pathier import Pathier
  8
  9root = Pathier(__file__).parent
 10
 11
 12@dataclass
 13class Output:
 14    """Dataclass representing the output of a terminal command.
 15
 16    #### Fields:
 17    * `return_code`
 18    * `stdout`
 19    * `stderr`"""
 20
 21    return_code: int
 22    stdout: str = ""
 23    stderr: str = ""
 24
 25
 26class Morbin:
 27    def __init__(self, capture_output: bool = False, shell: bool = False):
 28        """Command bindings should return an `Output` object.
 29
 30        If `capture_output` is `True` or the `capturing_output` context manager is used,
 31        the command's output will be available via `Output.stdout` and `Output.stderr`.
 32
 33        This property can be used to parse and use the command output or to simply execute commands "silently".
 34
 35        The return code will also be available via `Output.return_code`.
 36
 37        If `shell` is `True`, commands will be executed in the system shell (necessary on Windows for builtin shell commands like `cd` and `dir`).
 38
 39        [Security concerns using shell = True](https://docs.python.org/3/library/subprocess.html#security-considerations)
 40
 41        """
 42        self.capture_output = capture_output
 43        self.shell = shell
 44
 45    @property
 46    def capture_output(self) -> bool:
 47        """If `True`, member functions will return the generated `stdout` as a string,
 48        otherwise they return the command's exit code as a string (so my type checker doesn't throw a fit about ints.)."""
 49        return self._capture_output
 50
 51    @capture_output.setter
 52    def capture_output(self, should_capture: bool):
 53        self._capture_output = should_capture
 54
 55    @property
 56    def shell(self) -> bool:
 57        """If `True`, commands will be executed in the system shell."""
 58        return self._shell
 59
 60    @shell.setter
 61    def shell(self, should_use: bool):
 62        self._shell = should_use
 63
 64    @contextmanager
 65    def capturing_output(self):
 66        """Ensures `self.capture_output` is `True` while within the context.
 67
 68        Upon exiting the context, `self.capture_output` will be set back to whatever it was when the context was entered."""
 69        original_state = self.capture_output
 70        self.capture_output = True
 71        yield self
 72        self.capture_output = original_state
 73
 74    def execute(self, program: str, args: str) -> Output:
 75        command = [program] + shlex.split(args)
 76        if self.capture_output:
 77            output = subprocess.run(
 78                command,
 79                stdout=subprocess.PIPE,
 80                stderr=subprocess.PIPE,
 81                text=True,
 82                shell=self.shell,
 83            )
 84            return Output(output.returncode, output.stdout, output.stderr)
 85        else:
 86            output = subprocess.run(command, shell=self.shell)
 87            return Output(output.returncode)
 88
 89
 90def get_args() -> argparse.Namespace:
 91    parser = argparse.ArgumentParser()
 92
 93    parser.add_argument(
 94        "name",
 95        type=str,
 96        help=""" The program name to create a template subclass of Morbin for. """,
 97    )
 98    args = parser.parse_args()
 99
100    return args
101
102
103def main(args: argparse.Namespace | None = None):
104    if not args:
105        args = get_args()
106    template = (root / "template.py").read_text()
107    template = template.replace("Program", args.name.capitalize()).replace(
108        "program", args.name
109    )
110    (Pathier.cwd() / f"{args.name}.py").write_text(template)
111
112
113if __name__ == "__main__":
114    main(get_args())
@dataclass
class Output:
13@dataclass
14class Output:
15    """Dataclass representing the output of a terminal command.
16
17    #### Fields:
18    * `return_code`
19    * `stdout`
20    * `stderr`"""
21
22    return_code: int
23    stdout: str = ""
24    stderr: str = ""

Dataclass representing the output of a terminal command.

Fields:

  • return_code
  • stdout
  • stderr
Output(return_code: int, stdout: str = '', stderr: str = '')
class Morbin:
27class Morbin:
28    def __init__(self, capture_output: bool = False, shell: bool = False):
29        """Command bindings should return an `Output` object.
30
31        If `capture_output` is `True` or the `capturing_output` context manager is used,
32        the command's output will be available via `Output.stdout` and `Output.stderr`.
33
34        This property can be used to parse and use the command output or to simply execute commands "silently".
35
36        The return code will also be available via `Output.return_code`.
37
38        If `shell` is `True`, commands will be executed in the system shell (necessary on Windows for builtin shell commands like `cd` and `dir`).
39
40        [Security concerns using shell = True](https://docs.python.org/3/library/subprocess.html#security-considerations)
41
42        """
43        self.capture_output = capture_output
44        self.shell = shell
45
46    @property
47    def capture_output(self) -> bool:
48        """If `True`, member functions will return the generated `stdout` as a string,
49        otherwise they return the command's exit code as a string (so my type checker doesn't throw a fit about ints.)."""
50        return self._capture_output
51
52    @capture_output.setter
53    def capture_output(self, should_capture: bool):
54        self._capture_output = should_capture
55
56    @property
57    def shell(self) -> bool:
58        """If `True`, commands will be executed in the system shell."""
59        return self._shell
60
61    @shell.setter
62    def shell(self, should_use: bool):
63        self._shell = should_use
64
65    @contextmanager
66    def capturing_output(self):
67        """Ensures `self.capture_output` is `True` while within the context.
68
69        Upon exiting the context, `self.capture_output` will be set back to whatever it was when the context was entered."""
70        original_state = self.capture_output
71        self.capture_output = True
72        yield self
73        self.capture_output = original_state
74
75    def execute(self, program: str, args: str) -> Output:
76        command = [program] + shlex.split(args)
77        if self.capture_output:
78            output = subprocess.run(
79                command,
80                stdout=subprocess.PIPE,
81                stderr=subprocess.PIPE,
82                text=True,
83                shell=self.shell,
84            )
85            return Output(output.returncode, output.stdout, output.stderr)
86        else:
87            output = subprocess.run(command, shell=self.shell)
88            return Output(output.returncode)
Morbin(capture_output: bool = False, shell: bool = False)
28    def __init__(self, capture_output: bool = False, shell: bool = False):
29        """Command bindings should return an `Output` object.
30
31        If `capture_output` is `True` or the `capturing_output` context manager is used,
32        the command's output will be available via `Output.stdout` and `Output.stderr`.
33
34        This property can be used to parse and use the command output or to simply execute commands "silently".
35
36        The return code will also be available via `Output.return_code`.
37
38        If `shell` is `True`, commands will be executed in the system shell (necessary on Windows for builtin shell commands like `cd` and `dir`).
39
40        [Security concerns using shell = True](https://docs.python.org/3/library/subprocess.html#security-considerations)
41
42        """
43        self.capture_output = capture_output
44        self.shell = shell

Command bindings should return an Output object.

If capture_output is True or the capturing_output context manager is used, the command's output will be available via Output.stdout and Output.stderr.

This property can be used to parse and use the command output or to simply execute commands "silently".

The return code will also be available via Output.return_code.

If shell is True, commands will be executed in the system shell (necessary on Windows for builtin shell commands like cd and dir).

Security concerns using shell = True

capture_output: bool

If True, member functions will return the generated stdout as a string, otherwise they return the command's exit code as a string (so my type checker doesn't throw a fit about ints.).

shell: bool

If True, commands will be executed in the system shell.

@contextmanager
def capturing_output(self):
65    @contextmanager
66    def capturing_output(self):
67        """Ensures `self.capture_output` is `True` while within the context.
68
69        Upon exiting the context, `self.capture_output` will be set back to whatever it was when the context was entered."""
70        original_state = self.capture_output
71        self.capture_output = True
72        yield self
73        self.capture_output = original_state

Ensures self.capture_output is True while within the context.

Upon exiting the context, self.capture_output will be set back to whatever it was when the context was entered.

def execute(self, program: str, args: str) -> morbin.morbin.Output:
75    def execute(self, program: str, args: str) -> Output:
76        command = [program] + shlex.split(args)
77        if self.capture_output:
78            output = subprocess.run(
79                command,
80                stdout=subprocess.PIPE,
81                stderr=subprocess.PIPE,
82                text=True,
83                shell=self.shell,
84            )
85            return Output(output.returncode, output.stdout, output.stderr)
86        else:
87            output = subprocess.run(command, shell=self.shell)
88            return Output(output.returncode)
def get_args() -> argparse.Namespace:
 91def get_args() -> argparse.Namespace:
 92    parser = argparse.ArgumentParser()
 93
 94    parser.add_argument(
 95        "name",
 96        type=str,
 97        help=""" The program name to create a template subclass of Morbin for. """,
 98    )
 99    args = parser.parse_args()
100
101    return args
def main(args: argparse.Namespace | None = None):
104def main(args: argparse.Namespace | None = None):
105    if not args:
106        args = get_args()
107    template = (root / "template.py").read_text()
108    template = template.replace("Program", args.name.capitalize()).replace(
109        "program", args.name
110    )
111    (Pathier.cwd() / f"{args.name}.py").write_text(template)