nercone-shell 0.2.5__py3-none-any.whl
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.
|
File without changes
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# -- nercone-shell ---------------------------------------------- #
|
|
4
|
+
# __main__.py on Nersh #
|
|
5
|
+
# Made by DiamondGotCat, Licensed under MIT License #
|
|
6
|
+
# Copyright (c) 2025 DiamondGotCat #
|
|
7
|
+
# ---------------------------------------------- DiamondGotCat -- #
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import re
|
|
12
|
+
import json
|
|
13
|
+
import glob
|
|
14
|
+
import shutil
|
|
15
|
+
import getpass
|
|
16
|
+
import readline
|
|
17
|
+
import subprocess
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from nercone_modern.color import ModernColor
|
|
20
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
VERSION: str = version("nercone-shell")
|
|
24
|
+
except PackageNotFoundError:
|
|
25
|
+
VERSION: str = "0.0.0"
|
|
26
|
+
ENVIRONMENT: dict = {}
|
|
27
|
+
NERSH_AUTORUN: str = os.environ.get("NERSH_AUTORUN", None)
|
|
28
|
+
NERSH_PATH = Path(os.environ.get("NERSH_PATH", str(Path(Path("~").expanduser(), ".nercone", "nercone-shell"))))
|
|
29
|
+
NERSH_HISTORY_PATH = Path(os.environ.get("NERSH_HISTORY_PATH", str(Path(NERSH_PATH, "history.txt"))))
|
|
30
|
+
NERSH_CONFIG: dict = {}
|
|
31
|
+
NERSH_CONFIG_PATH = Path(os.environ.get("NERSH_CONFIG_PATH", str(Path(NERSH_PATH, "config.json"))))
|
|
32
|
+
NERSH_CONFIG_DEFAULT: dict = {
|
|
33
|
+
"show_version": True,
|
|
34
|
+
"override_env": {
|
|
35
|
+
"SHELL": f"{shutil.which('nersh')}"
|
|
36
|
+
},
|
|
37
|
+
"autoruns": [
|
|
38
|
+
f"{Path(NERSH_PATH, 'autostart.sh')}"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class NershCompleter:
|
|
43
|
+
def __init__(self):
|
|
44
|
+
self.matches = []
|
|
45
|
+
|
|
46
|
+
def complete(self, text, state):
|
|
47
|
+
if state == 0:
|
|
48
|
+
full_line = readline.get_line_buffer()
|
|
49
|
+
line_to_cursor = full_line[:readline.get_begidx()]
|
|
50
|
+
if not line_to_cursor.strip() and ' ' not in text:
|
|
51
|
+
self.matches = self._complete_command(text)
|
|
52
|
+
else:
|
|
53
|
+
self.matches = self._complete_path(text)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
return self.matches[state]
|
|
57
|
+
except IndexError:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def _complete_command(self, text):
|
|
61
|
+
results = set()
|
|
62
|
+
|
|
63
|
+
builtins = ['cd', 'exit']
|
|
64
|
+
for cmd in builtins:
|
|
65
|
+
if cmd.startswith(text):
|
|
66
|
+
results.add(cmd + " ")
|
|
67
|
+
|
|
68
|
+
path_dirs = os.environ.get("PATH", "").split(os.pathsep)
|
|
69
|
+
for p in path_dirs:
|
|
70
|
+
if not os.path.isdir(p):
|
|
71
|
+
continue
|
|
72
|
+
try:
|
|
73
|
+
for filename in os.listdir(p):
|
|
74
|
+
if filename.startswith(text):
|
|
75
|
+
results.add(filename + " ")
|
|
76
|
+
except (PermissionError, OSError):
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
return sorted(list(results))
|
|
80
|
+
|
|
81
|
+
def _complete_path(self, text):
|
|
82
|
+
expanded_text = os.path.expanduser(text)
|
|
83
|
+
|
|
84
|
+
current_pwd = ENVIRONMENT.get("PWD", os.getcwd())
|
|
85
|
+
|
|
86
|
+
if os.path.isabs(expanded_text):
|
|
87
|
+
search_pattern = expanded_text + "*"
|
|
88
|
+
else:
|
|
89
|
+
search_pattern = os.path.join(current_pwd, expanded_text + "*")
|
|
90
|
+
|
|
91
|
+
glob_matches = glob.glob(search_pattern)
|
|
92
|
+
|
|
93
|
+
results = []
|
|
94
|
+
for match in glob_matches:
|
|
95
|
+
if os.path.isdir(match):
|
|
96
|
+
display_match = match + "/"
|
|
97
|
+
else:
|
|
98
|
+
display_match = match + " "
|
|
99
|
+
|
|
100
|
+
if not os.path.isabs(expanded_text) and not text.startswith("~"):
|
|
101
|
+
rel_path = os.path.relpath(match, current_pwd)
|
|
102
|
+
if os.path.isdir(match):
|
|
103
|
+
rel_path += "/"
|
|
104
|
+
else:
|
|
105
|
+
rel_path += " "
|
|
106
|
+
results.append(rel_path)
|
|
107
|
+
elif text.startswith("~"):
|
|
108
|
+
home = os.path.expanduser("~")
|
|
109
|
+
if match.startswith(home):
|
|
110
|
+
rel_home = "~" + match[len(home):]
|
|
111
|
+
if os.path.isdir(match):
|
|
112
|
+
rel_home += "/"
|
|
113
|
+
else:
|
|
114
|
+
rel_home += " "
|
|
115
|
+
results.append(rel_home)
|
|
116
|
+
else:
|
|
117
|
+
results.append(display_match)
|
|
118
|
+
else:
|
|
119
|
+
results.append(display_match)
|
|
120
|
+
|
|
121
|
+
return sorted(results)
|
|
122
|
+
|
|
123
|
+
def shorten_path(path: str) -> str:
|
|
124
|
+
path = os.path.abspath(path)
|
|
125
|
+
home = os.path.expanduser('~')
|
|
126
|
+
if path.startswith(home):
|
|
127
|
+
rel = os.path.relpath(path, home)
|
|
128
|
+
if rel == ".":
|
|
129
|
+
return "~"
|
|
130
|
+
parts = rel.split("/")
|
|
131
|
+
prefix = "~"
|
|
132
|
+
else:
|
|
133
|
+
parts = path.strip("/").split("/")
|
|
134
|
+
prefix = "/"
|
|
135
|
+
if len(parts) > 1:
|
|
136
|
+
shortened = [p[0] for p in parts[:-1]]
|
|
137
|
+
shortened.append(parts[-1])
|
|
138
|
+
path_str = "/".join(shortened)
|
|
139
|
+
elif parts:
|
|
140
|
+
path_str = parts[0]
|
|
141
|
+
else:
|
|
142
|
+
path_str = ""
|
|
143
|
+
if prefix == "~":
|
|
144
|
+
return f"~/{path_str}"
|
|
145
|
+
elif prefix == "/":
|
|
146
|
+
return f"/{path_str}"
|
|
147
|
+
return path_str
|
|
148
|
+
|
|
149
|
+
def show_version():
|
|
150
|
+
print(f"Nersh v{VERSION}")
|
|
151
|
+
|
|
152
|
+
def reset():
|
|
153
|
+
global ENVIRONMENT, NERSH_CONFIG
|
|
154
|
+
ENVIRONMENT = {"PWD": f"{Path(Path("~").expanduser())}"}
|
|
155
|
+
NERSH_CONFIG = {}
|
|
156
|
+
reload()
|
|
157
|
+
|
|
158
|
+
def reload():
|
|
159
|
+
global ENVIRONMENT
|
|
160
|
+
load_config()
|
|
161
|
+
ENVIRONMENT |= os.environ
|
|
162
|
+
ENVIRONMENT |= NERSH_CONFIG.get("override_env", {})
|
|
163
|
+
|
|
164
|
+
def load_config() -> dict:
|
|
165
|
+
global NERSH_CONFIG
|
|
166
|
+
NERSH_PATH.mkdir(parents=True, exist_ok=True)
|
|
167
|
+
if not NERSH_CONFIG_PATH.is_file():
|
|
168
|
+
with NERSH_CONFIG_PATH.open("w") as f:
|
|
169
|
+
f.write(json.dumps(NERSH_CONFIG_DEFAULT, indent=4) + "\n")
|
|
170
|
+
with NERSH_CONFIG_PATH.open("r") as f:
|
|
171
|
+
NERSH_CONFIG |= json.loads(f.read())
|
|
172
|
+
for p in NERSH_CONFIG.get("autoruns", []):
|
|
173
|
+
if not Path(p).is_file():
|
|
174
|
+
with Path(p).open("w") as f:
|
|
175
|
+
f.write("\n")
|
|
176
|
+
return NERSH_CONFIG
|
|
177
|
+
|
|
178
|
+
def run_line(command: str) -> int:
|
|
179
|
+
def expand_vars(match):
|
|
180
|
+
key = match.group(1) or match.group(2)
|
|
181
|
+
return ENVIRONMENT.get(key, "")
|
|
182
|
+
if command.strip():
|
|
183
|
+
command = re.sub(r'\$(\w+)|\$\{(\w+)\}', expand_vars, command)
|
|
184
|
+
args = command.strip().split(" ")
|
|
185
|
+
cmd = args[0]
|
|
186
|
+
if cmd == "version":
|
|
187
|
+
show_version()
|
|
188
|
+
elif cmd == "reset":
|
|
189
|
+
reset()
|
|
190
|
+
elif cmd == "reload":
|
|
191
|
+
reload()
|
|
192
|
+
elif cmd == "cd":
|
|
193
|
+
target = " ".join(args[1:])
|
|
194
|
+
if not target:
|
|
195
|
+
ENVIRONMENT["PWD"] = f"{Path("~").expanduser()}"
|
|
196
|
+
target = os.path.expanduser(target)
|
|
197
|
+
target_path = Path(target)
|
|
198
|
+
if not target_path.is_absolute():
|
|
199
|
+
target_path = Path(ENVIRONMENT["PWD"]) / target_path
|
|
200
|
+
try:
|
|
201
|
+
resolved_path = target_path.resolve()
|
|
202
|
+
if resolved_path.is_dir():
|
|
203
|
+
ENVIRONMENT["PWD"] = str(resolved_path)
|
|
204
|
+
elif resolved_path.exists():
|
|
205
|
+
print(f"Not a directory: {target}")
|
|
206
|
+
else:
|
|
207
|
+
print(f"Not exist: {target}")
|
|
208
|
+
except FileNotFoundError:
|
|
209
|
+
print(f"Not exist: {target}")
|
|
210
|
+
elif cmd == "export":
|
|
211
|
+
content = command.strip()[6:].strip()
|
|
212
|
+
if "=" in content:
|
|
213
|
+
key, value = content.split("=", 1)
|
|
214
|
+
if len(value) >= 2 and ((value[0] == '"' and value[-1] == '"') or (value[0] == "'" and value[-1] == "'")):
|
|
215
|
+
value = value[1:-1]
|
|
216
|
+
ENVIRONMENT[key] = value
|
|
217
|
+
else:
|
|
218
|
+
if not content:
|
|
219
|
+
for k, v in ENVIRONMENT.items():
|
|
220
|
+
print(f"{k}={v}")
|
|
221
|
+
elif cmd == "source" or cmd == ".":
|
|
222
|
+
if len(args) < 2:
|
|
223
|
+
print(f"{cmd}: filename argument required")
|
|
224
|
+
return 1
|
|
225
|
+
target = args[1]
|
|
226
|
+
target_path = Path(os.path.expanduser(target))
|
|
227
|
+
if not target_path.is_absolute():
|
|
228
|
+
target_path = Path(ENVIRONMENT.get("PWD")) / target_path
|
|
229
|
+
if target_path.is_file():
|
|
230
|
+
try:
|
|
231
|
+
with target_path.open("r") as f:
|
|
232
|
+
for line in f:
|
|
233
|
+
if line.strip() and not line.strip().startswith("#"):
|
|
234
|
+
run_line(line)
|
|
235
|
+
except Exception as e:
|
|
236
|
+
print(f"nersh: {e}")
|
|
237
|
+
return 1
|
|
238
|
+
else:
|
|
239
|
+
print(f"nersh: {target}: No such file")
|
|
240
|
+
return 1
|
|
241
|
+
elif cmd == "exit":
|
|
242
|
+
readline.write_history_file(str(NERSH_HISTORY_PATH))
|
|
243
|
+
try:
|
|
244
|
+
raise SystemExit(int(args[1]))
|
|
245
|
+
except IndexError:
|
|
246
|
+
raise SystemExit(0)
|
|
247
|
+
else:
|
|
248
|
+
process = subprocess.run(command, cwd=ENVIRONMENT.get("PWD", None), env=ENVIRONMENT, shell=True, encoding="utf-8", stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
|
|
249
|
+
return process.returncode
|
|
250
|
+
|
|
251
|
+
def run_script(script: str):
|
|
252
|
+
for line in script.split('\n'):
|
|
253
|
+
run_line(line)
|
|
254
|
+
|
|
255
|
+
def main() -> int:
|
|
256
|
+
reset()
|
|
257
|
+
completer = NershCompleter()
|
|
258
|
+
readline.set_completer(completer.complete)
|
|
259
|
+
if 'libedit' in readline.__doc__:
|
|
260
|
+
readline.parse_and_bind("bind ^I rl_complete")
|
|
261
|
+
else:
|
|
262
|
+
readline.parse_and_bind("tab: complete")
|
|
263
|
+
readline.set_completer_delims(' \t\n;')
|
|
264
|
+
if NERSH_CONFIG.get("show_version", True):
|
|
265
|
+
show_version()
|
|
266
|
+
for p in NERSH_CONFIG.get("autoruns", []):
|
|
267
|
+
if Path(p).is_file():
|
|
268
|
+
with Path(p).open("r") as f:
|
|
269
|
+
run_script(f.read())
|
|
270
|
+
elif Path(p).exists():
|
|
271
|
+
print(f"(autorun failed) Not a file: {p}")
|
|
272
|
+
else:
|
|
273
|
+
print(f"(autorun failed) Not exist: {p}")
|
|
274
|
+
if NERSH_AUTORUN:
|
|
275
|
+
run_script(NERSH_AUTORUN)
|
|
276
|
+
if NERSH_HISTORY_PATH.is_file():
|
|
277
|
+
readline.read_history_file(NERSH_HISTORY_PATH)
|
|
278
|
+
while True:
|
|
279
|
+
try:
|
|
280
|
+
print(f"{ModernColor.color('green')}{getpass.getuser()}{ModernColor.color('reset')}@{os.uname()[1].rsplit('.', 1)[0]} {ModernColor.color('green')}{shorten_path(ENVIRONMENT.get('PWD', f'{Path('~').expanduser()}'))}{ModernColor.color('reset')}> ", end="")
|
|
281
|
+
run_line(input())
|
|
282
|
+
except KeyboardInterrupt:
|
|
283
|
+
print()
|
|
284
|
+
continue
|
|
285
|
+
except EOFError:
|
|
286
|
+
print()
|
|
287
|
+
break
|
|
288
|
+
|
|
289
|
+
if __name__ == "__main__":
|
|
290
|
+
main()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: nercone-shell
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Modern shell for Developers
|
|
5
|
+
Author: Nercone
|
|
6
|
+
Author-email: Nercone <nercone@diamondgotcat.net>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Dist: nercone-modern
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
<img width="1710" alt="Screenshot" src="https://github.com/user-attachments/assets/a717ebd3-c9d4-447c-86a5-7d33eea9c028" />
|
|
17
|
+
|
|
18
|
+
# Nersh (Nercone Shell)
|
|
19
|
+
Modern shell for Developers
|
|
20
|
+
|
|
21
|
+
## Requiments
|
|
22
|
+
- CPython 3.9+
|
|
23
|
+
- `uv` [PyPI↗︎](https://pypi.org/project/uv/) or `pip3` [PyPI↗︎](https://pypi.org/project/pip/)
|
|
24
|
+
- `nercone-modern` [PyPI↗︎](https://pypi.org/project/nercone-modern/)
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### using uv (recommended)
|
|
29
|
+
```
|
|
30
|
+
uv tool install nercone-shell
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### using pip3
|
|
34
|
+
|
|
35
|
+
**System Python:**
|
|
36
|
+
```
|
|
37
|
+
pip3 install nercone-shell --break-system-packages
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Venv Python:**
|
|
41
|
+
```
|
|
42
|
+
pip3 install nercone-shell
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Update
|
|
46
|
+
|
|
47
|
+
### using uv (recommended)
|
|
48
|
+
```
|
|
49
|
+
uv tool install nercone-shell --upgrade
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### using pip3
|
|
53
|
+
|
|
54
|
+
**System Python:**
|
|
55
|
+
```
|
|
56
|
+
pip3 install nercone-shell --upgrade --break-system-packages
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Venv Python:**
|
|
60
|
+
```
|
|
61
|
+
pip3 install nercone-shell --upgrade
|
|
62
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
nercone_shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
nercone_shell/__main__.py,sha256=lAsnlCqUU4jQecJdUiPU0eXAm5zd08Jv3JweHXfmpag,9782
|
|
3
|
+
nercone_shell-0.2.5.dist-info/WHEEL,sha256=YUH1mBqsx8Dh2cQG2rlcuRYUhJddG9iClegy4IgnHik,79
|
|
4
|
+
nercone_shell-0.2.5.dist-info/entry_points.txt,sha256=STrGlvrPc2Rr5ktWVfiZ_5Lr7i1GcfOMvsTxL6768sw,55
|
|
5
|
+
nercone_shell-0.2.5.dist-info/METADATA,sha256=Uj41oUNZ0aTRkwsnVkek72FcXJzbh269pMabAQtHvg8,1286
|
|
6
|
+
nercone_shell-0.2.5.dist-info/RECORD,,
|