reveal-cli 0.8.0__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.
- plugins/c-header.yaml +89 -0
- plugins/gdscript.yaml +96 -0
- plugins/python.yaml +94 -0
- plugins/yaml.yaml +87 -0
- reveal/__init__.py +26 -0
- reveal/analyzers/__init__.py +39 -0
- reveal/analyzers/bash.py +44 -0
- reveal/analyzers/dockerfile.py +179 -0
- reveal/analyzers/gdscript.py +176 -0
- reveal/analyzers/go.py +13 -0
- reveal/analyzers/javascript.py +21 -0
- reveal/analyzers/jupyter_analyzer.py +230 -0
- reveal/analyzers/markdown.py +79 -0
- reveal/analyzers/nginx.py +185 -0
- reveal/analyzers/python.py +15 -0
- reveal/analyzers/rust.py +13 -0
- reveal/analyzers/toml.py +96 -0
- reveal/analyzers/typescript.py +24 -0
- reveal/analyzers/yaml_json.py +110 -0
- reveal/base.py +267 -0
- reveal/main.py +355 -0
- reveal/tests/__init__.py +1 -0
- reveal/tests/test_json_yaml_line_numbers.py +238 -0
- reveal/tests/test_line_numbers.py +151 -0
- reveal/tests/test_toml_analyzer.py +220 -0
- reveal/tree_view.py +105 -0
- reveal/treesitter.py +281 -0
- reveal_cli-0.8.0.dist-info/METADATA +352 -0
- reveal_cli-0.8.0.dist-info/RECORD +33 -0
- reveal_cli-0.8.0.dist-info/WHEEL +5 -0
- reveal_cli-0.8.0.dist-info/entry_points.txt +2 -0
- reveal_cli-0.8.0.dist-info/licenses/LICENSE +21 -0
- reveal_cli-0.8.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for JSON/YAML line number support.
|
|
3
|
+
|
|
4
|
+
Ensures that JSON and YAML analyzers return real line numbers
|
|
5
|
+
from the source file, not fake enumerated positions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
from reveal.analyzers.json_analyzer import JSONAnalyzer
|
|
10
|
+
from reveal.analyzers.yaml_analyzer import YAMLAnalyzer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestJSONLineNumbers(unittest.TestCase):
|
|
14
|
+
"""Test JSON analyzer line number extraction."""
|
|
15
|
+
|
|
16
|
+
def test_json_returns_line_numbers(self):
|
|
17
|
+
"""JSON analyzer should return line numbers for top-level keys."""
|
|
18
|
+
json_lines = [
|
|
19
|
+
'{',
|
|
20
|
+
' "name": "test",',
|
|
21
|
+
' "version": "1.0.0",',
|
|
22
|
+
' "dependencies": {',
|
|
23
|
+
' "foo": "1.0"',
|
|
24
|
+
' }',
|
|
25
|
+
'}'
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
analyzer = JSONAnalyzer(json_lines)
|
|
29
|
+
structure = analyzer.analyze_structure()
|
|
30
|
+
|
|
31
|
+
# Should have top-level keys
|
|
32
|
+
self.assertIn('top_level_keys', structure)
|
|
33
|
+
keys = structure['top_level_keys']
|
|
34
|
+
|
|
35
|
+
# Should have 3 keys
|
|
36
|
+
self.assertEqual(len(keys), 3)
|
|
37
|
+
|
|
38
|
+
# Each key should be a dict with 'name' and 'line'
|
|
39
|
+
for key in keys:
|
|
40
|
+
self.assertIsInstance(key, dict)
|
|
41
|
+
self.assertIn('name', key)
|
|
42
|
+
self.assertIn('line', key)
|
|
43
|
+
|
|
44
|
+
# Check specific line numbers
|
|
45
|
+
key_dict = {k['name']: k['line'] for k in keys}
|
|
46
|
+
|
|
47
|
+
self.assertEqual(key_dict['name'], 2)
|
|
48
|
+
self.assertEqual(key_dict['version'], 3)
|
|
49
|
+
self.assertEqual(key_dict['dependencies'], 4)
|
|
50
|
+
|
|
51
|
+
def test_json_with_file_path(self):
|
|
52
|
+
"""JSON analyzer should use file_path when provided."""
|
|
53
|
+
json_lines = [
|
|
54
|
+
'{',
|
|
55
|
+
' "key1": "value1",',
|
|
56
|
+
' "key2": "value2"',
|
|
57
|
+
'}'
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
analyzer = JSONAnalyzer(json_lines, file_path='/tmp/test.json')
|
|
61
|
+
structure = analyzer.analyze_structure()
|
|
62
|
+
|
|
63
|
+
# Verify file_path is stored
|
|
64
|
+
self.assertEqual(analyzer.file_path, '/tmp/test.json')
|
|
65
|
+
|
|
66
|
+
# Line numbers should still be found
|
|
67
|
+
keys = structure['top_level_keys']
|
|
68
|
+
self.assertEqual(len(keys), 2)
|
|
69
|
+
self.assertEqual(keys[0]['line'], 2)
|
|
70
|
+
self.assertEqual(keys[1]['line'], 3)
|
|
71
|
+
|
|
72
|
+
def test_json_empty_object(self):
|
|
73
|
+
"""JSON analyzer should handle empty objects."""
|
|
74
|
+
json_lines = ['{}']
|
|
75
|
+
|
|
76
|
+
analyzer = JSONAnalyzer(json_lines)
|
|
77
|
+
structure = analyzer.analyze_structure()
|
|
78
|
+
|
|
79
|
+
# Should return empty list, not crash
|
|
80
|
+
self.assertEqual(structure['top_level_keys'], [])
|
|
81
|
+
|
|
82
|
+
def test_json_array_top_level(self):
|
|
83
|
+
"""JSON analyzer should handle arrays at top level."""
|
|
84
|
+
json_lines = ['[1, 2, 3]']
|
|
85
|
+
|
|
86
|
+
analyzer = JSONAnalyzer(json_lines)
|
|
87
|
+
structure = analyzer.analyze_structure()
|
|
88
|
+
|
|
89
|
+
# Arrays have no keys
|
|
90
|
+
self.assertEqual(structure['top_level_keys'], [])
|
|
91
|
+
|
|
92
|
+
def test_json_parse_error(self):
|
|
93
|
+
"""JSON analyzer should handle parse errors gracefully."""
|
|
94
|
+
json_lines = ['{invalid json}']
|
|
95
|
+
|
|
96
|
+
analyzer = JSONAnalyzer(json_lines)
|
|
97
|
+
structure = analyzer.analyze_structure()
|
|
98
|
+
|
|
99
|
+
# Should have error key
|
|
100
|
+
self.assertIn('error', structure)
|
|
101
|
+
self.assertEqual(structure['top_level_keys'], [])
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestYAMLLineNumbers(unittest.TestCase):
|
|
105
|
+
"""Test YAML analyzer line number extraction."""
|
|
106
|
+
|
|
107
|
+
def test_yaml_returns_line_numbers(self):
|
|
108
|
+
"""YAML analyzer should return line numbers for top-level keys."""
|
|
109
|
+
yaml_lines = [
|
|
110
|
+
'name: test-project',
|
|
111
|
+
'version: 1.0.0',
|
|
112
|
+
'dependencies:',
|
|
113
|
+
' foo: 1.0',
|
|
114
|
+
' bar: 2.0',
|
|
115
|
+
'scripts:',
|
|
116
|
+
' test: pytest'
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
analyzer = YAMLAnalyzer(yaml_lines)
|
|
120
|
+
structure = analyzer.analyze_structure()
|
|
121
|
+
|
|
122
|
+
# Should have top-level keys
|
|
123
|
+
self.assertIn('top_level_keys', structure)
|
|
124
|
+
keys = structure['top_level_keys']
|
|
125
|
+
|
|
126
|
+
# Should have 4 keys
|
|
127
|
+
self.assertEqual(len(keys), 4)
|
|
128
|
+
|
|
129
|
+
# Each key should be a dict with 'name' and 'line'
|
|
130
|
+
for key in keys:
|
|
131
|
+
self.assertIsInstance(key, dict)
|
|
132
|
+
self.assertIn('name', key)
|
|
133
|
+
self.assertIn('line', key)
|
|
134
|
+
|
|
135
|
+
# Check specific line numbers
|
|
136
|
+
key_dict = {k['name']: k['line'] for k in keys}
|
|
137
|
+
|
|
138
|
+
self.assertEqual(key_dict['name'], 1)
|
|
139
|
+
self.assertEqual(key_dict['version'], 2)
|
|
140
|
+
self.assertEqual(key_dict['dependencies'], 3)
|
|
141
|
+
self.assertEqual(key_dict['scripts'], 6)
|
|
142
|
+
|
|
143
|
+
def test_yaml_with_file_path(self):
|
|
144
|
+
"""YAML analyzer should use file_path when provided."""
|
|
145
|
+
yaml_lines = [
|
|
146
|
+
'key1: value1',
|
|
147
|
+
'key2: value2'
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
analyzer = YAMLAnalyzer(yaml_lines, file_path='/tmp/test.yaml')
|
|
151
|
+
structure = analyzer.analyze_structure()
|
|
152
|
+
|
|
153
|
+
# Verify file_path is stored
|
|
154
|
+
self.assertEqual(analyzer.file_path, '/tmp/test.yaml')
|
|
155
|
+
|
|
156
|
+
# Line numbers should still be found
|
|
157
|
+
keys = structure['top_level_keys']
|
|
158
|
+
self.assertEqual(len(keys), 2)
|
|
159
|
+
self.assertEqual(keys[0]['line'], 1)
|
|
160
|
+
self.assertEqual(keys[1]['line'], 2)
|
|
161
|
+
|
|
162
|
+
def test_yaml_with_comments(self):
|
|
163
|
+
"""YAML analyzer should find keys despite comments."""
|
|
164
|
+
yaml_lines = [
|
|
165
|
+
'# Configuration file',
|
|
166
|
+
'name: test',
|
|
167
|
+
'# Database settings',
|
|
168
|
+
'database:',
|
|
169
|
+
' host: localhost'
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
analyzer = YAMLAnalyzer(yaml_lines)
|
|
173
|
+
structure = analyzer.analyze_structure()
|
|
174
|
+
|
|
175
|
+
keys = structure['top_level_keys']
|
|
176
|
+
key_dict = {k['name']: k['line'] for k in keys}
|
|
177
|
+
|
|
178
|
+
# Should find correct lines despite comments
|
|
179
|
+
self.assertEqual(key_dict['name'], 2)
|
|
180
|
+
self.assertEqual(key_dict['database'], 4)
|
|
181
|
+
|
|
182
|
+
def test_yaml_empty_document(self):
|
|
183
|
+
"""YAML analyzer should handle empty documents."""
|
|
184
|
+
yaml_lines = ['']
|
|
185
|
+
|
|
186
|
+
analyzer = YAMLAnalyzer(yaml_lines)
|
|
187
|
+
structure = analyzer.analyze_structure()
|
|
188
|
+
|
|
189
|
+
# Should return empty list, not crash
|
|
190
|
+
self.assertEqual(structure['top_level_keys'], [])
|
|
191
|
+
|
|
192
|
+
def test_yaml_list_top_level(self):
|
|
193
|
+
"""YAML analyzer should handle lists at top level."""
|
|
194
|
+
yaml_lines = ['- item1', '- item2', '- item3']
|
|
195
|
+
|
|
196
|
+
analyzer = YAMLAnalyzer(yaml_lines)
|
|
197
|
+
structure = analyzer.analyze_structure()
|
|
198
|
+
|
|
199
|
+
# Lists have no keys
|
|
200
|
+
self.assertEqual(structure['top_level_keys'], [])
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class TestLineNumberComposability(unittest.TestCase):
|
|
204
|
+
"""Test that line numbers make output composable with other tools."""
|
|
205
|
+
|
|
206
|
+
def test_json_format_location_helper(self):
|
|
207
|
+
"""JSON analyzer should use format_location helper."""
|
|
208
|
+
json_lines = ['{', ' "test": "value"', '}']
|
|
209
|
+
|
|
210
|
+
analyzer = JSONAnalyzer(json_lines, file_path='config.json')
|
|
211
|
+
|
|
212
|
+
# Test format_location helper
|
|
213
|
+
loc = analyzer.format_location(2)
|
|
214
|
+
self.assertEqual(loc, 'config.json:2')
|
|
215
|
+
|
|
216
|
+
# Without file_path, should use L0000 format
|
|
217
|
+
analyzer_no_path = JSONAnalyzer(json_lines)
|
|
218
|
+
loc = analyzer_no_path.format_location(2)
|
|
219
|
+
self.assertEqual(loc, 'L0002')
|
|
220
|
+
|
|
221
|
+
def test_yaml_format_location_helper(self):
|
|
222
|
+
"""YAML analyzer should use format_location helper."""
|
|
223
|
+
yaml_lines = ['test: value']
|
|
224
|
+
|
|
225
|
+
analyzer = YAMLAnalyzer(yaml_lines, file_path='config.yaml')
|
|
226
|
+
|
|
227
|
+
# Test format_location helper
|
|
228
|
+
loc = analyzer.format_location(1)
|
|
229
|
+
self.assertEqual(loc, 'config.yaml:1')
|
|
230
|
+
|
|
231
|
+
# Without file_path, should use L0000 format
|
|
232
|
+
analyzer_no_path = YAMLAnalyzer(yaml_lines)
|
|
233
|
+
loc = analyzer_no_path.format_location(1)
|
|
234
|
+
self.assertEqual(loc, 'L0001')
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
if __name__ == '__main__':
|
|
238
|
+
unittest.main()
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Tests for line number functionality."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from reveal.cli import parse_file_location
|
|
6
|
+
from reveal.analyzers import SQLAnalyzer, PythonAnalyzer, JSONAnalyzer, YAMLAnalyzer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestFileLocationParsing(unittest.TestCase):
|
|
10
|
+
"""Test file:line parsing."""
|
|
11
|
+
|
|
12
|
+
def test_plain_file(self):
|
|
13
|
+
"""Test plain filename without line number."""
|
|
14
|
+
path, start, end = parse_file_location("test.sql")
|
|
15
|
+
self.assertEqual(path, "test.sql")
|
|
16
|
+
self.assertIsNone(start)
|
|
17
|
+
self.assertIsNone(end)
|
|
18
|
+
|
|
19
|
+
def test_file_with_single_line(self):
|
|
20
|
+
"""Test file:32 syntax."""
|
|
21
|
+
path, start, end = parse_file_location("test.sql:32")
|
|
22
|
+
self.assertEqual(path, "test.sql")
|
|
23
|
+
self.assertEqual(start, 32)
|
|
24
|
+
self.assertEqual(end, 32)
|
|
25
|
+
|
|
26
|
+
def test_file_with_range(self):
|
|
27
|
+
"""Test file:10-50 syntax."""
|
|
28
|
+
path, start, end = parse_file_location("test.sql:10-50")
|
|
29
|
+
self.assertEqual(path, "test.sql")
|
|
30
|
+
self.assertEqual(start, 10)
|
|
31
|
+
self.assertEqual(end, 50)
|
|
32
|
+
|
|
33
|
+
def test_file_with_open_range(self):
|
|
34
|
+
"""Test file:10- syntax (from line 10 to end)."""
|
|
35
|
+
path, start, end = parse_file_location("test.sql:10-")
|
|
36
|
+
self.assertEqual(path, "test.sql")
|
|
37
|
+
self.assertEqual(start, 10)
|
|
38
|
+
self.assertIsNone(end)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestAnalyzerParameters(unittest.TestCase):
|
|
42
|
+
"""Test that all analyzers accept the new parameters."""
|
|
43
|
+
|
|
44
|
+
def test_sql_analyzer_accepts_kwargs(self):
|
|
45
|
+
"""SQL analyzer should accept file_path and focus parameters."""
|
|
46
|
+
lines = ["CREATE TABLE test (id INT);"]
|
|
47
|
+
analyzer = SQLAnalyzer(
|
|
48
|
+
lines,
|
|
49
|
+
file_path="test.sql",
|
|
50
|
+
focus_start=1,
|
|
51
|
+
focus_end=10
|
|
52
|
+
)
|
|
53
|
+
self.assertEqual(analyzer.file_path, "test.sql")
|
|
54
|
+
self.assertEqual(analyzer.focus_start, 1)
|
|
55
|
+
self.assertEqual(analyzer.focus_end, 10)
|
|
56
|
+
|
|
57
|
+
def test_python_analyzer_accepts_kwargs(self):
|
|
58
|
+
"""Python analyzer should accept file_path and focus parameters."""
|
|
59
|
+
lines = ["def test(): pass"]
|
|
60
|
+
analyzer = PythonAnalyzer(
|
|
61
|
+
lines,
|
|
62
|
+
file_path="test.py",
|
|
63
|
+
focus_start=1,
|
|
64
|
+
focus_end=10
|
|
65
|
+
)
|
|
66
|
+
self.assertEqual(analyzer.file_path, "test.py")
|
|
67
|
+
|
|
68
|
+
def test_json_analyzer_accepts_kwargs(self):
|
|
69
|
+
"""JSON analyzer should accept file_path and focus parameters."""
|
|
70
|
+
lines = ['{"key": "value"}']
|
|
71
|
+
analyzer = JSONAnalyzer(
|
|
72
|
+
lines,
|
|
73
|
+
file_path="test.json",
|
|
74
|
+
focus_start=1
|
|
75
|
+
)
|
|
76
|
+
self.assertEqual(analyzer.file_path, "test.json")
|
|
77
|
+
|
|
78
|
+
def test_yaml_analyzer_accepts_kwargs(self):
|
|
79
|
+
"""YAML analyzer should accept file_path and focus parameters."""
|
|
80
|
+
lines = ["key: value"]
|
|
81
|
+
analyzer = YAMLAnalyzer(
|
|
82
|
+
lines,
|
|
83
|
+
file_path="test.yaml"
|
|
84
|
+
)
|
|
85
|
+
self.assertEqual(analyzer.file_path, "test.yaml")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestPythonAnalyzerLineNumbers(unittest.TestCase):
|
|
89
|
+
"""Test Python analyzer returns line numbers."""
|
|
90
|
+
|
|
91
|
+
def test_function_has_line_number(self):
|
|
92
|
+
"""Functions should include line numbers."""
|
|
93
|
+
lines = [
|
|
94
|
+
"# comment",
|
|
95
|
+
"def test_func():",
|
|
96
|
+
" pass",
|
|
97
|
+
]
|
|
98
|
+
analyzer = PythonAnalyzer(lines)
|
|
99
|
+
structure = analyzer.analyze_structure()
|
|
100
|
+
|
|
101
|
+
self.assertEqual(len(structure['functions']), 1)
|
|
102
|
+
func = structure['functions'][0]
|
|
103
|
+
self.assertIsInstance(func, dict)
|
|
104
|
+
self.assertEqual(func['name'], 'test_func')
|
|
105
|
+
self.assertEqual(func['line'], 2)
|
|
106
|
+
|
|
107
|
+
def test_class_has_line_number(self):
|
|
108
|
+
"""Classes should include line numbers."""
|
|
109
|
+
lines = [
|
|
110
|
+
"# comment",
|
|
111
|
+
"",
|
|
112
|
+
"class TestClass:",
|
|
113
|
+
" pass",
|
|
114
|
+
]
|
|
115
|
+
analyzer = PythonAnalyzer(lines)
|
|
116
|
+
structure = analyzer.analyze_structure()
|
|
117
|
+
|
|
118
|
+
self.assertEqual(len(structure['classes']), 1)
|
|
119
|
+
cls = structure['classes'][0]
|
|
120
|
+
self.assertIsInstance(cls, dict)
|
|
121
|
+
self.assertEqual(cls['name'], 'TestClass')
|
|
122
|
+
self.assertEqual(cls['line'], 3)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class TestFormatLocation(unittest.TestCase):
|
|
126
|
+
"""Test BaseAnalyzer format_location helper."""
|
|
127
|
+
|
|
128
|
+
def test_format_location_with_file_path(self):
|
|
129
|
+
"""Should return filename:line when file_path is set."""
|
|
130
|
+
lines = ["CREATE TABLE test (id INT);"]
|
|
131
|
+
analyzer = SQLAnalyzer(lines, file_path="/path/to/test.sql")
|
|
132
|
+
loc = analyzer.format_location(32)
|
|
133
|
+
self.assertEqual(loc, "/path/to/test.sql:32")
|
|
134
|
+
|
|
135
|
+
def test_format_location_without_file_path(self):
|
|
136
|
+
"""Should return L0000 format when no file_path."""
|
|
137
|
+
lines = ["CREATE TABLE test (id INT);"]
|
|
138
|
+
analyzer = SQLAnalyzer(lines)
|
|
139
|
+
loc = analyzer.format_location(32)
|
|
140
|
+
self.assertEqual(loc, "L0032")
|
|
141
|
+
|
|
142
|
+
def test_format_location_none(self):
|
|
143
|
+
"""Should return empty string for None."""
|
|
144
|
+
lines = ["CREATE TABLE test (id INT);"]
|
|
145
|
+
analyzer = SQLAnalyzer(lines)
|
|
146
|
+
loc = analyzer.format_location(None)
|
|
147
|
+
self.assertEqual(loc, "")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == '__main__':
|
|
151
|
+
unittest.main()
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for TOML analyzer line number support.
|
|
3
|
+
|
|
4
|
+
Ensures that TOML analyzer returns real line numbers
|
|
5
|
+
from the source file using the find_definition() helper.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
from reveal.analyzers.toml_analyzer import TOMLAnalyzer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestTOMLLineNumbers(unittest.TestCase):
|
|
13
|
+
"""Test TOML analyzer line number extraction."""
|
|
14
|
+
|
|
15
|
+
def test_toml_returns_line_numbers_for_keys(self):
|
|
16
|
+
"""TOML analyzer should return line numbers for top-level keys."""
|
|
17
|
+
toml_lines = [
|
|
18
|
+
'# Configuration file',
|
|
19
|
+
'name = "test-project"',
|
|
20
|
+
'version = "1.0.0"',
|
|
21
|
+
'debug = true',
|
|
22
|
+
'',
|
|
23
|
+
'[server]',
|
|
24
|
+
'host = "localhost"',
|
|
25
|
+
'port = 8080',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
analyzer = TOMLAnalyzer(toml_lines)
|
|
29
|
+
structure = analyzer.analyze_structure()
|
|
30
|
+
|
|
31
|
+
# Should have top-level keys
|
|
32
|
+
self.assertIn('top_level_keys', structure)
|
|
33
|
+
keys = structure['top_level_keys']
|
|
34
|
+
|
|
35
|
+
# Should have 3 top-level keys (name, version, debug)
|
|
36
|
+
self.assertEqual(len(keys), 3)
|
|
37
|
+
|
|
38
|
+
# Each key should be a dict with 'name' and 'line'
|
|
39
|
+
for key in keys:
|
|
40
|
+
self.assertIsInstance(key, dict)
|
|
41
|
+
self.assertIn('name', key)
|
|
42
|
+
self.assertIn('line', key)
|
|
43
|
+
|
|
44
|
+
# Check specific line numbers
|
|
45
|
+
key_dict = {k['name']: k['line'] for k in keys}
|
|
46
|
+
self.assertEqual(key_dict['name'], 2)
|
|
47
|
+
self.assertEqual(key_dict['version'], 3)
|
|
48
|
+
self.assertEqual(key_dict['debug'], 4)
|
|
49
|
+
|
|
50
|
+
def test_toml_returns_line_numbers_for_sections(self):
|
|
51
|
+
"""TOML analyzer should return line numbers for sections."""
|
|
52
|
+
toml_lines = [
|
|
53
|
+
'name = "test"',
|
|
54
|
+
'',
|
|
55
|
+
'[server]',
|
|
56
|
+
'host = "localhost"',
|
|
57
|
+
'',
|
|
58
|
+
'[database]',
|
|
59
|
+
'driver = "postgresql"',
|
|
60
|
+
'',
|
|
61
|
+
'[logging]',
|
|
62
|
+
'level = "info"',
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
analyzer = TOMLAnalyzer(toml_lines)
|
|
66
|
+
structure = analyzer.analyze_structure()
|
|
67
|
+
|
|
68
|
+
# Should have sections
|
|
69
|
+
self.assertIn('sections', structure)
|
|
70
|
+
sections = structure['sections']
|
|
71
|
+
|
|
72
|
+
# Should have 3 sections
|
|
73
|
+
self.assertEqual(len(sections), 3)
|
|
74
|
+
|
|
75
|
+
# Each section should be a dict with 'name' and 'line'
|
|
76
|
+
for section in sections:
|
|
77
|
+
self.assertIsInstance(section, dict)
|
|
78
|
+
self.assertIn('name', section)
|
|
79
|
+
self.assertIn('line', section)
|
|
80
|
+
|
|
81
|
+
# Check specific line numbers
|
|
82
|
+
section_dict = {s['name']: s['line'] for s in sections}
|
|
83
|
+
self.assertEqual(section_dict['server'], 3)
|
|
84
|
+
self.assertEqual(section_dict['database'], 6)
|
|
85
|
+
self.assertEqual(section_dict['logging'], 9)
|
|
86
|
+
|
|
87
|
+
def test_toml_with_file_path(self):
|
|
88
|
+
"""TOML analyzer should use file_path when provided."""
|
|
89
|
+
toml_lines = [
|
|
90
|
+
'key1 = "value1"',
|
|
91
|
+
'key2 = "value2"'
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
analyzer = TOMLAnalyzer(toml_lines, file_path='/tmp/test.toml')
|
|
95
|
+
structure = analyzer.analyze_structure()
|
|
96
|
+
|
|
97
|
+
# Verify file_path is stored
|
|
98
|
+
self.assertEqual(analyzer.file_path, '/tmp/test.toml')
|
|
99
|
+
|
|
100
|
+
# Line numbers should still be found
|
|
101
|
+
keys = structure['top_level_keys']
|
|
102
|
+
self.assertEqual(len(keys), 2)
|
|
103
|
+
self.assertEqual(keys[0]['line'], 1)
|
|
104
|
+
self.assertEqual(keys[1]['line'], 2)
|
|
105
|
+
|
|
106
|
+
def test_toml_pyproject_example(self):
|
|
107
|
+
"""TOML analyzer should handle real pyproject.toml structure."""
|
|
108
|
+
toml_lines = [
|
|
109
|
+
'[build-system]',
|
|
110
|
+
'requires = ["setuptools>=61.0"]',
|
|
111
|
+
'',
|
|
112
|
+
'[project]',
|
|
113
|
+
'name = "reveal"',
|
|
114
|
+
'version = "0.1.0"',
|
|
115
|
+
'description = "Progressive file revelation tool"',
|
|
116
|
+
'',
|
|
117
|
+
'[project.scripts]',
|
|
118
|
+
'reveal = "reveal.cli:main"',
|
|
119
|
+
'',
|
|
120
|
+
'[tool.pytest]',
|
|
121
|
+
'testpaths = ["tests"]',
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
analyzer = TOMLAnalyzer(toml_lines)
|
|
125
|
+
structure = analyzer.analyze_structure()
|
|
126
|
+
|
|
127
|
+
sections = structure['sections']
|
|
128
|
+
section_dict = {s['name']: s['line'] for s in sections}
|
|
129
|
+
|
|
130
|
+
# Check section line numbers
|
|
131
|
+
self.assertEqual(section_dict['build-system'], 1)
|
|
132
|
+
self.assertEqual(section_dict['project'], 4)
|
|
133
|
+
self.assertEqual(section_dict['tool'], 12)
|
|
134
|
+
|
|
135
|
+
def test_toml_nested_sections(self):
|
|
136
|
+
"""TOML analyzer should count subsections."""
|
|
137
|
+
toml_lines = [
|
|
138
|
+
'[project]',
|
|
139
|
+
'name = "test"',
|
|
140
|
+
'',
|
|
141
|
+
'[project.dependencies]',
|
|
142
|
+
'pytest = "^7.0"',
|
|
143
|
+
'',
|
|
144
|
+
'[project.scripts]',
|
|
145
|
+
'test = "pytest"',
|
|
146
|
+
'',
|
|
147
|
+
'[tool]',
|
|
148
|
+
'version = "1.0"',
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
analyzer = TOMLAnalyzer(toml_lines)
|
|
152
|
+
structure = analyzer.analyze_structure()
|
|
153
|
+
|
|
154
|
+
sections = structure['sections']
|
|
155
|
+
|
|
156
|
+
# Find project section
|
|
157
|
+
project_section = next(s for s in sections if s['name'] == 'project')
|
|
158
|
+
|
|
159
|
+
# Should have subsections count
|
|
160
|
+
self.assertIn('subsections', project_section)
|
|
161
|
+
# project.dependencies and project.scripts are subsections
|
|
162
|
+
self.assertGreater(project_section['subsections'], 0)
|
|
163
|
+
|
|
164
|
+
def test_toml_empty_file(self):
|
|
165
|
+
"""TOML analyzer should handle empty files."""
|
|
166
|
+
toml_lines = ['']
|
|
167
|
+
|
|
168
|
+
analyzer = TOMLAnalyzer(toml_lines)
|
|
169
|
+
structure = analyzer.analyze_structure()
|
|
170
|
+
|
|
171
|
+
# Should return empty lists, not crash
|
|
172
|
+
self.assertEqual(structure['top_level_keys'], [])
|
|
173
|
+
self.assertEqual(structure['sections'], [])
|
|
174
|
+
|
|
175
|
+
def test_toml_with_comments(self):
|
|
176
|
+
"""TOML analyzer should find keys despite comments."""
|
|
177
|
+
toml_lines = [
|
|
178
|
+
'# Application settings',
|
|
179
|
+
'name = "myapp"',
|
|
180
|
+
'',
|
|
181
|
+
'# Server configuration',
|
|
182
|
+
'[server]',
|
|
183
|
+
'port = 8080',
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
analyzer = TOMLAnalyzer(toml_lines)
|
|
187
|
+
structure = analyzer.analyze_structure()
|
|
188
|
+
|
|
189
|
+
keys = structure['top_level_keys']
|
|
190
|
+
sections = structure['sections']
|
|
191
|
+
|
|
192
|
+
# Should find keys on correct lines (not comment lines)
|
|
193
|
+
key_dict = {k['name']: k['line'] for k in keys}
|
|
194
|
+
self.assertEqual(key_dict['name'], 2)
|
|
195
|
+
|
|
196
|
+
section_dict = {s['name']: s['line'] for s in sections}
|
|
197
|
+
self.assertEqual(section_dict['server'], 5)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class TestTOMLComposability(unittest.TestCase):
|
|
201
|
+
"""Test that TOML line numbers make output composable."""
|
|
202
|
+
|
|
203
|
+
def test_toml_format_location_helper(self):
|
|
204
|
+
"""TOML analyzer should use format_location helper."""
|
|
205
|
+
toml_lines = ['name = "test"']
|
|
206
|
+
|
|
207
|
+
analyzer = TOMLAnalyzer(toml_lines, file_path='config.toml')
|
|
208
|
+
|
|
209
|
+
# Test format_location helper
|
|
210
|
+
loc = analyzer.format_location(1)
|
|
211
|
+
self.assertEqual(loc, 'config.toml:1')
|
|
212
|
+
|
|
213
|
+
# Without file_path, should use L0000 format
|
|
214
|
+
analyzer_no_path = TOMLAnalyzer(toml_lines)
|
|
215
|
+
loc = analyzer_no_path.format_location(1)
|
|
216
|
+
self.assertEqual(loc, 'L0001')
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == '__main__':
|
|
220
|
+
unittest.main()
|