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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.11
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ nersh = nercone_shell.__main__:main
3
+