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())
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
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])
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
).
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.).
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.
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])
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)