ai-pack-cli 0.1.1__tar.gz → 0.1.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ai-pack-cli
3
- Version: 0.1.1
3
+ Version: 0.1.2
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
@@ -231,10 +231,16 @@ class SkeletonExtractor:
231
231
  indent = len(line) - len(line.lstrip())
232
232
  sig_lines = []
233
233
 
234
- # Consume multi-line signatures
234
+ # Consume multi-line signatures by tracking open parentheses/brackets
235
+ open_braces = 0
235
236
  while i < n:
236
- sig_lines.append(lines[i])
237
- if lines[i].rstrip().endswith(":"):
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:
238
244
  break
239
245
  i += 1
240
246
 
@@ -360,15 +366,18 @@ class SkeletonExtractor:
360
366
  is_func = True
361
367
  elif any(x in prefix_str for x in ('fn ', 'func ')):
362
368
  is_func = True
363
- elif prefix_str.endswith(')'):
364
- words = [w for w in prefix_str.split() if w]
365
- clean_words = [w for w in words if w not in (
366
- 'export', 'async', 'default', 'public', 'private', 'protected', 'static', 'readonly'
367
- )]
368
- if clean_words:
369
- first_word = clean_words[0].split('(')[0].strip()
370
- if first_word not in ('if', 'for', 'while', 'switch', 'catch'):
371
- 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
372
381
 
373
382
  if is_func:
374
383
  skip_until_brace_depth = brace_depth
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ai-pack-cli
3
- Version: 0.1.1
3
+ Version: 0.1.2
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
@@ -6,4 +6,8 @@ ai_pack_cli.egg-info/SOURCES.txt
6
6
  ai_pack_cli.egg-info/dependency_links.txt
7
7
  ai_pack_cli.egg-info/entry_points.txt
8
8
  ai_pack_cli.egg-info/requires.txt
9
- ai_pack_cli.egg-info/top_level.txt
9
+ ai_pack_cli.egg-info/top_level.txt
10
+ tests/test_binary.py
11
+ tests/test_gitignore.py
12
+ tests/test_skeleton_braces.py
13
+ tests/test_skeleton_python.py
@@ -6,7 +6,7 @@ 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.1",
9
+ version="0.1.2",
10
10
  py_modules=["ai_pack"],
11
11
  install_requires=[
12
12
  "pyperclip>=1.8.2",
@@ -0,0 +1,33 @@
1
+ import unittest
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ from ai_pack import is_binary
6
+
7
+ class TestBinaryDetection(unittest.TestCase):
8
+ def setUp(self):
9
+ self.test_dir = tempfile.mkdtemp()
10
+
11
+ def tearDown(self):
12
+ shutil.rmtree(self.test_dir)
13
+
14
+ def test_text_file_is_not_binary(self):
15
+ file_path = os.path.join(self.test_dir, "text.txt")
16
+ with open(file_path, "w", encoding="utf-8") as f:
17
+ f.write("Hello, this is a plain text file with normal characters.")
18
+ self.assertFalse(is_binary(file_path))
19
+
20
+ def test_empty_file_is_not_binary(self):
21
+ file_path = os.path.join(self.test_dir, "empty.txt")
22
+ with open(file_path, "w") as f:
23
+ f.write("")
24
+ self.assertFalse(is_binary(file_path))
25
+
26
+ def test_binary_file_with_null_byte_is_binary(self):
27
+ file_path = os.path.join(self.test_dir, "binary.bin")
28
+ with open(file_path, "wb") as f:
29
+ f.write(b"PNG\x00\x00\x00\r\nIHDR")
30
+ self.assertTrue(is_binary(file_path))
31
+
32
+ def test_missing_file_is_treated_as_binary(self):
33
+ self.assertTrue(is_binary(os.path.join(self.test_dir, "does_not_exist.bin")))
@@ -0,0 +1,38 @@
1
+ import unittest
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ from ai_pack import GitignoreMatcher
6
+
7
+ class TestGitignoreMatcher(unittest.TestCase):
8
+ def setUp(self):
9
+ self.test_dir = tempfile.mkdtemp()
10
+
11
+ def tearDown(self):
12
+ shutil.rmtree(self.test_dir)
13
+
14
+ def test_standard_ignores(self):
15
+ matcher = GitignoreMatcher(self.test_dir)
16
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "node_modules", "lodash", "index.js")))
17
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "src", "__pycache__", "utils.cpython-38.pyc")))
18
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, ".git", "config")))
19
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "venv", "lib", "python3.8", "site-packages")))
20
+
21
+ def test_matching_wildcard_patterns(self):
22
+ with open(os.path.join(self.test_dir, ".gitignore"), "w") as f:
23
+ f.write("*.log\ntemp*\n")
24
+ matcher = GitignoreMatcher(self.test_dir)
25
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "error.log")))
26
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "sub", "output.log")))
27
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "temp_data.csv")))
28
+ self.assertFalse(matcher.is_ignored(os.path.join(self.test_dir, "item_temp.txt")))
29
+
30
+ def test_absolute_vs_relative_gitignore_paths(self):
31
+ with open(os.path.join(self.test_dir, ".gitignore"), "w") as f:
32
+ f.write("/root_only.txt\nsub/ignored.txt\n")
33
+ matcher = GitignoreMatcher(self.test_dir)
34
+
35
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "root_only.txt")))
36
+ self.assertFalse(matcher.is_ignored(os.path.join(self.test_dir, "nested", "root_only.txt")))
37
+
38
+ self.assertTrue(matcher.is_ignored(os.path.join(self.test_dir, "sub", "ignored.txt")))
@@ -0,0 +1,139 @@
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())
@@ -0,0 +1,63 @@
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