roysh 0.1__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.
- roysh/__init__.py +3 -0
- roysh/shell.py +263 -0
- roysh-0.1.dist-info/METADATA +103 -0
- roysh-0.1.dist-info/RECORD +7 -0
- roysh-0.1.dist-info/WHEEL +5 -0
- roysh-0.1.dist-info/entry_points.txt +2 -0
- roysh-0.1.dist-info/top_level.txt +1 -0
roysh/__init__.py
ADDED
roysh/shell.py
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
import sys
|
2
|
+
import os
|
3
|
+
import subprocess
|
4
|
+
import shlex
|
5
|
+
|
6
|
+
# Handle readline import for different platforms
|
7
|
+
try:
|
8
|
+
import readline
|
9
|
+
except ImportError:
|
10
|
+
import pyreadline3 as readline
|
11
|
+
|
12
|
+
def extract_after_third_slash(command):
|
13
|
+
"""Extracts the portion of the command after the third slash (/)"""
|
14
|
+
parts = command.split("/")
|
15
|
+
if len(parts) > 3:
|
16
|
+
return "/".join(parts[3:])
|
17
|
+
return command
|
18
|
+
|
19
|
+
def get_executables_from_path():
|
20
|
+
"""Get list of executable files from PATH directories"""
|
21
|
+
executables = []
|
22
|
+
extensions = ['.exe', '.bat', '.cmd', ''] if os.name == 'nt' else ['']
|
23
|
+
path_sep = ';' if os.name == 'nt' else ':'
|
24
|
+
|
25
|
+
for directory in os.environ.get("PATH", "").split(path_sep):
|
26
|
+
try:
|
27
|
+
for file in os.listdir(directory):
|
28
|
+
file_path = os.path.join(directory, file)
|
29
|
+
# Check if the file has an executable extension on Windows
|
30
|
+
if os.name == 'nt':
|
31
|
+
file_name, file_ext = os.path.splitext(file.lower())
|
32
|
+
if file_ext not in extensions:
|
33
|
+
continue
|
34
|
+
if os.path.isfile(file_path) and os.access(file_path, os.X_OK):
|
35
|
+
# Remove extension for consistency
|
36
|
+
base_name = os.path.splitext(file)[0]
|
37
|
+
executables.append(base_name)
|
38
|
+
except OSError:
|
39
|
+
continue
|
40
|
+
return executables
|
41
|
+
|
42
|
+
def get_common_prefix(matches):
|
43
|
+
"""Find the longest common prefix of all matches"""
|
44
|
+
if not matches:
|
45
|
+
return ""
|
46
|
+
if len(matches) == 1:
|
47
|
+
return matches[0]
|
48
|
+
|
49
|
+
reference = matches[0]
|
50
|
+
|
51
|
+
for i in range(len(reference)):
|
52
|
+
for other in matches[1:]:
|
53
|
+
if i >= len(other) or other[i] != reference[i]:
|
54
|
+
return reference[:i]
|
55
|
+
|
56
|
+
return reference
|
57
|
+
|
58
|
+
def get_matches(text):
|
59
|
+
"""Get all matching commands for the given text"""
|
60
|
+
builtins = ["echo", "exit", "type", "pwd", "cd", "list"]
|
61
|
+
all_commands = builtins + get_executables_from_path()
|
62
|
+
return sorted(set(cmd for cmd in all_commands if cmd.startswith(text)))
|
63
|
+
|
64
|
+
def complete(text, state):
|
65
|
+
"""Autocomplete callback for readline"""
|
66
|
+
matches = get_matches(text)
|
67
|
+
|
68
|
+
if not matches:
|
69
|
+
return None
|
70
|
+
|
71
|
+
if state == 0:
|
72
|
+
if len(matches) == 1:
|
73
|
+
return matches[0] + " "
|
74
|
+
else:
|
75
|
+
common = get_common_prefix(matches)
|
76
|
+
if common != text:
|
77
|
+
return common
|
78
|
+
sys.stdout.write('\a')
|
79
|
+
sys.stdout.flush()
|
80
|
+
print()
|
81
|
+
print(" ".join(matches))
|
82
|
+
sys.stdout.write("$ " + text)
|
83
|
+
sys.stdout.flush()
|
84
|
+
return text
|
85
|
+
|
86
|
+
return None
|
87
|
+
|
88
|
+
def get_installed_packages():
|
89
|
+
"""Get list of installed Python packages"""
|
90
|
+
try:
|
91
|
+
import pkg_resources
|
92
|
+
return sorted([f"{dist.key} {dist.version}" for dist in pkg_resources.working_set])
|
93
|
+
except Exception:
|
94
|
+
return ["Error: Unable to fetch installed packages"]
|
95
|
+
|
96
|
+
def get_roysh_commands():
|
97
|
+
"""Get list of roysh commands with descriptions"""
|
98
|
+
commands = {
|
99
|
+
"echo": "Print text to stdout",
|
100
|
+
"exit": "Exit the shell (optional: exit <code>)",
|
101
|
+
"type": "Show command type/location",
|
102
|
+
"pwd": "Print working directory",
|
103
|
+
"cd": "Change directory (defaults to HOME)",
|
104
|
+
"list": "List available commands or installed packages",
|
105
|
+
}
|
106
|
+
return commands
|
107
|
+
|
108
|
+
def main():
|
109
|
+
# Display welcome message
|
110
|
+
print("\nRoysh - Shell by Python")
|
111
|
+
print("Developed by Nishan Roy")
|
112
|
+
print("Type 'exit' to quit\n")
|
113
|
+
|
114
|
+
# Set up readline with our completer
|
115
|
+
readline.parse_and_bind('tab: complete')
|
116
|
+
readline.set_completer(complete)
|
117
|
+
|
118
|
+
# Define the list of built-in commands
|
119
|
+
builtins = {"echo", "exit", "type", "pwd", "cd", "list"}
|
120
|
+
|
121
|
+
while True:
|
122
|
+
# Display the shell prompt
|
123
|
+
sys.stdout.write("$ ")
|
124
|
+
sys.stdout.flush()
|
125
|
+
try:
|
126
|
+
# Read user input (readline will handle tab completion)
|
127
|
+
command = input().strip()
|
128
|
+
|
129
|
+
# Check for output redirection
|
130
|
+
output_file = None
|
131
|
+
error_file = None
|
132
|
+
append_mode = False
|
133
|
+
append_error_mode = False
|
134
|
+
|
135
|
+
if " 2>> " in command:
|
136
|
+
parts = command.split(" 2>> ", 1)
|
137
|
+
command = parts[0].strip()
|
138
|
+
error_file = parts[1].strip()
|
139
|
+
append_error_mode = True
|
140
|
+
elif " 2> " in command:
|
141
|
+
parts = command.split(" 2> ", 1)
|
142
|
+
command = parts[0].strip()
|
143
|
+
error_file = parts[1].strip()
|
144
|
+
elif " >> " in command or " 1>> " in command:
|
145
|
+
parts = command.split(" >> ", 1) if " >> " in command else command.split(" 1>> ", 1)
|
146
|
+
command = parts[0].strip()
|
147
|
+
output_file = parts[1].strip()
|
148
|
+
append_mode = True
|
149
|
+
elif " > " in command or " 1> " in command:
|
150
|
+
parts = command.split(" > ", 1) if " > " in command else command.split(" 1> ", 1)
|
151
|
+
command = parts[0].strip()
|
152
|
+
output_file = parts[1].strip()
|
153
|
+
|
154
|
+
# Parse input to handle quotes
|
155
|
+
args = shlex.split(command, posix=True)
|
156
|
+
if not args:
|
157
|
+
continue
|
158
|
+
|
159
|
+
# Save original stdout/stderr if we need to redirect
|
160
|
+
original_stdout = None
|
161
|
+
original_stderr = None
|
162
|
+
|
163
|
+
try:
|
164
|
+
if output_file:
|
165
|
+
original_stdout = sys.stdout
|
166
|
+
sys.stdout = open(output_file, 'a' if append_mode else 'w')
|
167
|
+
if error_file:
|
168
|
+
original_stderr = sys.stderr
|
169
|
+
sys.stderr = open(error_file, 'a' if append_error_mode else 'w')
|
170
|
+
|
171
|
+
cmd = args[0]
|
172
|
+
# Handle built-in commands
|
173
|
+
if cmd == "exit":
|
174
|
+
if len(args) > 1 and args[1].isdigit():
|
175
|
+
exit_code = int(args[1])
|
176
|
+
else:
|
177
|
+
exit_code = 0
|
178
|
+
sys.exit(exit_code)
|
179
|
+
elif cmd == "list":
|
180
|
+
if len(args) > 1 and args[1] == "packages":
|
181
|
+
packages = get_installed_packages()
|
182
|
+
for package in packages:
|
183
|
+
print(package)
|
184
|
+
else:
|
185
|
+
print("\nAvailable Roysh Commands:")
|
186
|
+
print("----------------------")
|
187
|
+
commands = get_roysh_commands()
|
188
|
+
for cmd_name, description in commands.items():
|
189
|
+
print(f"{cmd_name:<10} - {description}")
|
190
|
+
print("\nUsage:")
|
191
|
+
print("list - Show this help")
|
192
|
+
print("list packages - Show installed Python packages\n")
|
193
|
+
elif cmd == "type":
|
194
|
+
if len(args) < 2:
|
195
|
+
print("type: missing operand")
|
196
|
+
continue
|
197
|
+
cmd_to_check = args[1]
|
198
|
+
if cmd_to_check in builtins:
|
199
|
+
print(f"{cmd_to_check} is a shell builtin")
|
200
|
+
else:
|
201
|
+
found = False
|
202
|
+
for directory in os.environ["PATH"].split(":"):
|
203
|
+
command_path = os.path.join(directory, cmd_to_check)
|
204
|
+
if os.path.isfile(command_path) and os.access(command_path, os.X_OK):
|
205
|
+
print(f"{cmd_to_check} is {command_path}")
|
206
|
+
found = True
|
207
|
+
break
|
208
|
+
if not found:
|
209
|
+
print(f"{cmd_to_check}: not found")
|
210
|
+
elif cmd == "pwd":
|
211
|
+
print(os.getcwd())
|
212
|
+
elif cmd == "cd":
|
213
|
+
if len(args) < 2:
|
214
|
+
target_dir = os.environ.get("HOME", "/")
|
215
|
+
else:
|
216
|
+
target_dir = args[1]
|
217
|
+
if target_dir == "~":
|
218
|
+
target_dir = os.environ.get("HOME", "/")
|
219
|
+
try:
|
220
|
+
os.chdir(target_dir)
|
221
|
+
except FileNotFoundError:
|
222
|
+
print(f"cd: {target_dir}: No such file or directory")
|
223
|
+
except PermissionError:
|
224
|
+
print(f"cd: {target_dir}: Permission denied")
|
225
|
+
elif cmd == "echo":
|
226
|
+
print(" ".join(args[1:]))
|
227
|
+
else:
|
228
|
+
found = False
|
229
|
+
extensions = ['.exe', '.bat', '.cmd', ''] if os.name == 'nt' else ['']
|
230
|
+
for directory in os.environ["PATH"].split(os.pathsep):
|
231
|
+
for ext in extensions:
|
232
|
+
program_path = os.path.join(directory, cmd + ext)
|
233
|
+
if os.path.isfile(program_path) and os.access(program_path, os.X_OK):
|
234
|
+
found = True
|
235
|
+
try:
|
236
|
+
result = subprocess.run(
|
237
|
+
[program_path] + args[1:],
|
238
|
+
capture_output=True,
|
239
|
+
text=True,
|
240
|
+
)
|
241
|
+
if result.stdout:
|
242
|
+
print(result.stdout.strip())
|
243
|
+
if result.stderr:
|
244
|
+
print(result.stderr.strip(), file=sys.stderr)
|
245
|
+
except Exception as e:
|
246
|
+
print(f"Error running {cmd}: {e}", file=sys.stderr)
|
247
|
+
break
|
248
|
+
if found:
|
249
|
+
break
|
250
|
+
if not found:
|
251
|
+
print(f"{cmd}: not found")
|
252
|
+
finally:
|
253
|
+
if output_file and original_stdout:
|
254
|
+
sys.stdout.close()
|
255
|
+
sys.stdout = original_stdout
|
256
|
+
if error_file and original_stderr:
|
257
|
+
sys.stderr.close()
|
258
|
+
sys.stderr = original_stderr
|
259
|
+
except EOFError:
|
260
|
+
sys.exit(0)
|
261
|
+
|
262
|
+
if __name__ == "__main__":
|
263
|
+
main()
|
@@ -0,0 +1,103 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: roysh
|
3
|
+
Version: 0.1
|
4
|
+
Summary: A Python-based shell with tab completion
|
5
|
+
Home-page: https://github.com/nishanroy561/RoySH
|
6
|
+
Author: Nishan Roy
|
7
|
+
Author-email: nishanroy561@gmail.com
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Environment :: Console
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: Topic :: System :: Shells
|
14
|
+
Requires-Python: >=3.6
|
15
|
+
Description-Content-Type: text/markdown
|
16
|
+
Requires-Dist: pyreadline3
|
17
|
+
|
18
|
+
# Roysh - A Python-based Shell
|
19
|
+
|
20
|
+
Roysh is a simple yet powerful Python-based shell that provides familiar shell functionality with tab completion support.
|
21
|
+
|
22
|
+
## Features
|
23
|
+
|
24
|
+
- Interactive command-line interface
|
25
|
+
- Tab completion for commands and file paths
|
26
|
+
- Built-in shell commands
|
27
|
+
- Command output redirection
|
28
|
+
- Path-based command execution
|
29
|
+
- Cross-platform support
|
30
|
+
|
31
|
+
## Installation
|
32
|
+
|
33
|
+
Install using pip:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
pip install roysh
|
37
|
+
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
Start the shell by running:
|
42
|
+
|
43
|
+
```bash
|
44
|
+
roysh
|
45
|
+
```
|
46
|
+
|
47
|
+
### Built-in Commands
|
48
|
+
|
49
|
+
| Command | Description |
|
50
|
+
|---------|-------------|
|
51
|
+
| `echo [text]` | Print text to stdout |
|
52
|
+
| `exit [code]` | Exit the shell (optional status code) |
|
53
|
+
| `type <command>` | Show command type/location |
|
54
|
+
| `pwd` | Print working directory |
|
55
|
+
| `cd [path]` | Change directory (defaults to HOME) |
|
56
|
+
|
57
|
+
### Output Redirection
|
58
|
+
|
59
|
+
Roysh supports standard output redirection operators:
|
60
|
+
|
61
|
+
| Operator | Description |
|
62
|
+
|----------|-------------|
|
63
|
+
| `>` | Redirect stdout to file (overwrite) |
|
64
|
+
| `>>` | Append stdout to file |
|
65
|
+
| `2>` | Redirect stderr to file (overwrite) |
|
66
|
+
| `2>>` | Append stderr to file |
|
67
|
+
|
68
|
+
### Examples
|
69
|
+
|
70
|
+
```bash
|
71
|
+
# Basic command usage
|
72
|
+
$ echo Hello World
|
73
|
+
Hello World
|
74
|
+
|
75
|
+
# Working with directories
|
76
|
+
$ pwd
|
77
|
+
/home/user
|
78
|
+
$ cd /tmp
|
79
|
+
$ pwd
|
80
|
+
/tmp
|
81
|
+
|
82
|
+
# Output redirection
|
83
|
+
$ echo "log entry" >> log.txt
|
84
|
+
$ echo "error message" 2> errors.txt
|
85
|
+
|
86
|
+
# Command information
|
87
|
+
$ type echo
|
88
|
+
echo is a shell builtin
|
89
|
+
$ type python
|
90
|
+
python is /usr/bin/python
|
91
|
+
```
|
92
|
+
|
93
|
+
## Contributing
|
94
|
+
|
95
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
96
|
+
|
97
|
+
## License
|
98
|
+
|
99
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
100
|
+
|
101
|
+
---
|
102
|
+
Developed by Nishan Roy
|
103
|
+
```
|
@@ -0,0 +1,7 @@
|
|
1
|
+
roysh/__init__.py,sha256=8YND5SC_fWqv0C_bZCp0c56HU-wQCUlI1xzBY7fWHq4,49
|
2
|
+
roysh/shell.py,sha256=6MxZBS0PJvN5wxxtgb8gLr8RmDrDmtHwZ_y1qqVzYtU,10667
|
3
|
+
roysh-0.1.dist-info/METADATA,sha256=JwEVOef1tke0J56Vx7zJwSrmXwxkTdQBLmWJLaZAmGk,2306
|
4
|
+
roysh-0.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
5
|
+
roysh-0.1.dist-info/entry_points.txt,sha256=ujz21BagQ6boNinTjOmAxCypk9ShJJW6NimV0Ukh9IY,37
|
6
|
+
roysh-0.1.dist-info/top_level.txt,sha256=g9yff3iyVU5f3JdBFdMYcIcKLRPL7iMCukSbrGmdA3A,6
|
7
|
+
roysh-0.1.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
roysh
|