ai-pack-cli 0.1.2__tar.gz → 0.1.7__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.
- {ai_pack_cli-0.1.2/ai_pack_cli.egg-info → ai_pack_cli-0.1.7}/PKG-INFO +17 -2
- ai_pack_cli-0.1.2/PKG-INFO → ai_pack_cli-0.1.7/README.md +14 -12
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/ai_pack.py +33 -205
- ai_pack_cli-0.1.2/README.md → ai_pack_cli-0.1.7/ai_pack_cli.egg-info/PKG-INFO +27 -1
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/ai_pack_cli.egg-info/SOURCES.txt +1 -2
- ai_pack_cli-0.1.7/ai_pack_cli.egg-info/requires.txt +8 -0
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/setup.py +3 -1
- ai_pack_cli-0.1.7/tests/test_skeleton.py +41 -0
- ai_pack_cli-0.1.2/ai_pack_cli.egg-info/requires.txt +0 -2
- ai_pack_cli-0.1.2/tests/test_skeleton_braces.py +0 -139
- ai_pack_cli-0.1.2/tests/test_skeleton_python.py +0 -63
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/ai_pack_cli.egg-info/dependency_links.txt +0 -0
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/ai_pack_cli.egg-info/entry_points.txt +0 -0
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/ai_pack_cli.egg-info/top_level.txt +0 -0
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/setup.cfg +0 -0
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/tests/test_binary.py +0 -0
- {ai_pack_cli-0.1.2 → ai_pack_cli-0.1.7}/tests/test_gitignore.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ai-pack-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Home-page: https://github.com/iamraydoan/ai-pack
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -8,6 +8,8 @@ Classifier: Operating System :: OS Independent
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
Requires-Dist: pyperclip>=1.8.2
|
|
10
10
|
Requires-Dist: questionary>=1.10.0
|
|
11
|
+
Requires-Dist: codesigs>=0.0.1; python_version >= "3.9"
|
|
12
|
+
Requires-Dist: ast-grep-py<=0.39.7; python_version == "3.9"
|
|
11
13
|
|
|
12
14
|
# 📦 ai-pack
|
|
13
15
|
|
|
@@ -72,6 +74,14 @@ curl -o ~/.local/bin/aip https://raw.githubusercontent.com/iamraydoan/ai-pack/ma
|
|
|
72
74
|
```
|
|
73
75
|
*(Note: If you run it standalone, you will need to manually run `pip3 install pyperclip questionary` to enable all optional interactive and clipboard features).*
|
|
74
76
|
|
|
77
|
+
> [!TIP]
|
|
78
|
+
> **Optional backend for Skeleton Mode (`--skeleton`)**:
|
|
79
|
+
> If you plan to use skeleton extraction for non-Python languages (such as JavaScript, TypeScript, Go, Rust, Java, C#, C++, PHP, Lua, CSS, Swift, and Kotlin), you must install `ast-grep` globally:
|
|
80
|
+
> ```bash
|
|
81
|
+
> npm install -g @ast-grep/cli
|
|
82
|
+
> # or: cargo install ast-grep
|
|
83
|
+
> ```
|
|
84
|
+
|
|
75
85
|
|
|
76
86
|
### Usage Examples
|
|
77
87
|
|
|
@@ -123,7 +133,12 @@ def fibonacci(n):
|
|
|
123
133
|
...
|
|
124
134
|
```
|
|
125
135
|
|
|
126
|
-
*Supports Python
|
|
136
|
+
*Supports Python, JavaScript, TypeScript, Go, Rust, Java, C#, C++, PHP, Lua, CSS, Swift, and Kotlin via codesigs and ast-grep.*
|
|
137
|
+
|
|
138
|
+
> [!IMPORTANT]
|
|
139
|
+
> **Skeleton Mode Dependencies**:
|
|
140
|
+
> * **Python >= 3.9**: Required to run the `--skeleton` feature.
|
|
141
|
+
> * **ast-grep**: For non-Python languages, the `ast-grep` command-line tool must be installed globally (e.g. via npm: `npm install -g @ast-grep/cli` or cargo: `cargo install ast-grep`).
|
|
127
142
|
|
|
128
143
|
---
|
|
129
144
|
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: ai-pack-cli
|
|
3
|
-
Version: 0.1.2
|
|
4
|
-
Home-page: https://github.com/iamraydoan/ai-pack
|
|
5
|
-
Classifier: Programming Language :: Python :: 3
|
|
6
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
-
Classifier: Operating System :: OS Independent
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: pyperclip>=1.8.2
|
|
10
|
-
Requires-Dist: questionary>=1.10.0
|
|
11
|
-
|
|
12
1
|
# 📦 ai-pack
|
|
13
2
|
|
|
14
3
|
[](https://www.python.org/)
|
|
@@ -72,6 +61,14 @@ curl -o ~/.local/bin/aip https://raw.githubusercontent.com/iamraydoan/ai-pack/ma
|
|
|
72
61
|
```
|
|
73
62
|
*(Note: If you run it standalone, you will need to manually run `pip3 install pyperclip questionary` to enable all optional interactive and clipboard features).*
|
|
74
63
|
|
|
64
|
+
> [!TIP]
|
|
65
|
+
> **Optional backend for Skeleton Mode (`--skeleton`)**:
|
|
66
|
+
> If you plan to use skeleton extraction for non-Python languages (such as JavaScript, TypeScript, Go, Rust, Java, C#, C++, PHP, Lua, CSS, Swift, and Kotlin), you must install `ast-grep` globally:
|
|
67
|
+
> ```bash
|
|
68
|
+
> npm install -g @ast-grep/cli
|
|
69
|
+
> # or: cargo install ast-grep
|
|
70
|
+
> ```
|
|
71
|
+
|
|
75
72
|
|
|
76
73
|
### Usage Examples
|
|
77
74
|
|
|
@@ -123,7 +120,12 @@ def fibonacci(n):
|
|
|
123
120
|
...
|
|
124
121
|
```
|
|
125
122
|
|
|
126
|
-
*Supports Python
|
|
123
|
+
*Supports Python, JavaScript, TypeScript, Go, Rust, Java, C#, C++, PHP, Lua, CSS, Swift, and Kotlin via codesigs and ast-grep.*
|
|
124
|
+
|
|
125
|
+
> [!IMPORTANT]
|
|
126
|
+
> **Skeleton Mode Dependencies**:
|
|
127
|
+
> * **Python >= 3.9**: Required to run the `--skeleton` feature.
|
|
128
|
+
> * **ast-grep**: For non-Python languages, the `ast-grep` command-line tool must be installed globally (e.g. via npm: `npm install -g @ast-grep/cli` or cargo: `cargo install ast-grep`).
|
|
127
129
|
|
|
128
130
|
---
|
|
129
131
|
|
|
@@ -195,211 +195,31 @@ class SkeletonExtractor:
|
|
|
195
195
|
|
|
196
196
|
@staticmethod
|
|
197
197
|
def get_skeleton(file_path: str, content: str) -> str:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
try:
|
|
199
|
+
from codesigs import file_sigs
|
|
200
|
+
sigs = file_sigs(file_path)
|
|
201
|
+
if sigs is not None:
|
|
202
|
+
return "\n".join(sigs)
|
|
203
|
+
except ImportError:
|
|
204
|
+
print("⚠️ Error: 'codesigs' package is required for --skeleton mode.", file=sys.stderr)
|
|
205
|
+
print("💡 Please install it using: pip install ai-pack-cli (requires Python >= 3.9)", file=sys.stderr)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f"⚠️ codesigs failed for {file_path}: {e}", file=sys.stderr)
|
|
208
|
+
return content
|
|
206
209
|
|
|
207
|
-
@staticmethod
|
|
208
|
-
def extract_python_skeleton(content: str) -> str:
|
|
209
|
-
lines = content.splitlines()
|
|
210
|
-
skeleton_lines = []
|
|
211
|
-
i = 0
|
|
212
|
-
n = len(lines)
|
|
213
|
-
decorator_buffer = []
|
|
214
|
-
|
|
215
|
-
while i < n:
|
|
216
|
-
line = lines[i]
|
|
217
|
-
stripped = line.strip()
|
|
218
|
-
|
|
219
|
-
# Keep track of decorators
|
|
220
|
-
if stripped.startswith("@"):
|
|
221
|
-
decorator_buffer.append(line)
|
|
222
|
-
i += 1
|
|
223
|
-
continue
|
|
224
|
-
|
|
225
|
-
# If function signature
|
|
226
|
-
if stripped.startswith("def ") or stripped.startswith("async def "):
|
|
227
|
-
if decorator_buffer:
|
|
228
|
-
skeleton_lines.extend(decorator_buffer)
|
|
229
|
-
decorator_buffer = []
|
|
230
|
-
|
|
231
|
-
indent = len(line) - len(line.lstrip())
|
|
232
|
-
sig_lines = []
|
|
233
|
-
|
|
234
|
-
# Consume multi-line signatures by tracking open parentheses/brackets
|
|
235
|
-
open_braces = 0
|
|
236
|
-
while i < n:
|
|
237
|
-
line_to_add = lines[i]
|
|
238
|
-
sig_lines.append(line_to_add)
|
|
239
|
-
open_braces += line_to_add.count('(') - line_to_add.count(')')
|
|
240
|
-
open_braces += line_to_add.count('[') - line_to_add.count(']')
|
|
241
|
-
open_braces += line_to_add.count('{') - line_to_add.count('}')
|
|
242
|
-
|
|
243
|
-
if open_braces <= 0 and ":" in line_to_add:
|
|
244
|
-
break
|
|
245
|
-
i += 1
|
|
246
|
-
|
|
247
|
-
skeleton_lines.extend(sig_lines)
|
|
248
|
-
|
|
249
|
-
# Skip the function body (all lines indented further than indent)
|
|
250
|
-
i += 1
|
|
251
|
-
body_skipped = False
|
|
252
|
-
while i < n:
|
|
253
|
-
next_line = lines[i]
|
|
254
|
-
next_stripped = next_line.strip()
|
|
255
|
-
if not next_stripped:
|
|
256
|
-
i += 1
|
|
257
|
-
continue
|
|
258
|
-
next_indent = len(next_line) - len(next_line.lstrip())
|
|
259
|
-
if next_indent <= indent:
|
|
260
|
-
break
|
|
261
|
-
body_skipped = True
|
|
262
|
-
i += 1
|
|
263
|
-
|
|
264
|
-
if body_skipped:
|
|
265
|
-
skeleton_lines.append(" " * (indent + 4) + "...")
|
|
266
|
-
decorator_buffer = []
|
|
267
|
-
else:
|
|
268
|
-
if decorator_buffer:
|
|
269
|
-
skeleton_lines.extend(decorator_buffer)
|
|
270
|
-
decorator_buffer = []
|
|
271
|
-
skeleton_lines.append(line)
|
|
272
|
-
i += 1
|
|
273
|
-
|
|
274
|
-
return "\n".join(skeleton_lines)
|
|
275
210
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
while i < n:
|
|
290
|
-
c = content[i]
|
|
291
|
-
|
|
292
|
-
# --- 1. Handle Comments ---
|
|
293
|
-
if in_comment == 'single':
|
|
294
|
-
if c == '\n':
|
|
295
|
-
in_comment = None
|
|
296
|
-
if skip_until_brace_depth is None:
|
|
297
|
-
result.append(c)
|
|
298
|
-
i += 1
|
|
299
|
-
continue
|
|
300
|
-
elif in_comment == 'multi':
|
|
301
|
-
if c == '/' and i > 0 and content[i-1] == '*':
|
|
302
|
-
in_comment = None
|
|
303
|
-
i += 1
|
|
304
|
-
continue
|
|
305
|
-
|
|
306
|
-
# --- 2. Handle Strings ---
|
|
307
|
-
if in_string:
|
|
308
|
-
if c == '\\' and i + 1 < n:
|
|
309
|
-
escaped_char = content[i:i+2]
|
|
310
|
-
if skip_until_brace_depth is None:
|
|
311
|
-
result.append(escaped_char)
|
|
312
|
-
prefix_buffer.append(escaped_char)
|
|
313
|
-
i += 2
|
|
314
|
-
continue
|
|
315
|
-
if skip_until_brace_depth is None:
|
|
316
|
-
result.append(c)
|
|
317
|
-
prefix_buffer.append(c)
|
|
318
|
-
if c == in_string:
|
|
319
|
-
in_string = None
|
|
320
|
-
i += 1
|
|
321
|
-
continue
|
|
322
|
-
|
|
323
|
-
# --- 3. Detect Comment Start (when not in string/comment) ---
|
|
324
|
-
if c == '/' and i + 1 < n:
|
|
325
|
-
next_c = content[i+1]
|
|
326
|
-
if next_c == '/':
|
|
327
|
-
in_comment = 'single'
|
|
328
|
-
i += 2
|
|
329
|
-
continue
|
|
330
|
-
elif next_c == '*':
|
|
331
|
-
in_comment = 'multi'
|
|
332
|
-
i += 2
|
|
333
|
-
continue
|
|
334
|
-
|
|
335
|
-
# --- 4. Detect String Start (when not in string/comment) ---
|
|
336
|
-
if c in ('"', "'", "`"):
|
|
337
|
-
in_string = c
|
|
338
|
-
if skip_until_brace_depth is None:
|
|
339
|
-
result.append(c)
|
|
340
|
-
prefix_buffer.append(c)
|
|
341
|
-
i += 1
|
|
342
|
-
continue
|
|
343
|
-
|
|
344
|
-
# --- 5. Brace & Skipping Logic ---
|
|
345
|
-
if skip_until_brace_depth is not None:
|
|
346
|
-
if c == '{':
|
|
347
|
-
brace_depth += 1
|
|
348
|
-
elif c == '}':
|
|
349
|
-
brace_depth -= 1
|
|
350
|
-
if brace_depth == skip_until_brace_depth:
|
|
351
|
-
result.append(' { /* ... */ }')
|
|
352
|
-
skip_until_brace_depth = None
|
|
353
|
-
i += 1
|
|
354
|
-
continue
|
|
355
|
-
|
|
356
|
-
# Normal code processing
|
|
357
|
-
if c == '{':
|
|
358
|
-
prefix_str = "".join(prefix_buffer).strip()
|
|
359
|
-
is_func = False
|
|
360
|
-
|
|
361
|
-
if '=>' in prefix_str:
|
|
362
|
-
is_func = True
|
|
363
|
-
elif 'function' in prefix_str:
|
|
364
|
-
is_func = True
|
|
365
|
-
elif 'constructor' in prefix_str:
|
|
366
|
-
is_func = True
|
|
367
|
-
elif any(x in prefix_str for x in ('fn ', 'func ')):
|
|
368
|
-
is_func = True
|
|
369
|
-
elif '(' in prefix_str and ')' in prefix_str:
|
|
370
|
-
first_paren = prefix_str.find('(')
|
|
371
|
-
name_part = prefix_str[:first_paren].strip()
|
|
372
|
-
words = name_part.split()
|
|
373
|
-
if words:
|
|
374
|
-
clean_words = [w for w in words if w not in (
|
|
375
|
-
'export', 'async', 'default', 'public', 'private', 'protected', 'static', 'readonly'
|
|
376
|
-
)]
|
|
377
|
-
if clean_words:
|
|
378
|
-
first_word = clean_words[0].strip()
|
|
379
|
-
if first_word not in ('if', 'for', 'while', 'switch', 'catch'):
|
|
380
|
-
is_func = True
|
|
381
|
-
|
|
382
|
-
if is_func:
|
|
383
|
-
skip_until_brace_depth = brace_depth
|
|
384
|
-
brace_depth += 1
|
|
385
|
-
prefix_buffer = []
|
|
386
|
-
else:
|
|
387
|
-
brace_depth += 1
|
|
388
|
-
result.append(c)
|
|
389
|
-
prefix_buffer = []
|
|
390
|
-
elif c == '}':
|
|
391
|
-
brace_depth -= 1
|
|
392
|
-
result.append(c)
|
|
393
|
-
prefix_buffer = []
|
|
394
|
-
else:
|
|
395
|
-
result.append(c)
|
|
396
|
-
if c == '\n':
|
|
397
|
-
prefix_buffer = []
|
|
398
|
-
else:
|
|
399
|
-
prefix_buffer.append(c)
|
|
400
|
-
i += 1
|
|
401
|
-
|
|
402
|
-
return "".join(result)
|
|
211
|
+
def copy_via_osc52(payload: str) -> bool:
|
|
212
|
+
"""Attempts to copy text to the client system clipboard using OSC 52 escape sequence."""
|
|
213
|
+
try:
|
|
214
|
+
import base64
|
|
215
|
+
# Base64 encode the payload
|
|
216
|
+
b64_data = base64.b64encode(payload.encode("utf-8")).decode("ascii")
|
|
217
|
+
# Write to stdout using the OSC 52 escape sequence: \x1b]52;c;[base64]\x07
|
|
218
|
+
sys.stdout.write(f"\x1b]52;c;{b64_data}\x07")
|
|
219
|
+
sys.stdout.flush()
|
|
220
|
+
return True
|
|
221
|
+
except Exception:
|
|
222
|
+
return False
|
|
403
223
|
|
|
404
224
|
|
|
405
225
|
def walk_dir(directory: str, git_files: Set[str] = None, gitignore: GitignoreMatcher = None) -> List[str]:
|
|
@@ -656,12 +476,20 @@ def main():
|
|
|
656
476
|
print(f"❌ Error saving to file {args.output}: {e}", file=sys.stderr)
|
|
657
477
|
sys.exit(1)
|
|
658
478
|
else:
|
|
479
|
+
copied = False
|
|
659
480
|
try:
|
|
660
481
|
import pyperclip
|
|
661
482
|
pyperclip.copy(payload)
|
|
483
|
+
copied = True
|
|
662
484
|
print("🚀 Successfully copied to clipboard!")
|
|
663
|
-
except Exception
|
|
664
|
-
|
|
485
|
+
except Exception:
|
|
486
|
+
# Fallback to OSC 52 (works seamlessly over SSH / remote terminal sessions)
|
|
487
|
+
if copy_via_osc52(payload):
|
|
488
|
+
copied = True
|
|
489
|
+
print("🚀 Successfully copied to clipboard (via OSC 52 remote sequence)!")
|
|
490
|
+
|
|
491
|
+
if not copied:
|
|
492
|
+
print("❌ Error copying to clipboard: Pyperclip could not find a copy/paste mechanism.", file=sys.stderr)
|
|
665
493
|
print("💡 In headless Linux systems, you must install 'xclip' or 'xsel', or direct output to a file using the '-o' flag.", file=sys.stderr)
|
|
666
494
|
sys.exit(1)
|
|
667
495
|
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ai-pack-cli
|
|
3
|
+
Version: 0.1.7
|
|
4
|
+
Home-page: https://github.com/iamraydoan/ai-pack
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pyperclip>=1.8.2
|
|
10
|
+
Requires-Dist: questionary>=1.10.0
|
|
11
|
+
Requires-Dist: codesigs>=0.0.1; python_version >= "3.9"
|
|
12
|
+
Requires-Dist: ast-grep-py<=0.39.7; python_version == "3.9"
|
|
13
|
+
|
|
1
14
|
# 📦 ai-pack
|
|
2
15
|
|
|
3
16
|
[](https://www.python.org/)
|
|
@@ -61,6 +74,14 @@ curl -o ~/.local/bin/aip https://raw.githubusercontent.com/iamraydoan/ai-pack/ma
|
|
|
61
74
|
```
|
|
62
75
|
*(Note: If you run it standalone, you will need to manually run `pip3 install pyperclip questionary` to enable all optional interactive and clipboard features).*
|
|
63
76
|
|
|
77
|
+
> [!TIP]
|
|
78
|
+
> **Optional backend for Skeleton Mode (`--skeleton`)**:
|
|
79
|
+
> If you plan to use skeleton extraction for non-Python languages (such as JavaScript, TypeScript, Go, Rust, Java, C#, C++, PHP, Lua, CSS, Swift, and Kotlin), you must install `ast-grep` globally:
|
|
80
|
+
> ```bash
|
|
81
|
+
> npm install -g @ast-grep/cli
|
|
82
|
+
> # or: cargo install ast-grep
|
|
83
|
+
> ```
|
|
84
|
+
|
|
64
85
|
|
|
65
86
|
### Usage Examples
|
|
66
87
|
|
|
@@ -112,7 +133,12 @@ def fibonacci(n):
|
|
|
112
133
|
...
|
|
113
134
|
```
|
|
114
135
|
|
|
115
|
-
*Supports Python
|
|
136
|
+
*Supports Python, JavaScript, TypeScript, Go, Rust, Java, C#, C++, PHP, Lua, CSS, Swift, and Kotlin via codesigs and ast-grep.*
|
|
137
|
+
|
|
138
|
+
> [!IMPORTANT]
|
|
139
|
+
> **Skeleton Mode Dependencies**:
|
|
140
|
+
> * **Python >= 3.9**: Required to run the `--skeleton` feature.
|
|
141
|
+
> * **ast-grep**: For non-Python languages, the `ast-grep` command-line tool must be installed globally (e.g. via npm: `npm install -g @ast-grep/cli` or cargo: `cargo install ast-grep`).
|
|
116
142
|
|
|
117
143
|
---
|
|
118
144
|
|
|
@@ -6,11 +6,13 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="ai-pack-cli",
|
|
9
|
-
version="0.1.
|
|
9
|
+
version="0.1.7",
|
|
10
10
|
py_modules=["ai_pack"],
|
|
11
11
|
install_requires=[
|
|
12
12
|
"pyperclip>=1.8.2",
|
|
13
13
|
"questionary>=1.10.0",
|
|
14
|
+
'codesigs>=0.0.1; python_version >= "3.9"',
|
|
15
|
+
'ast-grep-py<=0.39.7; python_version == "3.9"',
|
|
14
16
|
],
|
|
15
17
|
entry_points={
|
|
16
18
|
"console_scripts": [
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import patch, MagicMock
|
|
3
|
+
import sys
|
|
4
|
+
from io import StringIO
|
|
5
|
+
from ai_pack import SkeletonExtractor
|
|
6
|
+
|
|
7
|
+
class TestSkeletonExtractor(unittest.TestCase):
|
|
8
|
+
@patch('ai_pack.sys.stderr', new_callable=StringIO)
|
|
9
|
+
def test_get_skeleton_success(self, mock_stderr):
|
|
10
|
+
# Mock codesigs module in sys.modules
|
|
11
|
+
mock_codesigs = MagicMock()
|
|
12
|
+
mock_codesigs.file_sigs.return_value = ["def foo():", " ..."]
|
|
13
|
+
|
|
14
|
+
with patch.dict(sys.modules, {'codesigs': mock_codesigs}):
|
|
15
|
+
result = SkeletonExtractor.get_skeleton("test.py", "def foo():\n pass")
|
|
16
|
+
self.assertEqual(result, "def foo():\n ...")
|
|
17
|
+
mock_codesigs.file_sigs.assert_called_once_with("test.py")
|
|
18
|
+
|
|
19
|
+
@patch('ai_pack.sys.stderr', new_callable=StringIO)
|
|
20
|
+
def test_get_skeleton_import_error(self, mock_stderr):
|
|
21
|
+
# Clear sys.modules of any real codesigs import to trigger ImportError
|
|
22
|
+
original_modules = sys.modules.copy()
|
|
23
|
+
if 'codesigs' in sys.modules:
|
|
24
|
+
del sys.modules['codesigs']
|
|
25
|
+
try:
|
|
26
|
+
with patch.dict(sys.modules, {'codesigs': None}):
|
|
27
|
+
result = SkeletonExtractor.get_skeleton("test.py", "def foo():\n pass")
|
|
28
|
+
self.assertEqual(result, "def foo():\n pass")
|
|
29
|
+
self.assertIn("package is required", mock_stderr.getvalue())
|
|
30
|
+
finally:
|
|
31
|
+
sys.modules = original_modules
|
|
32
|
+
|
|
33
|
+
@patch('ai_pack.sys.stderr', new_callable=StringIO)
|
|
34
|
+
def test_get_skeleton_runtime_error(self, mock_stderr):
|
|
35
|
+
mock_codesigs = MagicMock()
|
|
36
|
+
mock_codesigs.file_sigs.side_effect = Exception("ast-grep not found")
|
|
37
|
+
|
|
38
|
+
with patch.dict(sys.modules, {'codesigs': mock_codesigs}):
|
|
39
|
+
result = SkeletonExtractor.get_skeleton("test.py", "def foo():\n pass")
|
|
40
|
+
self.assertEqual(result, "def foo():\n pass")
|
|
41
|
+
self.assertIn("codesigs failed", mock_stderr.getvalue())
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from ai_pack import SkeletonExtractor
|
|
3
|
-
|
|
4
|
-
class TestBraceSkeletonExtractor(unittest.TestCase):
|
|
5
|
-
def test_javascript_typescript(self):
|
|
6
|
-
js_code = """import { component } from 'framework';
|
|
7
|
-
const PORT = 8080;
|
|
8
|
-
|
|
9
|
-
export default class Widget extends Base {
|
|
10
|
-
constructor(config) {
|
|
11
|
-
super(config);
|
|
12
|
-
this.init();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
render() {
|
|
16
|
-
return `<div>${this.config.title}</div>`;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const add = (a, b) => {
|
|
21
|
-
return a + b;
|
|
22
|
-
};
|
|
23
|
-
"""
|
|
24
|
-
expected = """import { component } from 'framework';
|
|
25
|
-
const PORT = 8080;
|
|
26
|
-
|
|
27
|
-
export default class Widget extends Base {
|
|
28
|
-
constructor(config) { /* ... */ }
|
|
29
|
-
|
|
30
|
-
render() { /* ... */ }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const add = (a, b) => { /* ... */ };"""
|
|
34
|
-
self.assertEqual(SkeletonExtractor.extract_brace_skeleton(js_code).strip(), expected.strip())
|
|
35
|
-
|
|
36
|
-
def test_js_template_literals_with_braces(self):
|
|
37
|
-
js_code = """const render = (name) => {
|
|
38
|
-
return `Hello, ${name}! Your age is ${getAge({birth: 1990})}.`;
|
|
39
|
-
};
|
|
40
|
-
"""
|
|
41
|
-
expected = """const render = (name) => { /* ... */ };"""
|
|
42
|
-
self.assertEqual(SkeletonExtractor.extract_brace_skeleton(js_code).strip(), expected.strip())
|
|
43
|
-
|
|
44
|
-
def test_golang_parser(self):
|
|
45
|
-
go_code = """package main
|
|
46
|
-
import "fmt"
|
|
47
|
-
|
|
48
|
-
type Server struct {
|
|
49
|
-
Port int
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
func NewServer(port int) *Server {
|
|
53
|
-
return &Server{Port: port}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
func (s *Server) Start() error {
|
|
57
|
-
fmt.Println("Starting...")
|
|
58
|
-
return nil
|
|
59
|
-
}
|
|
60
|
-
"""
|
|
61
|
-
expected = """package main
|
|
62
|
-
import "fmt"
|
|
63
|
-
|
|
64
|
-
type Server struct {
|
|
65
|
-
Port int
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func NewServer(port int) *Server { /* ... */ }
|
|
69
|
-
|
|
70
|
-
func (s *Server) Start() error { /* ... */ }"""
|
|
71
|
-
self.assertEqual(SkeletonExtractor.extract_brace_skeleton(go_code).strip(), expected.strip())
|
|
72
|
-
|
|
73
|
-
def test_rust_parser(self):
|
|
74
|
-
rust_code = """use std::io;
|
|
75
|
-
|
|
76
|
-
pub struct Robot {
|
|
77
|
-
name: String,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
impl Robot {
|
|
81
|
-
pub fn new(name: &str) -> Self {
|
|
82
|
-
Robot {
|
|
83
|
-
name: name.to_string(),
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
fn speak(&self) {
|
|
88
|
-
println!("Hello, my name is {}", self.name);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
"""
|
|
92
|
-
expected = """use std::io;
|
|
93
|
-
|
|
94
|
-
pub struct Robot {
|
|
95
|
-
name: String,
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
impl Robot {
|
|
99
|
-
pub fn new(name: &str) -> Self { /* ... */ }
|
|
100
|
-
|
|
101
|
-
fn speak(&self) { /* ... */ }
|
|
102
|
-
}"""
|
|
103
|
-
self.assertEqual(SkeletonExtractor.extract_brace_skeleton(rust_code).strip(), expected.strip())
|
|
104
|
-
|
|
105
|
-
def test_cpp_java_parser(self):
|
|
106
|
-
java_code = """public class App {
|
|
107
|
-
public static void main(String[] args) {
|
|
108
|
-
System.out.println("Hello World!");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private int calc(int a) {
|
|
112
|
-
return a * 2;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
"""
|
|
116
|
-
expected = """public class App {
|
|
117
|
-
public static void main(String[] args) { /* ... */ }
|
|
118
|
-
|
|
119
|
-
private int calc(int a) { /* ... */ }
|
|
120
|
-
}"""
|
|
121
|
-
self.assertEqual(SkeletonExtractor.extract_brace_skeleton(java_code).strip(), expected.strip())
|
|
122
|
-
|
|
123
|
-
def test_typescript_return_types(self):
|
|
124
|
-
ts_code = """class Manager {
|
|
125
|
-
async getItems(req: Request): Promise<Item[]> {
|
|
126
|
-
return this.client.get();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
add(a: number, b: number): number {
|
|
130
|
-
return a + b;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
"""
|
|
134
|
-
expected = """class Manager {
|
|
135
|
-
async getItems(req: Request): Promise<Item[]> { /* ... */ }
|
|
136
|
-
|
|
137
|
-
add(a: number, b: number): number { /* ... */ }
|
|
138
|
-
}"""
|
|
139
|
-
self.assertEqual(SkeletonExtractor.extract_brace_skeleton(ts_code).strip(), expected.strip())
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from ai_pack import SkeletonExtractor
|
|
3
|
-
|
|
4
|
-
class TestPythonSkeletonExtractor(unittest.TestCase):
|
|
5
|
-
def test_basic_functions_and_classes(self):
|
|
6
|
-
code = """class User:
|
|
7
|
-
def __init__(self, name):
|
|
8
|
-
self.name = name
|
|
9
|
-
def greet(self):
|
|
10
|
-
print("Hello " + self.name)
|
|
11
|
-
"""
|
|
12
|
-
expected = """class User:
|
|
13
|
-
def __init__(self, name):
|
|
14
|
-
...
|
|
15
|
-
def greet(self):
|
|
16
|
-
..."""
|
|
17
|
-
self.assertEqual(SkeletonExtractor.extract_python_skeleton(code).strip(), expected.strip())
|
|
18
|
-
|
|
19
|
-
def test_decorators_and_async(self):
|
|
20
|
-
code = """@property
|
|
21
|
-
@cache
|
|
22
|
-
def value(self):
|
|
23
|
-
return self._val
|
|
24
|
-
|
|
25
|
-
async def fetch_data(url):
|
|
26
|
-
res = await client.get(url)
|
|
27
|
-
return res.json()
|
|
28
|
-
"""
|
|
29
|
-
expected = """@property
|
|
30
|
-
@cache
|
|
31
|
-
def value(self):
|
|
32
|
-
...
|
|
33
|
-
async def fetch_data(url):
|
|
34
|
-
..."""
|
|
35
|
-
self.assertEqual(SkeletonExtractor.extract_python_skeleton(code).strip(), expected.strip())
|
|
36
|
-
|
|
37
|
-
def test_multiline_signatures(self):
|
|
38
|
-
code = """def complex_function(
|
|
39
|
-
a: int,
|
|
40
|
-
b: str,
|
|
41
|
-
c: float = 1.0
|
|
42
|
-
) -> bool:
|
|
43
|
-
print(a, b, c)
|
|
44
|
-
return True
|
|
45
|
-
"""
|
|
46
|
-
expected = """def complex_function(
|
|
47
|
-
a: int,
|
|
48
|
-
b: str,
|
|
49
|
-
c: float = 1.0
|
|
50
|
-
) -> bool:
|
|
51
|
-
..."""
|
|
52
|
-
self.assertEqual(SkeletonExtractor.extract_python_skeleton(code).strip(), expected.strip())
|
|
53
|
-
|
|
54
|
-
def test_single_line_and_comments(self):
|
|
55
|
-
code = """def simple_get(self): return self.x
|
|
56
|
-
|
|
57
|
-
def comment_sig(a, b): # basic add
|
|
58
|
-
return a + b
|
|
59
|
-
"""
|
|
60
|
-
expected = """def simple_get(self): return self.x
|
|
61
|
-
def comment_sig(a, b): # basic add
|
|
62
|
-
..."""
|
|
63
|
-
self.assertEqual(SkeletonExtractor.extract_python_skeleton(code).strip(), expected.strip())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|