flyshell 1.0.10__tar.gz → 1.0.12__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.
- {flyshell-1.0.10/src/flyshell.egg-info → flyshell-1.0.12}/PKG-INFO +1 -1
- flyshell-1.0.12/src/flyshell/builtin/diskops.py +510 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/constants.py +2 -2
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/core/directory.py +4 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/main.py +12 -7
- {flyshell-1.0.10 → flyshell-1.0.12/src/flyshell.egg-info}/PKG-INFO +1 -1
- flyshell-1.0.10/src/flyshell/builtin/diskops.py +0 -211
- {flyshell-1.0.10 → flyshell-1.0.12}/LICENSE +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/README.md +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/pyproject.toml +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/setup.cfg +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/__init__.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/abstract/__init__.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/abstract/base_cmd.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/abstract/base_plugin.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/abstract/parent_cmd.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/builtin/__init__.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/builtin/fs.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/builtin/system.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/builtin/utils.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/core/__init__.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/core/auth.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/core/console.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/core/data.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell/core/loader.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell.egg-info/SOURCES.txt +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell.egg-info/dependency_links.txt +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell.egg-info/entry_points.txt +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell.egg-info/requires.txt +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/src/flyshell.egg-info/top_level.txt +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/tests/test_auth.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/tests/test_data.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/tests/test_directory.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/tests/test_loader.py +0 -0
- {flyshell-1.0.10 → flyshell-1.0.12}/tests/test_security.py +0 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# src\flyshell\builtin\diskops.py
|
|
2
|
+
|
|
3
|
+
from flyshell import constants as c
|
|
4
|
+
from flyshell.abstract.base_cmd import BaseCmd
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Cat(BaseCmd):
|
|
12
|
+
name = "cat"
|
|
13
|
+
description = "Display the contents of a text file"
|
|
14
|
+
min_args = 1
|
|
15
|
+
options = "Target file"
|
|
16
|
+
aliases = ["read"]
|
|
17
|
+
|
|
18
|
+
def execute(self, args):
|
|
19
|
+
show_numbers = ("-n" in args) or ("--number" in args)
|
|
20
|
+
limit = None
|
|
21
|
+
filtered_args = []
|
|
22
|
+
skip_next = False
|
|
23
|
+
for i, token in enumerate(args):
|
|
24
|
+
if skip_next:
|
|
25
|
+
skip_next = False
|
|
26
|
+
continue
|
|
27
|
+
if token in ("-n", "--number"):
|
|
28
|
+
continue
|
|
29
|
+
if token in ("-l", "--limit"):
|
|
30
|
+
if i + 1 < len(args) and args[i + 1].isdigit():
|
|
31
|
+
limit = int(args[i + 1])
|
|
32
|
+
skip_next = True
|
|
33
|
+
continue
|
|
34
|
+
else:
|
|
35
|
+
return (
|
|
36
|
+
1,
|
|
37
|
+
f"\nCommand Error: '{token}' requires a positive integer limit.\n",
|
|
38
|
+
)
|
|
39
|
+
filtered_args.append(token)
|
|
40
|
+
if not filtered_args:
|
|
41
|
+
return (1, "\nCommand Error: Target file not specified.\n")
|
|
42
|
+
raw_target = " ".join(filtered_args).strip("\"'")
|
|
43
|
+
target_path = Path(raw_target).expanduser().resolve()
|
|
44
|
+
if not target_path.exists():
|
|
45
|
+
return (1, f"\nSystem Error: File '{raw_target}' not found.\n")
|
|
46
|
+
if target_path.is_dir():
|
|
47
|
+
return (1, f"\nSystem Error: '{raw_target}' is a directory, not a file.\n")
|
|
48
|
+
try:
|
|
49
|
+
with open(target_path, "r", encoding="utf-8", errors="replace") as f:
|
|
50
|
+
lines = f.readlines()
|
|
51
|
+
total_lines = len(lines)
|
|
52
|
+
if total_lines == 0:
|
|
53
|
+
return (1, f"\nSystem Error: '{raw_target}' is empty.\n")
|
|
54
|
+
if limit is not None:
|
|
55
|
+
lines = lines[:limit]
|
|
56
|
+
output_lines = [
|
|
57
|
+
f"\nDisplaying contents of file '{target_path.name}' ({len(lines)}/{total_lines} lines)\n"
|
|
58
|
+
]
|
|
59
|
+
for line_idx, line in enumerate(lines, start=1):
|
|
60
|
+
clean_line = line.rstrip("\r\n")
|
|
61
|
+
if show_numbers:
|
|
62
|
+
output_lines.append(f"{line_idx:4d} | {clean_line}")
|
|
63
|
+
else:
|
|
64
|
+
output_lines.append(clean_line)
|
|
65
|
+
output_lines.append(f"\nEnd of '{target_path.name}'\n")
|
|
66
|
+
return (0, "\n".join(output_lines))
|
|
67
|
+
except PermissionError:
|
|
68
|
+
return (
|
|
69
|
+
1,
|
|
70
|
+
f"\nSystem Error: Permission denied. Cannot read '{raw_target}'\n",
|
|
71
|
+
)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return (1, f"\nSystem Error: Failed to read file '{raw_target}' ({e})\n")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Copy(BaseCmd):
|
|
77
|
+
name = "cp"
|
|
78
|
+
description = "Copy files or directories to a new destination"
|
|
79
|
+
min_args = 2
|
|
80
|
+
options = "Source, then destination"
|
|
81
|
+
aliases = ["copy"]
|
|
82
|
+
|
|
83
|
+
def execute(self, args):
|
|
84
|
+
force = ("-f" in args) or ("--force" in args)
|
|
85
|
+
flags = {"-f", "--force"}
|
|
86
|
+
path_tokens = [arg for arg in args if arg not in flags]
|
|
87
|
+
if len(path_tokens) < 2:
|
|
88
|
+
return (
|
|
89
|
+
1,
|
|
90
|
+
"\nCommand Error: 'cp' requires both a source and destination path.\n",
|
|
91
|
+
)
|
|
92
|
+
raw_src = path_tokens[0].strip("\"'")
|
|
93
|
+
raw_dst = path_tokens[1].strip("\"'")
|
|
94
|
+
src_path = Path(raw_src).expanduser().resolve()
|
|
95
|
+
dst_path = Path(raw_dst).expanduser().resolve()
|
|
96
|
+
if not src_path.exists():
|
|
97
|
+
return (1, f"\nSystem Error: Source '{raw_src}' not found.\n")
|
|
98
|
+
if dst_path.exists() and dst_path.is_dir():
|
|
99
|
+
final_dst = dst_path / src_path.name
|
|
100
|
+
else:
|
|
101
|
+
final_dst = dst_path
|
|
102
|
+
if final_dst.exists() and not force:
|
|
103
|
+
try:
|
|
104
|
+
confirm = (
|
|
105
|
+
input(
|
|
106
|
+
f"\nDestination '{final_dst.name}' already exists. Overwrite? [y/n]: "
|
|
107
|
+
)
|
|
108
|
+
.strip()
|
|
109
|
+
.lower()
|
|
110
|
+
)
|
|
111
|
+
except KeyboardInterrupt:
|
|
112
|
+
return (1, "\nCopy cancelled by user.\n")
|
|
113
|
+
if confirm != "y":
|
|
114
|
+
return (0, "\nOperation cancelled. No files were copied.\n")
|
|
115
|
+
try:
|
|
116
|
+
if src_path.is_dir():
|
|
117
|
+
shutil.copytree(src_path, final_dst, dirs_exist_ok=force)
|
|
118
|
+
return (
|
|
119
|
+
0,
|
|
120
|
+
f"\nSUCCESS! Copied directory '{src_path.name}' -> '{final_dst}'\n",
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
shutil.copy2(src_path, final_dst)
|
|
124
|
+
return (
|
|
125
|
+
0,
|
|
126
|
+
f"\nSUCCESS! Copied file '{src_path.name}' -> '{final_dst}'\n",
|
|
127
|
+
)
|
|
128
|
+
except PermissionError:
|
|
129
|
+
return (
|
|
130
|
+
1,
|
|
131
|
+
f"\nSystem Error: Permission denied. Cannot copy to '{final_dst}'\n",
|
|
132
|
+
)
|
|
133
|
+
except shutil.SameFileError:
|
|
134
|
+
return (1, "\nSystem Error: Source and destination are the same.\n")
|
|
135
|
+
except Exception as e:
|
|
136
|
+
return (1, f"\nSystem Error: Failed to copy '{raw_src}' ({e})\n")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Dir(BaseCmd):
|
|
140
|
+
name = "dir"
|
|
141
|
+
description = "List all files in the current working directory"
|
|
142
|
+
min_args = 0
|
|
143
|
+
options = None
|
|
144
|
+
aliases = ["ls"]
|
|
145
|
+
|
|
146
|
+
def execute(self, args):
|
|
147
|
+
try:
|
|
148
|
+
current_path = os.getcwd()
|
|
149
|
+
items = os.listdir(current_path)
|
|
150
|
+
lines = [f"\nDirectory: '{current_path}'"]
|
|
151
|
+
for item in items:
|
|
152
|
+
if os.path.isdir(os.path.join(current_path, item)):
|
|
153
|
+
lines.append(f"[DIR] {item}/")
|
|
154
|
+
else:
|
|
155
|
+
lines.append(f"[FILE] {item}")
|
|
156
|
+
lines.append(f"\nTotal items in directory: {len(items)}\n")
|
|
157
|
+
return (0, "\n".join(lines))
|
|
158
|
+
except Exception as e:
|
|
159
|
+
return (1, f"\nSystem Error: Unable to list directory ({e})\n")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class MkDir(BaseCmd):
|
|
163
|
+
name = "mkdir"
|
|
164
|
+
description = "Create a new directory from the current working directory"
|
|
165
|
+
min_args = 1
|
|
166
|
+
options = "Directory name"
|
|
167
|
+
aliases = ["md"]
|
|
168
|
+
|
|
169
|
+
def execute(self, args):
|
|
170
|
+
use_parents = ("-p" in args) or ("--parents" in args)
|
|
171
|
+
path_tokens = [arg for arg in args if arg not in ("-p", "--parents")]
|
|
172
|
+
target = " ".join(path_tokens).strip("\"'")
|
|
173
|
+
try:
|
|
174
|
+
if use_parents:
|
|
175
|
+
os.makedirs(target, exist_ok=True)
|
|
176
|
+
else:
|
|
177
|
+
os.mkdir(target)
|
|
178
|
+
return (0, f"\nSUCCESS! Created directory '{target}'\n")
|
|
179
|
+
except FileExistsError:
|
|
180
|
+
return (1, f"\nSystem Error: Directory '{target}' already exists.\n")
|
|
181
|
+
except FileNotFoundError:
|
|
182
|
+
return (
|
|
183
|
+
1,
|
|
184
|
+
"\nSystem Error: Parent directory does not exist. Use '-p' or '--parents' to create parent directories.\n",
|
|
185
|
+
)
|
|
186
|
+
except PermissionError:
|
|
187
|
+
return (1, f"\nSystem Error: Permission denied. Cannot create '{target}'\n")
|
|
188
|
+
except Exception as e:
|
|
189
|
+
return (1, f"\nSystem Error: Unable to create directory '{target}' ({e})\n")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Move(BaseCmd):
|
|
193
|
+
name = "mv"
|
|
194
|
+
description = "Move or relocate files and directories"
|
|
195
|
+
min_args = 2
|
|
196
|
+
options = "Source, then destination"
|
|
197
|
+
aliases = ["move"]
|
|
198
|
+
|
|
199
|
+
def execute(self, args):
|
|
200
|
+
force = ("-f" in args) or ("--force" in args)
|
|
201
|
+
flags = {"-f", "--force"}
|
|
202
|
+
path_tokens = [arg for arg in args if arg not in flags]
|
|
203
|
+
if len(path_tokens) < 2:
|
|
204
|
+
return (
|
|
205
|
+
1,
|
|
206
|
+
"\nCommand Error: 'mv' requires both a source and a destination path.\n",
|
|
207
|
+
)
|
|
208
|
+
raw_src = path_tokens[0].strip("\"'")
|
|
209
|
+
raw_dst = path_tokens[1].strip("\"'")
|
|
210
|
+
src_path = Path(raw_src).expanduser().resolve()
|
|
211
|
+
dst_path = Path(raw_dst).expanduser().resolve()
|
|
212
|
+
if not src_path.exists():
|
|
213
|
+
return (1, f"\nSystem Error: Source '{raw_src}' not found.\n")
|
|
214
|
+
if src_path == src_path.parent or str(src_path) == src_path.anchor:
|
|
215
|
+
return (
|
|
216
|
+
1,
|
|
217
|
+
"\nSecurity Error: Flyshell has blocked moving the root filesystem.\n",
|
|
218
|
+
)
|
|
219
|
+
flyshell_dirs = [
|
|
220
|
+
c.APP_DIR.resolve(),
|
|
221
|
+
c.PACKAGE_ROOT.resolve(),
|
|
222
|
+
c.PROJECT_ROOT.resolve(),
|
|
223
|
+
]
|
|
224
|
+
for protected in flyshell_dirs:
|
|
225
|
+
if src_path == protected or src_path.is_relative_to(protected):
|
|
226
|
+
return (
|
|
227
|
+
1,
|
|
228
|
+
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
229
|
+
f"Source '{raw_src}' contains or belongs to Flyshell system files.\n",
|
|
230
|
+
)
|
|
231
|
+
if dst_path.exists() and dst_path.is_dir():
|
|
232
|
+
final_dst = dst_path / src_path.name
|
|
233
|
+
else:
|
|
234
|
+
final_dst = dst_path
|
|
235
|
+
if final_dst.exists() and not force:
|
|
236
|
+
try:
|
|
237
|
+
confirm = (
|
|
238
|
+
input(
|
|
239
|
+
f"\nDestination '{final_dst.name}' already exists. Overwrite? [y/n]: "
|
|
240
|
+
)
|
|
241
|
+
.strip()
|
|
242
|
+
.lower()
|
|
243
|
+
)
|
|
244
|
+
except KeyboardInterrupt:
|
|
245
|
+
return (1, "\nMove cancelled by user.\n")
|
|
246
|
+
if confirm != "y":
|
|
247
|
+
return (0, "\nOperation cancelled. No files were moved.\n")
|
|
248
|
+
try:
|
|
249
|
+
shutil.move(str(src_path), str(final_dst))
|
|
250
|
+
return (0, f"\nSUCCESS! Moved '{src_path.name}' -> '{final_dst}'\n")
|
|
251
|
+
except PermissionError:
|
|
252
|
+
return (
|
|
253
|
+
1,
|
|
254
|
+
f"\nSystem Error: Permission denied. Cannot move to '{final_dst}'\n",
|
|
255
|
+
)
|
|
256
|
+
except shutil.SameFileError:
|
|
257
|
+
return (1, "\nSystem Error: Source and destination are the same.\n")
|
|
258
|
+
except Exception as e:
|
|
259
|
+
return (1, f"\nSystem Error: Failed to move '{raw_src}' ({e})\n")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class Rm(BaseCmd):
|
|
263
|
+
name = "rm"
|
|
264
|
+
description = "Remove files from the filesystem"
|
|
265
|
+
min_args = 1
|
|
266
|
+
options = "Target file"
|
|
267
|
+
aliases = ["del"]
|
|
268
|
+
|
|
269
|
+
def execute(self, args):
|
|
270
|
+
force = ("-f" in args) or ("--force" in args)
|
|
271
|
+
recursive = ("-r" in args) or ("--recursive" in args)
|
|
272
|
+
flags = {"-f", "--force", "-r", "--recursive"}
|
|
273
|
+
path_tokens = [arg for arg in args if arg not in flags]
|
|
274
|
+
if not path_tokens:
|
|
275
|
+
return (1, "\nCommand Error: No target file or directory specified.\n")
|
|
276
|
+
raw_target = " ".join(path_tokens).strip("\"'")
|
|
277
|
+
target_path = Path(raw_target).expanduser().resolve()
|
|
278
|
+
if not target_path.exists():
|
|
279
|
+
return (1, f"\nSystem Error: Target '{raw_target}' not found.\n")
|
|
280
|
+
if target_path == target_path.parent or str(target_path) == target_path.anchor:
|
|
281
|
+
return (
|
|
282
|
+
1,
|
|
283
|
+
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
284
|
+
"You cannot delete the root filesystem directory.\n",
|
|
285
|
+
)
|
|
286
|
+
critical_system_dirs = [
|
|
287
|
+
Path.home().parent.resolve(),
|
|
288
|
+
Path.home().resolve(),
|
|
289
|
+
]
|
|
290
|
+
if c.HOST_OS == "Windows":
|
|
291
|
+
system_drive = os.environ.get("SystemDrive", "C:")
|
|
292
|
+
critical_system_dirs.extend(
|
|
293
|
+
[
|
|
294
|
+
Path(f"{system_drive}\\Windows").resolve(),
|
|
295
|
+
Path(f"{system_drive}\\Program Files").resolve(),
|
|
296
|
+
Path(f"{system_drive}\\Program Files (x86)").resolve(),
|
|
297
|
+
]
|
|
298
|
+
)
|
|
299
|
+
else:
|
|
300
|
+
critical_system_dirs.extend(
|
|
301
|
+
[
|
|
302
|
+
Path("/bin").resolve(),
|
|
303
|
+
Path("/etc").resolve(),
|
|
304
|
+
Path("/usr").resolve(),
|
|
305
|
+
Path("/var").resolve(),
|
|
306
|
+
]
|
|
307
|
+
)
|
|
308
|
+
if target_path in critical_system_dirs:
|
|
309
|
+
return (
|
|
310
|
+
1,
|
|
311
|
+
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
312
|
+
f"Target '{raw_target}' is a protected operating system directory.\n",
|
|
313
|
+
)
|
|
314
|
+
if target_path == Path(os.getcwd()).resolve():
|
|
315
|
+
return (
|
|
316
|
+
1,
|
|
317
|
+
"\nSystem Error: Cannot remove current working directory.\n"
|
|
318
|
+
"Switch to a different directory using 'cd' first.\n",
|
|
319
|
+
)
|
|
320
|
+
flyshell_dirs = [
|
|
321
|
+
c.APP_DIR.resolve(),
|
|
322
|
+
c.PACKAGE_ROOT.resolve(),
|
|
323
|
+
c.PROJECT_ROOT.resolve(),
|
|
324
|
+
]
|
|
325
|
+
for protected in flyshell_dirs:
|
|
326
|
+
if target_path == protected or target_path.is_relative_to(protected):
|
|
327
|
+
return (
|
|
328
|
+
1,
|
|
329
|
+
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
330
|
+
f"Target '{raw_target}' contains or belongs to Flyshell system files.\n"
|
|
331
|
+
"If you wish to delete Flyshell, use the host.\n",
|
|
332
|
+
)
|
|
333
|
+
is_directory = target_path.is_dir()
|
|
334
|
+
if is_directory and not recursive:
|
|
335
|
+
return (
|
|
336
|
+
1,
|
|
337
|
+
f"\nCommand Error: '{raw_target}' is a directory.\n"
|
|
338
|
+
"Use '-r' or '--recursive' to remove directories.\n",
|
|
339
|
+
)
|
|
340
|
+
if not force:
|
|
341
|
+
target_type = "directory and all its contents" if is_directory else "file"
|
|
342
|
+
prompt_msg = f"\nAre you sure you want to permanently delete {target_type} '{raw_target}'? [y/n]: "
|
|
343
|
+
try:
|
|
344
|
+
confirm = input(prompt_msg).strip().lower()
|
|
345
|
+
except (KeyboardInterrupt, EOFError):
|
|
346
|
+
return (1, "\nDeletion cancelled by user.\n")
|
|
347
|
+
if confirm != "y":
|
|
348
|
+
return (0, "\nOperation cancelled. No files were deleted.\n")
|
|
349
|
+
try:
|
|
350
|
+
if is_directory:
|
|
351
|
+
shutil.rmtree(target_path)
|
|
352
|
+
return (
|
|
353
|
+
0,
|
|
354
|
+
f"\nSUCCESS! Deleted directory '{raw_target}' and all its contents.\n",
|
|
355
|
+
)
|
|
356
|
+
else:
|
|
357
|
+
os.remove(target_path)
|
|
358
|
+
return (0, f"\nSUCCESS! Deleted file '{raw_target}'\n")
|
|
359
|
+
except PermissionError:
|
|
360
|
+
return (
|
|
361
|
+
1,
|
|
362
|
+
f"\nSystem Error: Permission denied. Cannot remove '{raw_target}'\n",
|
|
363
|
+
)
|
|
364
|
+
except OSError as e:
|
|
365
|
+
return (
|
|
366
|
+
1,
|
|
367
|
+
f"\nSystem Error: Unable to remove '{raw_target}' ({e.strerror})\n",
|
|
368
|
+
)
|
|
369
|
+
except Exception as e:
|
|
370
|
+
return (
|
|
371
|
+
1,
|
|
372
|
+
f"\nSystem Error: Unexpected error removing '{raw_target}' ({e})\n",
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class RmDir(BaseCmd):
|
|
377
|
+
name = "rmdir"
|
|
378
|
+
description = "Remove the specified directory"
|
|
379
|
+
min_args = 1
|
|
380
|
+
options = "Target directory"
|
|
381
|
+
aliases = []
|
|
382
|
+
|
|
383
|
+
def execute(self, args):
|
|
384
|
+
force = ("-f" in args) or ("--force" in args)
|
|
385
|
+
path_tokens = [arg for arg in args if arg not in ("-f", "--force")]
|
|
386
|
+
raw_target = " ".join(path_tokens).strip("\"'")
|
|
387
|
+
target_path = Path(raw_target).expanduser().resolve()
|
|
388
|
+
if target_path == target_path.parent or str(target_path) == target_path.anchor:
|
|
389
|
+
return (
|
|
390
|
+
1,
|
|
391
|
+
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
392
|
+
"You cannot delete the root filesystem directory.\n",
|
|
393
|
+
)
|
|
394
|
+
critical_system_dirs = [
|
|
395
|
+
Path.home().parent.resolve(),
|
|
396
|
+
Path.home().resolve(),
|
|
397
|
+
]
|
|
398
|
+
if c.HOST_OS == "Windows":
|
|
399
|
+
system_drive = os.environ.get("SystemDrive", "C:")
|
|
400
|
+
critical_system_dirs.extend(
|
|
401
|
+
[
|
|
402
|
+
Path(f"{system_drive}\\Windows").resolve(),
|
|
403
|
+
Path(f"{system_drive}\\Program Files").resolve(),
|
|
404
|
+
Path(f"{system_drive}\\Program Files (x86)").resolve(),
|
|
405
|
+
]
|
|
406
|
+
)
|
|
407
|
+
else:
|
|
408
|
+
critical_system_dirs.extend(
|
|
409
|
+
[
|
|
410
|
+
Path("/bin").resolve(),
|
|
411
|
+
Path("/etc").resolve(),
|
|
412
|
+
Path("/usr").resolve(),
|
|
413
|
+
Path("/var").resolve(),
|
|
414
|
+
]
|
|
415
|
+
)
|
|
416
|
+
if target_path in critical_system_dirs:
|
|
417
|
+
return (
|
|
418
|
+
1,
|
|
419
|
+
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
420
|
+
f"Target '{raw_target}' is a protected operating system directory.\n",
|
|
421
|
+
)
|
|
422
|
+
if target_path == Path(os.getcwd()).resolve():
|
|
423
|
+
return (
|
|
424
|
+
1,
|
|
425
|
+
"\nSystem Error: Cannot remove current working directory.\n"
|
|
426
|
+
"Switch to a different directory using 'cd' first.\n",
|
|
427
|
+
)
|
|
428
|
+
flyshell_dirs = [
|
|
429
|
+
c.APP_DIR.resolve(),
|
|
430
|
+
c.PACKAGE_ROOT.resolve(),
|
|
431
|
+
c.PROJECT_ROOT.resolve(),
|
|
432
|
+
]
|
|
433
|
+
for protected in flyshell_dirs:
|
|
434
|
+
if target_path == protected or target_path.is_relative_to(protected):
|
|
435
|
+
return (
|
|
436
|
+
1,
|
|
437
|
+
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
438
|
+
f"Target '{raw_target}' contains or belongs to Flyshell system files.\n"
|
|
439
|
+
"If you wish to delete Flyshell, use the host.\n",
|
|
440
|
+
)
|
|
441
|
+
try:
|
|
442
|
+
has_contents = any(target_path.iterdir())
|
|
443
|
+
if has_contents and not force:
|
|
444
|
+
return (
|
|
445
|
+
1,
|
|
446
|
+
f"\nDirectory Error: Directory '{raw_target}' is not empty.\n"
|
|
447
|
+
"Use '-f' or '--force' to remove a non-empty directory and its contents.\n",
|
|
448
|
+
)
|
|
449
|
+
if has_contents and force:
|
|
450
|
+
shutil.rmtree(target_path)
|
|
451
|
+
else:
|
|
452
|
+
os.rmdir(target_path)
|
|
453
|
+
return (0, f"\nSUCCESS! Removed directory '{raw_target}'\n")
|
|
454
|
+
except PermissionError:
|
|
455
|
+
return (
|
|
456
|
+
1,
|
|
457
|
+
f"\nSystem Error: Permission denied. Cannot delete '{raw_target}'\n",
|
|
458
|
+
)
|
|
459
|
+
except OSError as e:
|
|
460
|
+
return (
|
|
461
|
+
1,
|
|
462
|
+
f"\nSystem Error: Unable to remove directory '{raw_target}' ({e.strerror})\n",
|
|
463
|
+
)
|
|
464
|
+
except Exception as e:
|
|
465
|
+
return (
|
|
466
|
+
1,
|
|
467
|
+
f"\nSystem Error: Unexpected error removing '{raw_target}' ({e})\n",
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
class Touch(BaseCmd):
|
|
472
|
+
name = "touch"
|
|
473
|
+
description = "Create a new empty file or update timestamps of an existing file"
|
|
474
|
+
min_args = 1
|
|
475
|
+
options = "Target file"
|
|
476
|
+
aliases = []
|
|
477
|
+
|
|
478
|
+
def execute(self, args):
|
|
479
|
+
raw_target = " ".join(args).strip("\"'")
|
|
480
|
+
target_path = Path(raw_target).expanduser().resolve()
|
|
481
|
+
if target_path.is_dir():
|
|
482
|
+
return (
|
|
483
|
+
1,
|
|
484
|
+
f"\nCommand Error: '{raw_target}' is a directory, not a file.\n",
|
|
485
|
+
)
|
|
486
|
+
if not target_path.parent.exists():
|
|
487
|
+
return (
|
|
488
|
+
1,
|
|
489
|
+
f"\nSystem Error: Cannot create file '{raw_target}'. Parent directory does not exist.\n",
|
|
490
|
+
)
|
|
491
|
+
try:
|
|
492
|
+
if target_path.exists():
|
|
493
|
+
target_path.touch()
|
|
494
|
+
return (
|
|
495
|
+
0,
|
|
496
|
+
f"\nSUCCESS! Updated timestamp for existing file '{raw_target}'\n",
|
|
497
|
+
)
|
|
498
|
+
else:
|
|
499
|
+
target_path.touch(exist_ok=False)
|
|
500
|
+
return (0, f"\nSUCCESS! Created file '{raw_target}'\n")
|
|
501
|
+
except PermissionError:
|
|
502
|
+
return (
|
|
503
|
+
1,
|
|
504
|
+
f"\nSystem Error: Permission denied. Cannot access '{raw_target}'.\n",
|
|
505
|
+
)
|
|
506
|
+
except Exception as e:
|
|
507
|
+
return (
|
|
508
|
+
1,
|
|
509
|
+
f"\nSystem Error: Failed to touch file '{raw_target}' ({e})\n",
|
|
510
|
+
)
|
|
@@ -53,17 +53,21 @@ INSTALLED_COMMANDS: list[BaseCmd] = [
|
|
|
53
53
|
system.Cd(),
|
|
54
54
|
Cmds(),
|
|
55
55
|
utils.Clear(),
|
|
56
|
+
diskops.Copy(),
|
|
56
57
|
diskops.Dir(),
|
|
57
58
|
fs.Fs(),
|
|
58
59
|
utils.History(),
|
|
59
60
|
utils.Kill(),
|
|
60
61
|
auth.Lock(),
|
|
61
62
|
diskops.MkDir(),
|
|
63
|
+
diskops.Move(),
|
|
62
64
|
system.OpenFile(),
|
|
63
65
|
utils.Reboot(),
|
|
66
|
+
diskops.Rm(),
|
|
64
67
|
diskops.RmDir(),
|
|
65
68
|
utils.Sleep(),
|
|
66
69
|
system.SysExe(),
|
|
70
|
+
diskops.Touch(),
|
|
67
71
|
]
|
|
68
72
|
COMMAND_REGISTRY: dict[str, BaseCmd] = {}
|
|
69
73
|
for cmd_obj in INSTALLED_COMMANDS:
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
# src\flyshell\main.py
|
|
2
2
|
|
|
3
|
-
from flyshell import constants as c
|
|
4
|
-
|
|
5
3
|
from pathlib import Path
|
|
6
4
|
import importlib.util
|
|
7
5
|
import os
|
|
8
6
|
import sys
|
|
9
7
|
|
|
10
|
-
if str(c.SRC_DIR) not in sys.path:
|
|
11
|
-
sys.path.insert(0, str(c.SRC_DIR))
|
|
12
|
-
|
|
13
8
|
|
|
14
9
|
def boot_check():
|
|
15
10
|
print("\nFlyshell will now check that your modules exist.")
|
|
16
11
|
present = []
|
|
17
12
|
missing = []
|
|
13
|
+
try:
|
|
14
|
+
from flyshell import constants as c
|
|
15
|
+
|
|
16
|
+
if str(c.SRC_DIR) not in sys.path:
|
|
17
|
+
sys.path.insert(0, str(c.SRC_DIR))
|
|
18
|
+
except ImportError:
|
|
19
|
+
print(
|
|
20
|
+
"\nCRITICAL BOOT ERROR: Failed to initialise system due to corrupted or missing files.\n"
|
|
21
|
+
)
|
|
18
22
|
for obj in c.REQUIRED_COMPONENTS:
|
|
19
23
|
path = Path(c.PACKAGE_ROOT / obj)
|
|
20
24
|
if path.exists():
|
|
@@ -58,11 +62,12 @@ def boot_check():
|
|
|
58
62
|
|
|
59
63
|
def main():
|
|
60
64
|
os.system("")
|
|
61
|
-
print(
|
|
65
|
+
print("\033[H\033[2J", end="")
|
|
62
66
|
print("\nLaunch process initiated. Flyshell is checking it is able to launch...")
|
|
63
67
|
try:
|
|
64
68
|
if boot_check():
|
|
65
|
-
from flyshell
|
|
69
|
+
from flyshell import constants as c
|
|
70
|
+
from flyshell.core import auth, console, loader
|
|
66
71
|
|
|
67
72
|
if c.HOST_OS in ["Windows", "Darwin", "Linux"]:
|
|
68
73
|
if sys.version_info < c.SUPPORTED_PYTHON:
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
# src\flyshell\builtin\diskops.py
|
|
2
|
-
|
|
3
|
-
from flyshell import constants as c
|
|
4
|
-
from flyshell.abstract.base_cmd import BaseCmd
|
|
5
|
-
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
import os
|
|
8
|
-
import shutil
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Cat(BaseCmd):
|
|
12
|
-
name = "cat"
|
|
13
|
-
description = "Display the contents of a text file"
|
|
14
|
-
min_args = 1
|
|
15
|
-
options = "Target file"
|
|
16
|
-
aliases = ["read"]
|
|
17
|
-
|
|
18
|
-
def execute(self, args):
|
|
19
|
-
show_numbers = ("-n" in args) or ("--number" in args)
|
|
20
|
-
limit = None
|
|
21
|
-
filtered_args = []
|
|
22
|
-
skip_next = False
|
|
23
|
-
for i, token in enumerate(args):
|
|
24
|
-
if skip_next:
|
|
25
|
-
skip_next = False
|
|
26
|
-
continue
|
|
27
|
-
if token in ("-n", "--number"):
|
|
28
|
-
continue
|
|
29
|
-
if token in ("-l", "--limit"):
|
|
30
|
-
if i + 1 < len(args) and args[i + 1].isdigit():
|
|
31
|
-
limit = int(args[i + 1])
|
|
32
|
-
skip_next = True
|
|
33
|
-
continue
|
|
34
|
-
else:
|
|
35
|
-
return (1, f"\nCommand Error: '{token}' requires a positive integer limit.\n")
|
|
36
|
-
filtered_args.append(token)
|
|
37
|
-
if not filtered_args:
|
|
38
|
-
return (1, "\nCommand Error: Target file not specified.\n")
|
|
39
|
-
raw_target = " ".join(filtered_args).strip("\"'")
|
|
40
|
-
target_path = Path(raw_target).expanduser().resolve()
|
|
41
|
-
if not target_path.exists():
|
|
42
|
-
return (1, f"\nSystem Error: File '{raw_target}' not found.\n")
|
|
43
|
-
if target_path.is_dir():
|
|
44
|
-
return (1, f"\nSystem Error: '{raw_target}' is a directory, not a file.\n")
|
|
45
|
-
try:
|
|
46
|
-
with open(target_path, "r", encoding="utf-8", errors="replace") as f:
|
|
47
|
-
lines = f.readlines()
|
|
48
|
-
total_lines = len(lines)
|
|
49
|
-
if total_lines == 0:
|
|
50
|
-
return (1, f"\nSystem Error: '{raw_target}' is empty.\n")
|
|
51
|
-
if limit is not None:
|
|
52
|
-
lines = lines[:limit]
|
|
53
|
-
output_lines = [
|
|
54
|
-
f"\nDisplaying contents of file '{target_path.name}' ({len(lines)}/{total_lines} lines)\n"
|
|
55
|
-
]
|
|
56
|
-
for line_idx, line in enumerate(lines, start=1):
|
|
57
|
-
clean_line = line.rstrip("\r\n")
|
|
58
|
-
if show_numbers:
|
|
59
|
-
output_lines.append(f"{line_idx:4d} | {clean_line}")
|
|
60
|
-
else:
|
|
61
|
-
output_lines.append(clean_line)
|
|
62
|
-
output_lines.append(f"\nEnd of '{target_path.name}'\n")
|
|
63
|
-
return (0, "\n".join(output_lines))
|
|
64
|
-
except PermissionError:
|
|
65
|
-
return (1, f"\nSystem Error: Permission denied. Cannot read '{raw_target}'\n")
|
|
66
|
-
except Exception as e:
|
|
67
|
-
return (1, f"\nSystem Error: Failed to read file '{raw_target}' ({e})\n")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class Dir(BaseCmd):
|
|
71
|
-
name = "dir"
|
|
72
|
-
description = "List all files in the current working directory"
|
|
73
|
-
min_args = 0
|
|
74
|
-
options = None
|
|
75
|
-
aliases = ["ls"]
|
|
76
|
-
|
|
77
|
-
def execute(self, args):
|
|
78
|
-
try:
|
|
79
|
-
current_path = os.getcwd()
|
|
80
|
-
items = os.listdir(current_path)
|
|
81
|
-
lines = [f"\nDirectory: '{current_path}'"]
|
|
82
|
-
for item in items:
|
|
83
|
-
if os.path.isdir(os.path.join(current_path, item)):
|
|
84
|
-
lines.append(f"[DIR] {item}/")
|
|
85
|
-
else:
|
|
86
|
-
lines.append(f"[FILE] {item}")
|
|
87
|
-
lines.append(f"\nTotal items in directory: {len(items)}\n")
|
|
88
|
-
return (0, "\n".join(lines))
|
|
89
|
-
except Exception as e:
|
|
90
|
-
return (1, f"\nSystem Error: Unable to list directory ({e})\n")
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class MkDir(BaseCmd):
|
|
94
|
-
name = "mkdir"
|
|
95
|
-
description = "Create a new directory from the current working directory"
|
|
96
|
-
min_args = 1
|
|
97
|
-
options = "Directory name"
|
|
98
|
-
aliases = ["md"]
|
|
99
|
-
|
|
100
|
-
def execute(self, args):
|
|
101
|
-
use_parents = ("-p" in args) or ("--parents" in args)
|
|
102
|
-
path_tokens = [arg for arg in args if arg not in ("-p", "--parents")]
|
|
103
|
-
target = " ".join(path_tokens).strip("\"'")
|
|
104
|
-
try:
|
|
105
|
-
if use_parents:
|
|
106
|
-
os.makedirs(target, exist_ok=True)
|
|
107
|
-
else:
|
|
108
|
-
os.mkdir(target)
|
|
109
|
-
return (0, f"\nSUCCESS! Created directory '{target}'\n")
|
|
110
|
-
except FileExistsError:
|
|
111
|
-
return (1, f"\nSystem Error: Directory '{target}' already exists.\n")
|
|
112
|
-
except FileNotFoundError:
|
|
113
|
-
return (
|
|
114
|
-
1,
|
|
115
|
-
"\nSystem Error: Parent directory does not exist. Use '-p' or '--parents' to create parent directories.\n",
|
|
116
|
-
)
|
|
117
|
-
except PermissionError:
|
|
118
|
-
return (1, f"\nSystem Error: Permission denied. Cannot create '{target}'\n")
|
|
119
|
-
except Exception as e:
|
|
120
|
-
return (1, f"\nSystem Error: Unable to create directory '{target}' ({e})\n")
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
class RmDir(BaseCmd):
|
|
124
|
-
name = "rmdir"
|
|
125
|
-
description = "Remove the specified directory"
|
|
126
|
-
min_args = 1
|
|
127
|
-
options = "Target directory"
|
|
128
|
-
aliases = []
|
|
129
|
-
|
|
130
|
-
def execute(self, args):
|
|
131
|
-
force = ("-f" in args) or ("--force" in args)
|
|
132
|
-
path_tokens = [arg for arg in args if arg not in ("-f", "--force")]
|
|
133
|
-
raw_target = " ".join(path_tokens).strip("\"'")
|
|
134
|
-
target_path = Path(raw_target).expanduser().resolve()
|
|
135
|
-
if target_path == target_path.parent or str(target_path) == target_path.anchor:
|
|
136
|
-
return (
|
|
137
|
-
1,
|
|
138
|
-
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
139
|
-
"You cannot delete the root filesystem directory.\n",
|
|
140
|
-
)
|
|
141
|
-
critical_system_dirs = [
|
|
142
|
-
Path.home().parent.resolve(),
|
|
143
|
-
Path.home().resolve(),
|
|
144
|
-
]
|
|
145
|
-
if c.HOST_OS == "Windows":
|
|
146
|
-
system_drive = os.environ.get("SystemDrive", "C:")
|
|
147
|
-
critical_system_dirs.extend([
|
|
148
|
-
Path(f"{system_drive}\\Windows").resolve(),
|
|
149
|
-
Path(f"{system_drive}\\Program Files").resolve(),
|
|
150
|
-
Path(f"{system_drive}\\Program Files (x86)").resolve(),
|
|
151
|
-
])
|
|
152
|
-
else:
|
|
153
|
-
critical_system_dirs.extend([
|
|
154
|
-
Path("/bin").resolve(),
|
|
155
|
-
Path("/etc").resolve(),
|
|
156
|
-
Path("/usr").resolve(),
|
|
157
|
-
Path("/var").resolve(),
|
|
158
|
-
])
|
|
159
|
-
if target_path in critical_system_dirs:
|
|
160
|
-
return (
|
|
161
|
-
1,
|
|
162
|
-
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
163
|
-
f"Target '{raw_target}' is a protected operating system directory.\n",
|
|
164
|
-
)
|
|
165
|
-
if target_path == Path(os.getcwd()).resolve():
|
|
166
|
-
return (
|
|
167
|
-
1,
|
|
168
|
-
"\nSystem Error: Cannot remove current working directory.\n"
|
|
169
|
-
"Switch to a different directory using 'cd' first.\n",
|
|
170
|
-
)
|
|
171
|
-
flyshell_dirs = [
|
|
172
|
-
c.APP_DIR.resolve(),
|
|
173
|
-
c.PACKAGE_ROOT.resolve(),
|
|
174
|
-
c.PROJECT_ROOT.resolve(),
|
|
175
|
-
]
|
|
176
|
-
for protected in flyshell_dirs:
|
|
177
|
-
if target_path == protected or target_path.is_relative_to(protected):
|
|
178
|
-
return (
|
|
179
|
-
1,
|
|
180
|
-
"\nSecurity Error: Flyshell has blocked this action.\n"
|
|
181
|
-
f"Target '{raw_target}' contains or belongs to Flyshell system files.\n"
|
|
182
|
-
"If you wish to delete Flyshell, use the host.\n",
|
|
183
|
-
)
|
|
184
|
-
try:
|
|
185
|
-
has_contents = any(target_path.iterdir())
|
|
186
|
-
if has_contents and not force:
|
|
187
|
-
return (
|
|
188
|
-
1,
|
|
189
|
-
f"\nDirectory Error: Directory '{raw_target}' is not empty.\n"
|
|
190
|
-
"Use '-f' or '--force' to remove a non-empty directory and its contents.\n",
|
|
191
|
-
)
|
|
192
|
-
if has_contents and force:
|
|
193
|
-
shutil.rmtree(target_path)
|
|
194
|
-
else:
|
|
195
|
-
os.rmdir(target_path)
|
|
196
|
-
return (0, f"\nSUCCESS! Removed directory '{raw_target}'\n")
|
|
197
|
-
except PermissionError:
|
|
198
|
-
return (
|
|
199
|
-
1,
|
|
200
|
-
f"\nSystem Error: Permission denied. Cannot delete '{raw_target}'\n",
|
|
201
|
-
)
|
|
202
|
-
except OSError as e:
|
|
203
|
-
return (
|
|
204
|
-
1,
|
|
205
|
-
f"\nSystem Error: Unable to remove directory '{raw_target}' ({e.strerror})\n",
|
|
206
|
-
)
|
|
207
|
-
except Exception as e:
|
|
208
|
-
return (
|
|
209
|
-
1,
|
|
210
|
-
f"\nSystem Error: Unexpected error removing '{raw_target}' ({e})\n",
|
|
211
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|