minishell 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- minishell-0.0.1/PKG-INFO +21 -0
- minishell-0.0.1/README.md +4 -0
- minishell-0.0.1/pyproject.toml +25 -0
- minishell-0.0.1/src/minishell/__init__.py +21 -0
- minishell-0.0.1/src/minishell/cmd.py +170 -0
- minishell-0.0.1/src/minishell/config.py +58 -0
- minishell-0.0.1/src/minishell/error.py +58 -0
- minishell-0.0.1/src/minishell/logger.py +70 -0
- minishell-0.0.1/src/minishell/manual_test_run.py +50 -0
minishell-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: minishell
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary:
|
|
5
|
+
License: LGPL-3.0-or-later
|
|
6
|
+
Author: RomanLabs
|
|
7
|
+
Author-email: rromanotero@romanlabs.com
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Requires-Dist: mypy (>=1.10.0,<2.0.0)
|
|
14
|
+
Requires-Dist: rich (>=13.7.1,<14.0.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
## MiniShell
|
|
18
|
+
|
|
19
|
+
[Project in Gitlab](https://gitlab.com/romanlabs/public/minishell)
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "minishell"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = ["RomanLabs <rromanotero@romanlabs.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "LGPL-3.0-or-later"
|
|
8
|
+
packages = [{include = "minishell", from = "src"}]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.scripts]
|
|
11
|
+
manual-test-run = "minishell.manual_test_run:main"
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dependencies]
|
|
14
|
+
python = "^3.11"
|
|
15
|
+
mypy = "^1.10.0"
|
|
16
|
+
rich = "^13.7.1"
|
|
17
|
+
|
|
18
|
+
[tool.poetry.group.dev.dependencies]
|
|
19
|
+
mypy = "^1.10.0"
|
|
20
|
+
pytest = "^8.2.1"
|
|
21
|
+
toml = "^0.10.2"
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["poetry-core"]
|
|
25
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright (C) 2024 RomanLabs, Rafael Roman Otero
|
|
2
|
+
# This file is part of API Pipe.
|
|
3
|
+
#
|
|
4
|
+
# API Pipe is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# API Pipe is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
15
|
+
# along with API Pipe. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
'''
|
|
18
|
+
Module Name
|
|
19
|
+
'''
|
|
20
|
+
__version__ = '0.0.1'
|
|
21
|
+
__author__ = 'RomanLabs'
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Copyright (C) 2024 RomanLabs, Rafael Roman Otero
|
|
2
|
+
# This file is part of API Pipe.
|
|
3
|
+
#
|
|
4
|
+
# API Pipe is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# API Pipe is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
15
|
+
# along with API Pipe. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
'''
|
|
18
|
+
Cmd
|
|
19
|
+
'''
|
|
20
|
+
import logging
|
|
21
|
+
import subprocess
|
|
22
|
+
from typing import ClassVar
|
|
23
|
+
|
|
24
|
+
from minishell.error import ArgumentError, NonZeroExit
|
|
25
|
+
from minishell import logger
|
|
26
|
+
|
|
27
|
+
class CmdMeta(type):
|
|
28
|
+
def __getattr__(cls, method_name: str):
|
|
29
|
+
'''
|
|
30
|
+
__getattr__
|
|
31
|
+
|
|
32
|
+
Captures get attribute calls
|
|
33
|
+
to the Cmd class
|
|
34
|
+
|
|
35
|
+
This enables the chaining of commands
|
|
36
|
+
STARTING FROM Cmd, as opposed to Cmd()
|
|
37
|
+
|
|
38
|
+
e.g.
|
|
39
|
+
|
|
40
|
+
Cmd.i.am.a.command.withh.arguments("arg1")
|
|
41
|
+
|
|
42
|
+
'''
|
|
43
|
+
return Cmd(method_name)
|
|
44
|
+
|
|
45
|
+
class Cmd(metaclass=CmdMeta):
|
|
46
|
+
_log: ClassVar[logging.Logger] = logger.stdout(__name__, logging.DEBUG)
|
|
47
|
+
__is_first_call: ClassVar[bool] = False
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def log_setup(cls, log_level: int):
|
|
51
|
+
'''
|
|
52
|
+
log_setup
|
|
53
|
+
|
|
54
|
+
Sets up logging for all commands
|
|
55
|
+
'''
|
|
56
|
+
Cmd._log = logger.stdout(__name__, log_level)
|
|
57
|
+
|
|
58
|
+
def __init__(self, cmd_str_to_add=''):
|
|
59
|
+
'''
|
|
60
|
+
__init__
|
|
61
|
+
'''
|
|
62
|
+
if not Cmd.__is_first_call:
|
|
63
|
+
# enable pretty tracebacks
|
|
64
|
+
logger.enable_pretty_tracebacks()
|
|
65
|
+
Cmd.__is_first_call = True
|
|
66
|
+
|
|
67
|
+
self.cmd_str = cmd_str_to_add
|
|
68
|
+
|
|
69
|
+
def __getattr__(self, method_name: str):
|
|
70
|
+
'''
|
|
71
|
+
__getattr__
|
|
72
|
+
|
|
73
|
+
Captures get attribute calls
|
|
74
|
+
|
|
75
|
+
Returns a new Cmd object with
|
|
76
|
+
the attribute name appended to cmd_str
|
|
77
|
+
'''
|
|
78
|
+
if self.cmd_str:
|
|
79
|
+
return Cmd(f"{self.cmd_str} {method_name}")
|
|
80
|
+
else:
|
|
81
|
+
#
|
|
82
|
+
# this runs only once
|
|
83
|
+
# when the first method is called
|
|
84
|
+
#
|
|
85
|
+
# e.g.
|
|
86
|
+
# Cmd().i.am.a.command.withh.arguments("arg1")
|
|
87
|
+
#
|
|
88
|
+
# calling i will return a new Cmd object
|
|
89
|
+
# with cmd_str set to i
|
|
90
|
+
#
|
|
91
|
+
# Therefore, the next call to am will append to cmd_str
|
|
92
|
+
# and so forth
|
|
93
|
+
#
|
|
94
|
+
return Cmd(method_name)
|
|
95
|
+
|
|
96
|
+
def __call__(self, *args, **kwargs):
|
|
97
|
+
'''
|
|
98
|
+
__call__
|
|
99
|
+
|
|
100
|
+
Captures call to object
|
|
101
|
+
|
|
102
|
+
This is the last call in the chain
|
|
103
|
+
and will execute the command with
|
|
104
|
+
the arguments passed in
|
|
105
|
+
'''
|
|
106
|
+
if kwargs:
|
|
107
|
+
raise ArgumentError(
|
|
108
|
+
f"Shell Command object cannot take keyword arguments"
|
|
109
|
+
)
|
|
110
|
+
return self.__execute_command(
|
|
111
|
+
self.__add_args(self.cmd_str, *args)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def __add_args(self, cmd_str: str, *args) -> str:
|
|
115
|
+
'''
|
|
116
|
+
Add arguments to command string
|
|
117
|
+
|
|
118
|
+
Returns a string with the command string
|
|
119
|
+
and arguments appended to it
|
|
120
|
+
|
|
121
|
+
e.g.
|
|
122
|
+
|
|
123
|
+
In:
|
|
124
|
+
|
|
125
|
+
Cmd().i.am.a.command.withh.arguments("arg1")
|
|
126
|
+
|
|
127
|
+
cmd_str = "i am a command withh arguments"
|
|
128
|
+
args = ("arg1",)
|
|
129
|
+
|
|
130
|
+
will return:
|
|
131
|
+
"i am a command withh arguments arg1"
|
|
132
|
+
'''
|
|
133
|
+
args_str = ' '.join(str(arg) for arg in args)
|
|
134
|
+
return f"{cmd_str} {args_str}"
|
|
135
|
+
|
|
136
|
+
def __execute_command(self, cmd_str: str) -> tuple[str, str]:
|
|
137
|
+
'''
|
|
138
|
+
Execute command in cmd_str
|
|
139
|
+
'''
|
|
140
|
+
Cmd._log.debug(cmd_str)
|
|
141
|
+
|
|
142
|
+
output = subprocess.run(
|
|
143
|
+
cmd_str,
|
|
144
|
+
capture_output=True,
|
|
145
|
+
shell=True, # There's more to this but the main
|
|
146
|
+
# reason is that shell=False will
|
|
147
|
+
# raise exceptions on non-zero exit codes
|
|
148
|
+
text=True,
|
|
149
|
+
check=False # Don't raise exception on non-zero exit code
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if output.stdout:
|
|
153
|
+
Cmd._log.info(output.stdout)
|
|
154
|
+
|
|
155
|
+
if output.stderr:
|
|
156
|
+
Cmd._log.error(output.stderr)
|
|
157
|
+
|
|
158
|
+
if output.returncode != 0:
|
|
159
|
+
raise NonZeroExit(
|
|
160
|
+
cmd_str,
|
|
161
|
+
output.returncode,
|
|
162
|
+
output.stdout,
|
|
163
|
+
output.stderr
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
stdout = output.stdout.strip() if output.stdout else ''
|
|
167
|
+
stderr = output.stderr.strip() if output.stderr else ''
|
|
168
|
+
|
|
169
|
+
return stdout, stderr
|
|
170
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Copyright (C) 2024 RomanLabs, Rafael Roman Otero
|
|
2
|
+
# This file is part of API Pipe.
|
|
3
|
+
#
|
|
4
|
+
# API Pipe is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# API Pipe is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
15
|
+
# along with API Pipe. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
'''
|
|
18
|
+
Tool
|
|
19
|
+
|
|
20
|
+
This file is used to configure the tool
|
|
21
|
+
'''
|
|
22
|
+
|
|
23
|
+
#logs
|
|
24
|
+
# (these are Paths not strings)
|
|
25
|
+
log_formatter = "%(message)s"
|
|
26
|
+
log_show_time = False
|
|
27
|
+
log_words_to_highlight = [
|
|
28
|
+
"importing",
|
|
29
|
+
"exporting",
|
|
30
|
+
"validating",
|
|
31
|
+
"deleting",
|
|
32
|
+
"getting",
|
|
33
|
+
"writing",
|
|
34
|
+
"reading",
|
|
35
|
+
"found",
|
|
36
|
+
"making",
|
|
37
|
+
"removing",
|
|
38
|
+
"selecting",
|
|
39
|
+
"emptying",
|
|
40
|
+
"initializing",
|
|
41
|
+
"logging",
|
|
42
|
+
"checking",
|
|
43
|
+
"requesting",
|
|
44
|
+
"imported",
|
|
45
|
+
"collecting",
|
|
46
|
+
"parsing",
|
|
47
|
+
"loading",
|
|
48
|
+
"cleaning",
|
|
49
|
+
"fetching",
|
|
50
|
+
"converting",
|
|
51
|
+
"created",
|
|
52
|
+
"Initialiazing",
|
|
53
|
+
"rendering",
|
|
54
|
+
"appending",
|
|
55
|
+
"processing",
|
|
56
|
+
"creating",
|
|
57
|
+
"created"
|
|
58
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Copyright (C) 2024 RomanLabs, Rafael Roman Otero
|
|
2
|
+
# This file is part of API Pipe.
|
|
3
|
+
#
|
|
4
|
+
# API Pipe is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# API Pipe is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
15
|
+
# along with API Pipe. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
'''
|
|
18
|
+
error.py
|
|
19
|
+
'''
|
|
20
|
+
from minishell import logger
|
|
21
|
+
|
|
22
|
+
log = logger.stdout(
|
|
23
|
+
__name__,
|
|
24
|
+
logger.logging.CRITICAL
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
class ArgumentError(TypeError):
|
|
28
|
+
'''
|
|
29
|
+
ArgumentError
|
|
30
|
+
|
|
31
|
+
Raised when an argument is invalid
|
|
32
|
+
'''
|
|
33
|
+
def __init__(self, msg: str):
|
|
34
|
+
|
|
35
|
+
super().__init__(msg)
|
|
36
|
+
|
|
37
|
+
class NonZeroExit(RuntimeError):
|
|
38
|
+
'''
|
|
39
|
+
CommandError
|
|
40
|
+
|
|
41
|
+
Raised when a command fails
|
|
42
|
+
'''
|
|
43
|
+
def __init__(self,
|
|
44
|
+
command: str,
|
|
45
|
+
return_code: int,
|
|
46
|
+
stdout: str,
|
|
47
|
+
stderr: str
|
|
48
|
+
):
|
|
49
|
+
if stdout:
|
|
50
|
+
stdout = f"\n{stdout}"
|
|
51
|
+
if stderr:
|
|
52
|
+
stderr = f"\n{stderr}"
|
|
53
|
+
|
|
54
|
+
super().__init__(
|
|
55
|
+
f"Command '{command}' failed with return code {return_code}:"
|
|
56
|
+
f"{stdout}"
|
|
57
|
+
f"{stderr}"
|
|
58
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Copyright (C) 2024 RomanLabs, Rafael Roman Otero
|
|
2
|
+
# This file is part of API Pipe.
|
|
3
|
+
#
|
|
4
|
+
# API Pipe is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# API Pipe is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
15
|
+
# along with API Pipe. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
'''
|
|
18
|
+
logger.py
|
|
19
|
+
'''
|
|
20
|
+
import logging
|
|
21
|
+
from rich.logging import RichHandler
|
|
22
|
+
from rich.traceback import install
|
|
23
|
+
|
|
24
|
+
from minishell import config
|
|
25
|
+
|
|
26
|
+
def enable_pretty_tracebacks() -> None:
|
|
27
|
+
'''
|
|
28
|
+
Enable Pretty Traceback
|
|
29
|
+
|
|
30
|
+
Uses rich to print pretty tracebacks
|
|
31
|
+
'''
|
|
32
|
+
install()
|
|
33
|
+
|
|
34
|
+
def stdout(name: str, log_level: int) -> logging.Logger:
|
|
35
|
+
'''
|
|
36
|
+
Sets up a logger that logs to stdout
|
|
37
|
+
|
|
38
|
+
Uses RichHandler to pretty print logs
|
|
39
|
+
'''
|
|
40
|
+
words_to_highlight = config.log_words_to_highlight
|
|
41
|
+
|
|
42
|
+
handler = RichHandler(
|
|
43
|
+
show_time=config.log_show_time,
|
|
44
|
+
keywords=[w.lower() for w in words_to_highlight] + \
|
|
45
|
+
[w.upper() for w in words_to_highlight] + \
|
|
46
|
+
[w.capitalize() for w in words_to_highlight]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
logger = CustomLogger(name)
|
|
50
|
+
logger.setLevel(log_level)
|
|
51
|
+
formatter = logging.Formatter(config.log_formatter)
|
|
52
|
+
handler.setFormatter(formatter)
|
|
53
|
+
logger.addHandler(handler)
|
|
54
|
+
|
|
55
|
+
return logger
|
|
56
|
+
|
|
57
|
+
class CustomLogger(logging.Logger):
|
|
58
|
+
'''
|
|
59
|
+
Extends the logging.Logger class
|
|
60
|
+
with custom methods
|
|
61
|
+
'''
|
|
62
|
+
def flush(self) -> None:
|
|
63
|
+
'''
|
|
64
|
+
Flush
|
|
65
|
+
|
|
66
|
+
Flushes the logger
|
|
67
|
+
'''
|
|
68
|
+
for handler in self.handlers:
|
|
69
|
+
handler.flush()
|
|
70
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright (C) 2024 RomanLabs, Rafael Roman Otero
|
|
2
|
+
# This file is part of API Pipe.
|
|
3
|
+
#
|
|
4
|
+
# API Pipe is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# API Pipe is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Lesser General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
15
|
+
# along with API Pipe. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
'''
|
|
18
|
+
Run Manual Test
|
|
19
|
+
(entry point)
|
|
20
|
+
|
|
21
|
+
For help type:
|
|
22
|
+
poetry run manual-test-run --help
|
|
23
|
+
'''
|
|
24
|
+
import logging
|
|
25
|
+
|
|
26
|
+
from minishell.cmd import Cmd
|
|
27
|
+
from minishell import logger
|
|
28
|
+
|
|
29
|
+
log = logger.stdout(
|
|
30
|
+
__name__,
|
|
31
|
+
logging.DEBUG
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
'''
|
|
36
|
+
main
|
|
37
|
+
'''
|
|
38
|
+
Cmd.date()
|
|
39
|
+
Cmd.uname("-s")
|
|
40
|
+
Cmd.ipconfig.getifaddr.en0()
|
|
41
|
+
Cmd().ls(
|
|
42
|
+
"-l",
|
|
43
|
+
"tests/integration/ls_test_dir/file"
|
|
44
|
+
)
|
|
45
|
+
Cmd().cat(
|
|
46
|
+
"tests/integration/ls_test_dir/file"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
stdout, _ = Cmd().ipconfig.getifaddr.en0()
|
|
50
|
+
print("My IP Address is: ", stdout)
|