shell-lite 0.3.3__py3-none-any.whl → 0.3.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.
shell_lite/main.py CHANGED
@@ -8,7 +8,8 @@ import subprocess
8
8
  from .lexer import Lexer
9
9
  from .parser import Parser
10
10
  from .interpreter import Interpreter
11
-
11
+ from .ast_nodes import *
12
+ import json
12
13
  def execute_source(source: str, interpreter: Interpreter):
13
14
  lines = source.split('\n')
14
15
  try:
@@ -16,7 +17,6 @@ def execute_source(source: str, interpreter: Interpreter):
16
17
  tokens = lexer.tokenize()
17
18
  parser = Parser(tokens)
18
19
  statements = parser.parse()
19
-
20
20
  for stmt in statements:
21
21
  interpreter.visit(stmt)
22
22
  except Exception as e:
@@ -25,7 +25,6 @@ def execute_source(source: str, interpreter: Interpreter):
25
25
  if 0 <= e.line-1 < len(lines):
26
26
  print(f" {lines[e.line-1].strip()}")
27
27
  print(f"Exception: {e}")
28
-
29
28
  def run_file(filename: str):
30
29
  if not os.path.exists(filename):
31
30
  print(f"Error: File '{filename}' not found.")
@@ -34,7 +33,6 @@ def run_file(filename: str):
34
33
  source = f.read()
35
34
  interpreter = Interpreter()
36
35
  execute_source(source, interpreter)
37
-
38
36
  def run_repl():
39
37
  interpreter = Interpreter()
40
38
  print("\n" + "="*40)
@@ -43,19 +41,14 @@ def run_repl():
43
41
  print("Version: v0.03.3 | Made by Shrey Naithani")
44
42
  print("Commands: Type 'exit' to quit, 'help' for examples.")
45
43
  print("Note: Terminal commands (like 'shl install') must be run in CMD/PowerShell, not here.")
46
-
47
44
  buffer = []
48
45
  indent_level = 0
49
-
50
46
  while True:
51
47
  try:
52
48
  prompt = "... " if indent_level > 0 else ">>> "
53
49
  line = input(prompt)
54
-
55
50
  if line.strip() == "exit":
56
51
  break
57
-
58
- # Support line continuation with \
59
52
  if line.endswith("\\"):
60
53
  buffer.append(line[:-1])
61
54
  indent_level = 1
@@ -68,12 +61,10 @@ def run_repl():
68
61
  print(' display(tasks) # View the list')
69
62
  print(' \\ # Tip: Use \\ at the end of a line for multi-line typing')
70
63
  continue
71
-
72
64
  if line.strip().startswith("shl"):
73
65
  print("! Hint: You are already INSIDE ShellLite. You don't need to type 'shl' here.")
74
66
  print("! To run terminal commands, exit this REPL first.")
75
67
  continue
76
-
77
68
  if not line:
78
69
  if indent_level > 0:
79
70
  source = "\n".join(buffer)
@@ -81,7 +72,6 @@ def run_repl():
81
72
  buffer = []
82
73
  indent_level = 0
83
74
  continue
84
-
85
75
  if line.strip().endswith(":"):
86
76
  indent_level = 1
87
77
  buffer.append(line)
@@ -93,10 +83,8 @@ def run_repl():
93
83
  execute_source(source, interpreter)
94
84
  buffer = []
95
85
  indent_level = 0
96
-
97
86
  if line.strip():
98
87
  execute_source(line, interpreter)
99
-
100
88
  except KeyboardInterrupt:
101
89
  print("\nExiting...")
102
90
  break
@@ -104,34 +92,24 @@ def run_repl():
104
92
  print(f"Error: {e}")
105
93
  buffer = []
106
94
  indent_level = 0
107
-
108
95
  def install_globally():
109
- """Performs the global PATH installation."""
110
96
  print("\n" + "="*50)
111
97
  print(" ShellLite Global Installer")
112
98
  print("="*50)
113
-
114
99
  install_dir = os.path.join(os.environ['LOCALAPPDATA'], 'ShellLite')
115
100
  if not os.path.exists(install_dir):
116
101
  os.makedirs(install_dir)
117
-
118
102
  target_exe = os.path.join(install_dir, 'shl.exe')
119
103
  current_path = sys.executable
120
-
121
- # If not running as EXE (e.g. py script), we can't 'install' the script easily as shl.exe
122
104
  is_frozen = getattr(sys, 'frozen', False)
123
-
124
105
  try:
125
106
  if is_frozen:
126
107
  shutil.copy2(current_path, target_exe)
127
108
  else:
128
109
  print("Error: Installation requires the shl.exe file.")
129
110
  return
130
-
131
- # Add to PATH (User level)
132
111
  ps_cmd = f'$oldPath = [Environment]::GetEnvironmentVariable("Path", "User"); if ($oldPath -notlike "*ShellLite*") {{ [Environment]::SetEnvironmentVariable("Path", "$oldPath;{install_dir}", "User") }}'
133
112
  subprocess.run(["powershell", "-Command", ps_cmd], capture_output=True)
134
-
135
113
  print(f"\n[SUCCESS] ShellLite is now installed!")
136
114
  print(f"Location: {install_dir}")
137
115
  print("\nACTION REQUIRED:")
@@ -142,70 +120,45 @@ def install_globally():
142
120
  input("Press Enter to continue...")
143
121
  except Exception as e:
144
122
  print(f"Installation failed: {e}")
145
-
146
123
  def install_package(package_name: str):
147
- """
148
- Downloads a package from GitHub.
149
- Format: shl get user/repo
150
- Target: ~/.shell_lite/modules/<repo>/ (containing main.shl or similar)
151
- """
152
124
  if '/' not in package_name:
153
125
  print("Error: Package must be in format 'user/repo'")
154
126
  return
155
-
156
127
  user, repo = package_name.split('/')
157
128
  print(f"Fetching '{package_name}' from GitHub...")
158
-
159
- # Define modules dir
160
129
  home = os.path.expanduser("~")
161
130
  modules_dir = os.path.join(home, ".shell_lite", "modules")
162
131
  if not os.path.exists(modules_dir):
163
132
  os.makedirs(modules_dir)
164
-
165
- # Clean up existing
166
133
  target_dir = os.path.join(modules_dir, repo)
167
134
  if os.path.exists(target_dir):
168
135
  print(f"Removing existing '{repo}'...")
169
136
  shutil.rmtree(target_dir)
170
-
171
- # Strategy 1: Download Main Branch ZIP
172
137
  zip_url = f"https://github.com/{user}/{repo}/archive/refs/heads/main.zip"
173
-
174
138
  try:
175
139
  print(f"Downloading {zip_url}...")
176
140
  with urllib.request.urlopen(zip_url) as response:
177
141
  zip_data = response.read()
178
-
179
142
  with zipfile.ZipFile(io.BytesIO(zip_data)) as z:
180
143
  z.extractall(modules_dir)
181
-
182
- # Rename extracted folder (repo-main -> repo)
183
144
  extracted_name = f"{repo}-main"
184
145
  extracted_path = os.path.join(modules_dir, extracted_name)
185
-
186
146
  if os.path.exists(extracted_path):
187
147
  os.rename(extracted_path, target_dir)
188
148
  print(f"[SUCCESS] Installed '{repo}' to {target_dir}")
189
149
  else:
190
150
  print(f"Error: Could not find extracted folder '{extracted_name}'.")
191
-
192
151
  return
193
-
194
152
  except urllib.error.HTTPError:
195
- # Strategy 2: Try Master Branch
196
153
  zip_url = f"https://github.com/{user}/{repo}/archive/refs/heads/master.zip"
197
154
  try:
198
155
  print(f"Downloading {zip_url}...")
199
156
  with urllib.request.urlopen(zip_url) as response:
200
157
  zip_data = response.read()
201
-
202
158
  with zipfile.ZipFile(io.BytesIO(zip_data)) as z:
203
159
  z.extractall(modules_dir)
204
-
205
- # Rename extracted folder (repo-master -> repo)
206
160
  extracted_name = f"{repo}-master"
207
161
  extracted_path = os.path.join(modules_dir, extracted_name)
208
-
209
162
  if os.path.exists(extracted_path):
210
163
  os.rename(extracted_path, target_dir)
211
164
  print(f"[SUCCESS] Installed '{repo}' to {target_dir}")
@@ -213,30 +166,22 @@ def install_package(package_name: str):
213
166
  print(f"Error: Could not find extracted folder '{extracted_name}'.")
214
167
  except Exception as e:
215
168
  print(f"Installation failed: {e}")
216
-
217
169
  except Exception as e:
218
170
  print(f"Installation failed: {e}")
219
-
220
-
221
171
  def compile_file(filename: str, target: str = 'python'):
222
172
  if not os.path.exists(filename):
223
173
  print(f"Error: File '{filename}' not found.")
224
174
  return
225
-
226
175
  print(f"Compiling {filename} to {target.upper()}...")
227
-
228
176
  with open(filename, 'r', encoding='utf-8') as f:
229
177
  source = f.read()
230
-
231
178
  try:
232
179
  from .parser import Parser
233
180
  from .lexer import Lexer
234
-
235
181
  lexer = Lexer(source)
236
182
  tokens = lexer.tokenize()
237
183
  parser = Parser(tokens)
238
184
  statements = parser.parse()
239
-
240
185
  if target.lower() == 'js':
241
186
  from .js_compiler import JSCompiler
242
187
  compiler = JSCompiler()
@@ -247,17 +192,12 @@ def compile_file(filename: str, target: str = 'python'):
247
192
  compiler = Compiler()
248
193
  code = compiler.compile(statements)
249
194
  ext = '.py'
250
-
251
195
  output_file = filename.replace('.shl', ext)
252
196
  if output_file == filename: output_file += ext
253
-
254
197
  with open(output_file, 'w') as f:
255
198
  f.write(code)
256
-
257
199
  print(f"[SUCCESS] Transpiled to {output_file}")
258
-
259
200
  if target.lower() == 'python':
260
- # Optional: Compile to EXE using PyInstaller
261
201
  try:
262
202
  import PyInstaller.__main__
263
203
  print("Building Executable with PyInstaller...")
@@ -269,23 +209,114 @@ def compile_file(filename: str, target: str = 'python'):
269
209
  ])
270
210
  print(f"[SUCCESS] Built {os.path.splitext(os.path.basename(filename))[0]}.exe")
271
211
  except ImportError:
272
- pass # Silent fail regarding exe if just transpiling
273
-
212
+ pass
274
213
  except Exception as e:
275
214
  print(f"Compilation Failed: {e}")
276
-
277
-
215
+ def lint_file(filename: str):
216
+ if not os.path.exists(filename):
217
+ print(json.dumps([{"line": 0, "message": f"File {filename} not found"}]))
218
+ return
219
+ try:
220
+ with open(filename, 'r', encoding='utf-8') as f:
221
+ source = f.read()
222
+ lexer = Lexer(source)
223
+ tokens = lexer.tokenize()
224
+ parser = Parser(tokens)
225
+ parser.parse()
226
+ print(json.dumps([]))
227
+ except Exception as e:
228
+ line = getattr(e, 'line', 1)
229
+ print(json.dumps([{
230
+ "line": line,
231
+ "message": str(e)
232
+ }]))
233
+ def resolve_cursor(filename: str, line: int, col: int):
234
+ try:
235
+ with open(filename, 'r', encoding='utf-8') as f:
236
+ source = f.read()
237
+ lexer = Lexer(source)
238
+ tokens = lexer.tokenize()
239
+ parser = Parser(tokens)
240
+ nodes = parser.parse()
241
+ target_token = None
242
+ for t in tokens:
243
+ if t.line == line:
244
+ if t.column <= col <= t.column + len(t.value):
245
+ target_token = t
246
+ break
247
+ if not target_token or target_token.type != 'ID':
248
+ print(json.dumps({"found": False}))
249
+ return
250
+ word = target_token.value
251
+ def find_def(n_list, name):
252
+ for node in n_list:
253
+ if isinstance(node, FunctionDef) and node.name == name:
254
+ return node, "Function"
255
+ if isinstance(node, ClassDef) and node.name == name:
256
+ return node, "Class"
257
+ if isinstance(node, Assign) and node.name == name:
258
+ return node, "Variable"
259
+ if isinstance(node, If):
260
+ res = find_def(node.body, name)
261
+ if res: return res
262
+ return None, None
263
+ found_node = None
264
+ found_type = None
265
+ queue = nodes[:]
266
+ while queue:
267
+ n = queue.pop(0)
268
+ if isinstance(n, FunctionDef):
269
+ if n.name == word:
270
+ found_node = n
271
+ found_type = "Function"
272
+ break
273
+ queue.extend(n.body)
274
+ elif isinstance(n, ClassDef):
275
+ if n.name == word:
276
+ found_node = n
277
+ found_type = "Class"
278
+ break
279
+ queue.extend(n.methods)
280
+ elif isinstance(n, Assign) and n.name == word:
281
+ found_node = n
282
+ found_type = "Variable"
283
+ break
284
+ elif isinstance(n, If): queue.extend(n.body)
285
+ elif isinstance(n, While): queue.extend(n.body)
286
+ elif isinstance(n, For): queue.extend(n.body)
287
+ if found_node:
288
+ print(json.dumps({
289
+ "found": True,
290
+ "file": filename,
291
+ "line": found_node.line,
292
+ "hover": f"**{found_type}** `{word}`"
293
+ }))
294
+ else:
295
+ print(json.dumps({"found": False}))
296
+ except Exception:
297
+ print(json.dumps({"found": False}))
298
+ def format_file(filename: str):
299
+ if not os.path.exists(filename):
300
+ print(f"Error: File '{filename}' not found.")
301
+ return
302
+ try:
303
+ with open(filename, 'r', encoding='utf-8') as f:
304
+ source = f.read()
305
+ from .formatter import Formatter
306
+ formatter = Formatter(source)
307
+ formatted_code = formatter.format()
308
+ with open(filename, 'w', encoding='utf-8') as f:
309
+ f.write(formatted_code)
310
+ print(f"[SUCCESS] Formatted {filename}")
311
+ except Exception as e:
312
+ print(f"Formatting failed: {e}")
278
313
  def self_install_check():
279
- """Checks if shl is in PATH, if not, offer to install it."""
280
- # Simple check: is shl.exe in a known Global path?
281
- # Or just check if 'shl' works in shell
282
314
  res = subprocess.run(["where", "shl"], capture_output=True, text=True)
283
315
  if "ShellLite" not in res.stdout:
284
316
  print("\nShellLite is not installed globally.")
285
317
  choice = input("Would you like to install it so 'shl' works everywhere? (y/n): ").lower()
286
318
  if choice == 'y':
287
319
  install_globally()
288
-
289
320
  def show_help():
290
321
  print("""
291
322
  ShellLite - The English-Like Programming Language
@@ -294,11 +325,12 @@ Usage:
294
325
  shl Start the interactive REPL
295
326
  shl help Show this help message
296
327
  shl compile <file> Compile a script (Options: --target js)
328
+ shl fmt <file> Format a script
329
+ shl check <file> Lint a file (JSON output)
330
+ shl resolve <file> <line> <col> Resolve symbol (JSON output)
297
331
  shl install Install ShellLite globally to your system PATH
298
-
299
332
  For documentation, visit: https://github.com/Shrey-N/ShellDesk
300
333
  """)
301
-
302
334
  def main():
303
335
  if len(sys.argv) > 1:
304
336
  cmd = sys.argv[1]
@@ -325,14 +357,27 @@ def main():
325
357
  else:
326
358
  print("Usage: shl get <user/repo>")
327
359
  elif cmd == "install":
328
- # Install ShellLite itself
329
360
  install_globally()
361
+ elif cmd == "fmt" or cmd == "format":
362
+ if len(sys.argv) > 2:
363
+ filename = sys.argv[2]
364
+ format_file(filename)
365
+ else:
366
+ print("Usage: shl fmt <filename>")
367
+ elif cmd == "check":
368
+ if len(sys.argv) > 2:
369
+ filename = sys.argv[2]
370
+ lint_file(filename)
371
+ elif cmd == "resolve":
372
+ if len(sys.argv) > 4:
373
+ filename = sys.argv[2]
374
+ line = int(sys.argv[3])
375
+ col = int(sys.argv[4])
376
+ resolve_cursor(filename, line, col)
330
377
  else:
331
378
  run_file(sys.argv[1])
332
379
  else:
333
- # No args - trigger install check, then REPL
334
380
  self_install_check()
335
381
  run_repl()
336
-
337
382
  if __name__ == "__main__":
338
383
  main()