morbin.morbin

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

Dataclass representing the output of a terminal command.

Fields:

  • return_code: list[int]
  • stdout: str
  • stderr: str
Output(return_code: list[int], stdout: str = '', stderr: str = '')
class Morbin:
35class Morbin:
36    def __init__(self, capture_output: bool = False, shell: bool = False):
37        """Command bindings should return an `Output` object.
38
39        If `capture_output` is `True` or the `capturing_output` context manager is used,
40        the command's output will be available via `Output.stdout` and `Output.stderr`.
41
42        This property can be used to parse and use the command output or to simply execute commands "silently".
43
44        The return code will also be available via `Output.return_code`.
45
46        If `shell` is `True`, commands will be executed in the system shell (necessary on Windows for builtin shell commands like `cd` and `dir`).
47
48        [Security concerns using shell = True](https://docs.python.org/3/library/subprocess.html#security-considerations)
49
50        """
51        self.capture_output = capture_output
52        self.shell = shell
53
54    @property
55    def capture_output(self) -> bool:
56        """If `True`, member functions will return the generated `stdout` as a string,
57        otherwise they return the command's exit code as a string (so my type checker doesn't throw a fit about ints.)."""
58        return self._capture_output
59
60    @capture_output.setter
61    def capture_output(self, should_capture: bool):
62        self._capture_output = should_capture
63
64    @property
65    def shell(self) -> bool:
66        """If `True`, commands will be executed in the system shell."""
67        return self._shell
68
69    @shell.setter
70    def shell(self, should_use: bool):
71        self._shell = should_use
72
73    @contextmanager
74    def capturing_output(self):
75        """Ensures `self.capture_output` is `True` while within the context.
76
77        Upon exiting the context, `self.capture_output` will be set back to whatever it was when the context was entered."""
78        original_state = self.capture_output
79        self.capture_output = True
80        yield self
81        self.capture_output = original_state
82
83    def execute(self, program: str, args: str) -> Output:
84        command = [program] + shlex.split(args)
85        if self.capture_output:
86            output = subprocess.run(
87                command,
88                stdout=subprocess.PIPE,
89                stderr=subprocess.PIPE,
90                text=True,
91                shell=self.shell,
92            )
93            return Output([output.returncode], output.stdout, output.stderr)
94        else:
95            output = subprocess.run(command, shell=self.shell)
96            return Output([output.returncode])
Morbin(capture_output: bool = False, shell: bool = False)
36    def __init__(self, capture_output: bool = False, shell: bool = False):
37        """Command bindings should return an `Output` object.
38
39        If `capture_output` is `True` or the `capturing_output` context manager is used,
40        the command's output will be available via `Output.stdout` and `Output.stderr`.
41
42        This property can be used to parse and use the command output or to simply execute commands "silently".
43
44        The return code will also be available via `Output.return_code`.
45
46        If `shell` is `True`, commands will be executed in the system shell (necessary on Windows for builtin shell commands like `cd` and `dir`).
47
48        [Security concerns using shell = True](https://docs.python.org/3/library/subprocess.html#security-considerations)
49
50        """
51        self.capture_output = capture_output
52        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):
73    @contextmanager
74    def capturing_output(self):
75        """Ensures `self.capture_output` is `True` while within the context.
76
77        Upon exiting the context, `self.capture_output` will be set back to whatever it was when the context was entered."""
78        original_state = self.capture_output
79        self.capture_output = True
80        yield self
81        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:
83    def execute(self, program: str, args: str) -> Output:
84        command = [program] + shlex.split(args)
85        if self.capture_output:
86            output = subprocess.run(
87                command,
88                stdout=subprocess.PIPE,
89                stderr=subprocess.PIPE,
90                text=True,
91                shell=self.shell,
92            )
93            return Output([output.returncode], output.stdout, output.stderr)
94        else:
95            output = subprocess.run(command, shell=self.shell)
96            return Output([output.returncode])
def get_args() -> argparse.Namespace:
 99def get_args() -> argparse.Namespace:
100    parser = argparse.ArgumentParser()
101
102    parser.add_argument(
103        "name",
104        type=str,
105        help=""" The program name to create a template subclass of Morbin for. """,
106    )
107    args = parser.parse_args()
108
109    return args
def main(args: argparse.Namespace | None = None):
112def main(args: argparse.Namespace | None = None):
113    if not args:
114        args = get_args()
115    template = (root / "template.py").read_text()
116    template = template.replace("Program", args.name.capitalize()).replace(
117        "program", args.name
118    )
119    (Pathier.cwd() / f"{args.name}.py").write_text(template)