reveal-cli 0.2.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.
Files changed (51) hide show
  1. plugins/c-header.yaml +89 -0
  2. plugins/gdscript.yaml +96 -0
  3. plugins/python.yaml +94 -0
  4. plugins/yaml.yaml +87 -0
  5. reveal/__init__.py +20 -0
  6. reveal/_archive_old_v0.1/analyzers/__init__.py +39 -0
  7. reveal/_archive_old_v0.1/analyzers/base.py +143 -0
  8. reveal/_archive_old_v0.1/analyzers/bash_analyzer.py +202 -0
  9. reveal/_archive_old_v0.1/analyzers/csharp_analyzer.py +167 -0
  10. reveal/_archive_old_v0.1/analyzers/gdscript_analyzer.py +201 -0
  11. reveal/_archive_old_v0.1/analyzers/go_analyzer.py +230 -0
  12. reveal/_archive_old_v0.1/analyzers/javascript_analyzer.py +272 -0
  13. reveal/_archive_old_v0.1/analyzers/json_analyzer.py +143 -0
  14. reveal/_archive_old_v0.1/analyzers/markdown_analyzer.py +116 -0
  15. reveal/_archive_old_v0.1/analyzers/php_analyzer.py +166 -0
  16. reveal/_archive_old_v0.1/analyzers/python_analyzer.py +118 -0
  17. reveal/_archive_old_v0.1/analyzers/rust_analyzer.py +202 -0
  18. reveal/_archive_old_v0.1/analyzers/sql_analyzer.py +534 -0
  19. reveal/_archive_old_v0.1/analyzers/text_analyzer.py +35 -0
  20. reveal/_archive_old_v0.1/analyzers/toml_analyzer.py +137 -0
  21. reveal/_archive_old_v0.1/analyzers/treesitter_base.py +276 -0
  22. reveal/_archive_old_v0.1/analyzers/yaml_analyzer.py +127 -0
  23. reveal/_archive_old_v0.1/breadcrumbs.py +147 -0
  24. reveal/_archive_old_v0.1/cli.py +445 -0
  25. reveal/_archive_old_v0.1/core.py +136 -0
  26. reveal/_archive_old_v0.1/detectors.py +29 -0
  27. reveal/_archive_old_v0.1/formatters.py +272 -0
  28. reveal/_archive_old_v0.1/grep_filter.py +85 -0
  29. reveal/_archive_old_v0.1/plugin_loader.py +178 -0
  30. reveal/_archive_old_v0.1/registry.py +286 -0
  31. reveal/analyzers/__init__.py +23 -0
  32. reveal/analyzers/go.py +13 -0
  33. reveal/analyzers/jupyter_analyzer.py +226 -0
  34. reveal/analyzers/markdown.py +79 -0
  35. reveal/analyzers/python.py +15 -0
  36. reveal/analyzers/rust.py +13 -0
  37. reveal/analyzers/yaml_json.py +110 -0
  38. reveal/base.py +193 -0
  39. reveal/main.py +216 -0
  40. reveal/tests/__init__.py +1 -0
  41. reveal/tests/test_json_yaml_line_numbers.py +238 -0
  42. reveal/tests/test_line_numbers.py +151 -0
  43. reveal/tests/test_toml_analyzer.py +220 -0
  44. reveal/tree_view.py +105 -0
  45. reveal/treesitter.py +282 -0
  46. reveal_cli-0.2.0.dist-info/METADATA +319 -0
  47. reveal_cli-0.2.0.dist-info/RECORD +51 -0
  48. reveal_cli-0.2.0.dist-info/WHEEL +5 -0
  49. reveal_cli-0.2.0.dist-info/entry_points.txt +2 -0
  50. reveal_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
  51. reveal_cli-0.2.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,230 @@
1
+ """
2
+ Go file analyzer using tree-sitter.
3
+
4
+ Extracts:
5
+ - Functions
6
+ - Structs
7
+ - Interfaces
8
+ - Methods
9
+ - Imports
10
+ """
11
+
12
+ from typing import Dict, Any, List
13
+ from .treesitter_base import TreeSitterAnalyzer, TREE_SITTER_AVAILABLE
14
+ from ..registry import register
15
+
16
+
17
+ @register(['.go'], name='Go', icon='🐹')
18
+ class GoAnalyzer(TreeSitterAnalyzer):
19
+ """
20
+ Analyzer for Go source files.
21
+
22
+ Features:
23
+ - Functions, methods, structs, interfaces
24
+ - Package and import statements
25
+ - Type definitions
26
+
27
+ All positions are exact (tree-sitter preserves source locations).
28
+ """
29
+
30
+ language_name = 'go'
31
+
32
+ def __init__(self, lines: List[str], **kwargs):
33
+ """Initialize Go analyzer."""
34
+ self.node_type_map = {
35
+ 'function': 'function_declaration',
36
+ 'import': 'import_declaration',
37
+ }
38
+ super().__init__(lines, **kwargs)
39
+
40
+ def extract_custom(self) -> Dict[str, List[Dict[str, Any]]]:
41
+ """Extract Go-specific items."""
42
+ return {
43
+ 'methods': self._extract_nodes('method_declaration', name_field='name'),
44
+ 'interfaces': self._extract_interfaces(),
45
+ 'structs': self._extract_structs(),
46
+ 'types': self._extract_types(),
47
+ }
48
+
49
+ def _extract_structs(self) -> List[Dict[str, Any]]:
50
+ """Extract struct declarations."""
51
+ if not self.tree:
52
+ return []
53
+
54
+ structs = []
55
+ cursor = self.tree.walk()
56
+
57
+ def visit(cursor):
58
+ node = cursor.node
59
+
60
+ if node.type == 'type_spec':
61
+ # Check if this type_spec contains a struct_type
62
+ for child in node.children:
63
+ if child.type == 'struct_type':
64
+ # Get the name from type_identifier
65
+ name_node = node.child_by_field_name('name')
66
+ if name_node:
67
+ line = node.start_point[0] + 1
68
+ if self.in_focus_range(line):
69
+ structs.append({
70
+ 'name': name_node.text.decode('utf-8'),
71
+ 'line': line
72
+ })
73
+ break
74
+
75
+ # Recurse
76
+ if cursor.goto_first_child():
77
+ visit(cursor)
78
+ while cursor.goto_next_sibling():
79
+ visit(cursor)
80
+ cursor.goto_parent()
81
+
82
+ visit(cursor)
83
+ return structs
84
+
85
+ def _extract_interfaces(self) -> List[Dict[str, Any]]:
86
+ """Extract interface declarations."""
87
+ if not self.tree:
88
+ return []
89
+
90
+ interfaces = []
91
+ cursor = self.tree.walk()
92
+
93
+ def visit(cursor):
94
+ node = cursor.node
95
+
96
+ if node.type == 'type_spec':
97
+ # Check if this type_spec contains an interface_type
98
+ for child in node.children:
99
+ if child.type == 'interface_type':
100
+ name_node = node.child_by_field_name('name')
101
+ if name_node:
102
+ line = node.start_point[0] + 1
103
+ if self.in_focus_range(line):
104
+ interfaces.append({
105
+ 'name': name_node.text.decode('utf-8'),
106
+ 'line': line
107
+ })
108
+ break
109
+
110
+ # Recurse
111
+ if cursor.goto_first_child():
112
+ visit(cursor)
113
+ while cursor.goto_next_sibling():
114
+ visit(cursor)
115
+ cursor.goto_parent()
116
+
117
+ visit(cursor)
118
+ return interfaces
119
+
120
+ def _extract_types(self) -> List[Dict[str, Any]]:
121
+ """Extract type declarations (excluding interfaces and structs)."""
122
+ if not self.tree:
123
+ return []
124
+
125
+ types = []
126
+ cursor = self.tree.walk()
127
+
128
+ def visit(cursor):
129
+ node = cursor.node
130
+
131
+ if node.type == 'type_spec':
132
+ # Check that it's not a struct or interface (handled separately)
133
+ has_struct_or_interface = False
134
+ for child in node.children:
135
+ if child.type in ('interface_type', 'struct_type'):
136
+ has_struct_or_interface = True
137
+ break
138
+
139
+ if not has_struct_or_interface:
140
+ name_node = node.child_by_field_name('name')
141
+ if name_node:
142
+ line = node.start_point[0] + 1
143
+ if self.in_focus_range(line):
144
+ types.append({
145
+ 'name': name_node.text.decode('utf-8'),
146
+ 'line': line
147
+ })
148
+
149
+ # Recurse
150
+ if cursor.goto_first_child():
151
+ visit(cursor)
152
+ while cursor.goto_next_sibling():
153
+ visit(cursor)
154
+ cursor.goto_parent()
155
+
156
+ visit(cursor)
157
+ return types
158
+
159
+ def format_structure(self, structure: Dict[str, Any]) -> List[str]:
160
+ """
161
+ Custom formatting for Go structure output.
162
+
163
+ Groups items logically:
164
+ 1. Package/Imports
165
+ 2. Types (structs, interfaces)
166
+ 3. Functions
167
+ 4. Methods
168
+ """
169
+ if not TREE_SITTER_AVAILABLE or 'error' in structure:
170
+ return None # Use default formatting
171
+
172
+ lines = []
173
+
174
+ # Imports
175
+ imports = structure.get('imports', [])
176
+ if imports:
177
+ lines.append(f"\nImports ({len(imports)}):")
178
+ for item in imports[:10]:
179
+ loc = self.format_location(item['line'])
180
+ name = item['name']
181
+ if len(name) > 60:
182
+ name = name[:57] + "..."
183
+ lines.append(f" {loc:30} {name}")
184
+ if len(imports) > 10:
185
+ lines.append(f" ... and {len(imports) - 10} more")
186
+
187
+ # Types section
188
+ types = structure.get('types', [])
189
+ structs = structure.get('structs', [])
190
+ interfaces = structure.get('interfaces', [])
191
+
192
+ type_count = len(types) + len(structs) + len(interfaces)
193
+ if type_count > 0:
194
+ lines.append(f"\nTypes ({type_count}):")
195
+
196
+ if structs:
197
+ lines.append(f" Structs ({len(structs)}):")
198
+ for item in structs:
199
+ loc = self.format_location(item['line'])
200
+ lines.append(f" {loc:28} {item['name']}")
201
+
202
+ if interfaces:
203
+ lines.append(f" Interfaces ({len(interfaces)}):")
204
+ for item in interfaces:
205
+ loc = self.format_location(item['line'])
206
+ lines.append(f" {loc:28} {item['name']}")
207
+
208
+ if types:
209
+ lines.append(f" Other Types ({len(types)}):")
210
+ for item in types:
211
+ loc = self.format_location(item['line'])
212
+ lines.append(f" {loc:28} {item['name']}")
213
+
214
+ # Functions
215
+ functions = structure.get('functions', [])
216
+ if functions:
217
+ lines.append(f"\nFunctions ({len(functions)}):")
218
+ for item in functions:
219
+ loc = self.format_location(item['line'])
220
+ lines.append(f" {loc:30} func {item['name']}()")
221
+
222
+ # Methods
223
+ methods = structure.get('methods', [])
224
+ if methods:
225
+ lines.append(f"\nMethods ({len(methods)}):")
226
+ for item in methods:
227
+ loc = self.format_location(item['line'])
228
+ lines.append(f" {loc:30} func {item['name']}()")
229
+
230
+ return lines if lines else None
@@ -0,0 +1,272 @@
1
+ """
2
+ JavaScript/JSX file analyzer using tree-sitter.
3
+
4
+ Extracts:
5
+ - Functions (regular, arrow, async)
6
+ - Classes
7
+ - Imports/Exports
8
+ - Variables (const, let, var)
9
+ """
10
+
11
+ from typing import Dict, Any, List
12
+ from .treesitter_base import TreeSitterAnalyzer, TREE_SITTER_AVAILABLE
13
+ from ..registry import register
14
+
15
+
16
+ @register(['.js', '.jsx', '.mjs', '.cjs'], name='JavaScript', icon='📜')
17
+ class JavaScriptAnalyzer(TreeSitterAnalyzer):
18
+ """
19
+ Analyzer for JavaScript source files.
20
+
21
+ Features:
22
+ - Functions (declarations, expressions, arrow functions)
23
+ - Classes and methods
24
+ - Import/export statements
25
+ - Variable declarations
26
+
27
+ All positions are exact (tree-sitter preserves source locations).
28
+ """
29
+
30
+ language_name = 'javascript'
31
+
32
+ def __init__(self, lines: List[str], **kwargs):
33
+ """Initialize JavaScript analyzer."""
34
+ self.node_type_map = {
35
+ 'function': 'function_declaration',
36
+ 'class': 'class_declaration',
37
+ 'import': 'import_statement',
38
+ }
39
+ super().__init__(lines, **kwargs)
40
+
41
+ def extract_custom(self) -> Dict[str, List[Dict[str, Any]]]:
42
+ """Extract JavaScript-specific items."""
43
+ return {
44
+ 'arrow_functions': self._extract_arrow_functions(),
45
+ 'function_expressions': self._extract_function_expressions(),
46
+ 'exports': self._extract_exports(),
47
+ 'variables': self._extract_variables(),
48
+ }
49
+
50
+ def _extract_arrow_functions(self) -> List[Dict[str, Any]]:
51
+ """Extract arrow function expressions."""
52
+ if not self.tree:
53
+ return []
54
+
55
+ arrow_funcs = []
56
+ cursor = self.tree.walk()
57
+
58
+ def visit(cursor):
59
+ node = cursor.node
60
+
61
+ if node.type == 'arrow_function':
62
+ # Try to get the variable name it's assigned to
63
+ parent = node.parent
64
+ name = '<arrow>'
65
+
66
+ if parent and parent.type == 'variable_declarator':
67
+ name_node = parent.child_by_field_name('name')
68
+ if name_node:
69
+ name = name_node.text.decode('utf-8')
70
+
71
+ line = node.start_point[0] + 1
72
+ if self.in_focus_range(line):
73
+ arrow_funcs.append({
74
+ 'name': name,
75
+ 'line': line
76
+ })
77
+
78
+ # Recurse
79
+ if cursor.goto_first_child():
80
+ visit(cursor)
81
+ while cursor.goto_next_sibling():
82
+ visit(cursor)
83
+ cursor.goto_parent()
84
+
85
+ visit(cursor)
86
+ return arrow_funcs
87
+
88
+ def _extract_function_expressions(self) -> List[Dict[str, Any]]:
89
+ """Extract function expressions (const fn = function() {})."""
90
+ if not self.tree:
91
+ return []
92
+
93
+ func_exprs = []
94
+ cursor = self.tree.walk()
95
+
96
+ def visit(cursor):
97
+ node = cursor.node
98
+
99
+ if node.type == 'function' and node.parent:
100
+ parent = node.parent
101
+ if parent.type == 'variable_declarator':
102
+ name_node = parent.child_by_field_name('name')
103
+ if name_node:
104
+ line = node.start_point[0] + 1
105
+ if self.in_focus_range(line):
106
+ func_exprs.append({
107
+ 'name': name_node.text.decode('utf-8'),
108
+ 'line': line
109
+ })
110
+
111
+ # Recurse
112
+ if cursor.goto_first_child():
113
+ visit(cursor)
114
+ while cursor.goto_next_sibling():
115
+ visit(cursor)
116
+ cursor.goto_parent()
117
+
118
+ visit(cursor)
119
+ return func_exprs
120
+
121
+ def _extract_exports(self) -> List[Dict[str, Any]]:
122
+ """Extract export statements."""
123
+ if not self.tree:
124
+ return []
125
+
126
+ exports = []
127
+ cursor = self.tree.walk()
128
+
129
+ def visit(cursor):
130
+ node = cursor.node
131
+
132
+ if node.type == 'export_statement':
133
+ # Get exported name or use full statement text
134
+ name = node.text.decode('utf-8').strip()
135
+ if len(name) > 60:
136
+ name = name[:57] + "..."
137
+
138
+ line = node.start_point[0] + 1
139
+ if self.in_focus_range(line):
140
+ exports.append({
141
+ 'name': name,
142
+ 'line': line
143
+ })
144
+
145
+ # Recurse
146
+ if cursor.goto_first_child():
147
+ visit(cursor)
148
+ while cursor.goto_next_sibling():
149
+ visit(cursor)
150
+ cursor.goto_parent()
151
+
152
+ visit(cursor)
153
+ return exports
154
+
155
+ def _extract_variables(self) -> List[Dict[str, Any]]:
156
+ """Extract top-level variable declarations."""
157
+ if not self.tree:
158
+ return []
159
+
160
+ variables = []
161
+ cursor = self.tree.walk()
162
+
163
+ def visit(cursor):
164
+ node = cursor.node
165
+
166
+ # Only get top-level variables
167
+ if node.type == 'variable_declaration' and node.parent.type == 'program':
168
+ for child in node.children:
169
+ if child.type == 'variable_declarator':
170
+ name_node = child.child_by_field_name('name')
171
+ if name_node:
172
+ line = child.start_point[0] + 1
173
+ if self.in_focus_range(line):
174
+ variables.append({
175
+ 'name': name_node.text.decode('utf-8'),
176
+ 'line': line
177
+ })
178
+
179
+ # Recurse
180
+ if cursor.goto_first_child():
181
+ visit(cursor)
182
+ while cursor.goto_next_sibling():
183
+ visit(cursor)
184
+ cursor.goto_parent()
185
+
186
+ visit(cursor)
187
+ return variables
188
+
189
+ def format_structure(self, structure: Dict[str, Any]) -> List[str]:
190
+ """
191
+ Custom formatting for JavaScript structure output.
192
+
193
+ Groups items logically:
194
+ 1. Imports/Exports
195
+ 2. Classes
196
+ 3. Functions (declarations, expressions, arrow)
197
+ 4. Variables
198
+ """
199
+ if not TREE_SITTER_AVAILABLE or 'error' in structure:
200
+ return None
201
+
202
+ lines = []
203
+
204
+ # Imports
205
+ imports = structure.get('imports', [])
206
+ if imports:
207
+ lines.append(f"\nImports ({len(imports)}):")
208
+ for item in imports[:10]:
209
+ loc = self.format_location(item['line'])
210
+ name = item['name']
211
+ if len(name) > 60:
212
+ name = name[:57] + "..."
213
+ lines.append(f" {loc:30} {name}")
214
+ if len(imports) > 10:
215
+ lines.append(f" ... and {len(imports) - 10} more")
216
+
217
+ # Exports
218
+ exports = structure.get('exports', [])
219
+ if exports:
220
+ lines.append(f"\nExports ({len(exports)}):")
221
+ for item in exports[:10]:
222
+ loc = self.format_location(item['line'])
223
+ lines.append(f" {loc:30} {item['name']}")
224
+ if len(exports) > 10:
225
+ lines.append(f" ... and {len(exports) - 10} more")
226
+
227
+ # Classes
228
+ classes = structure.get('classes', [])
229
+ if classes:
230
+ lines.append(f"\nClasses ({len(classes)}):")
231
+ for item in classes:
232
+ loc = self.format_location(item['line'])
233
+ lines.append(f" {loc:30} class {item['name']}")
234
+
235
+ # Functions
236
+ functions = structure.get('functions', [])
237
+ arrow_functions = structure.get('arrow_functions', [])
238
+ func_expressions = structure.get('function_expressions', [])
239
+
240
+ func_count = len(functions) + len(arrow_functions) + len(func_expressions)
241
+ if func_count > 0:
242
+ lines.append(f"\nFunctions ({func_count}):")
243
+
244
+ if functions:
245
+ lines.append(f" Declarations ({len(functions)}):")
246
+ for item in functions:
247
+ loc = self.format_location(item['line'])
248
+ lines.append(f" {loc:28} function {item['name']}()")
249
+
250
+ if arrow_functions:
251
+ lines.append(f" Arrow Functions ({len(arrow_functions)}):")
252
+ for item in arrow_functions:
253
+ loc = self.format_location(item['line'])
254
+ lines.append(f" {loc:28} {item['name']} =>")
255
+
256
+ if func_expressions:
257
+ lines.append(f" Expressions ({len(func_expressions)}):")
258
+ for item in func_expressions:
259
+ loc = self.format_location(item['line'])
260
+ lines.append(f" {loc:28} {item['name']}")
261
+
262
+ # Variables
263
+ variables = structure.get('variables', [])
264
+ if variables:
265
+ lines.append(f"\nVariables ({len(variables)}):")
266
+ for item in variables[:15]:
267
+ loc = self.format_location(item['line'])
268
+ lines.append(f" {loc:30} {item['name']}")
269
+ if len(variables) > 15:
270
+ lines.append(f" ... and {len(variables) - 15} more")
271
+
272
+ return lines if lines else None
@@ -0,0 +1,143 @@
1
+ """JSON file analyzer."""
2
+
3
+ import json
4
+ from typing import Dict, Any, List
5
+ from .base import BaseAnalyzer
6
+ from ..registry import register
7
+
8
+
9
+ @register(['.json', '.jsonc', '.json5'], name='JSON', icon='📊')
10
+ class JSONAnalyzer(BaseAnalyzer):
11
+ """Analyzer for JSON data files"""
12
+
13
+ def __init__(self, lines: List[str], **kwargs):
14
+ super().__init__(lines, **kwargs)
15
+ self.parse_error = None
16
+ self.parsed_data = None
17
+
18
+ try:
19
+ content = '\n'.join(lines)
20
+ self.parsed_data = json.loads(content)
21
+ except Exception as e:
22
+ self.parse_error = str(e)
23
+
24
+ def analyze_structure(self) -> Dict[str, Any]:
25
+ """Analyze JSON structure."""
26
+ if self.parse_error:
27
+ return {
28
+ 'error': self.parse_error,
29
+ 'top_level_keys': [],
30
+ 'object_count': 0,
31
+ 'array_count': 0,
32
+ 'max_depth': 0,
33
+ 'value_types': {}
34
+ }
35
+
36
+ # Get top-level keys with line numbers
37
+ top_level_keys = []
38
+ if isinstance(self.parsed_data, dict):
39
+ for key in self.parsed_data.keys():
40
+ # Find where this key is defined in the source file
41
+ # Look for the key as a quoted string (JSON format)
42
+ line_num = self.find_definition(f'"{key}"', case_sensitive=True)
43
+ if line_num is None:
44
+ # Fallback: try without quotes (for malformed JSON)
45
+ line_num = self.find_definition(key, case_sensitive=True)
46
+
47
+ top_level_keys.append({
48
+ 'name': key,
49
+ 'line': line_num if line_num is not None else 1
50
+ })
51
+
52
+ # Count objects and arrays
53
+ object_count, array_count = self._count_structures(self.parsed_data)
54
+
55
+ # Calculate max depth
56
+ max_depth = self._calculate_depth(self.parsed_data)
57
+
58
+ # Count value types
59
+ value_types = self._count_value_types(self.parsed_data)
60
+
61
+ return {
62
+ 'top_level_keys': top_level_keys,
63
+ 'object_count': object_count,
64
+ 'array_count': array_count,
65
+ 'max_depth': max_depth,
66
+ 'value_types': value_types
67
+ }
68
+
69
+ def _count_structures(self, obj: Any) -> tuple[int, int]:
70
+ """Count objects and arrays recursively."""
71
+ object_count = 0
72
+ array_count = 0
73
+
74
+ if isinstance(obj, dict):
75
+ object_count += 1
76
+ for value in obj.values():
77
+ obj_c, arr_c = self._count_structures(value)
78
+ object_count += obj_c
79
+ array_count += arr_c
80
+ elif isinstance(obj, list):
81
+ array_count += 1
82
+ for item in obj:
83
+ obj_c, arr_c = self._count_structures(item)
84
+ object_count += obj_c
85
+ array_count += arr_c
86
+
87
+ return object_count, array_count
88
+
89
+ def _calculate_depth(self, obj: Any, current_depth: int = 0) -> int:
90
+ """Calculate maximum nesting depth."""
91
+ if isinstance(obj, dict):
92
+ if not obj:
93
+ return current_depth
94
+ return max(self._calculate_depth(v, current_depth + 1) for v in obj.values())
95
+ elif isinstance(obj, list):
96
+ if not obj:
97
+ return current_depth
98
+ return max(self._calculate_depth(item, current_depth + 1) for item in obj)
99
+ else:
100
+ return current_depth
101
+
102
+ def _count_value_types(self, obj: Any) -> Dict[str, int]:
103
+ """Count value types in JSON."""
104
+ types = {}
105
+
106
+ def count_type(value):
107
+ type_name = type(value).__name__
108
+ if type_name == 'NoneType':
109
+ type_name = 'null'
110
+ elif type_name == 'bool':
111
+ type_name = 'boolean'
112
+ elif type_name == 'int' or type_name == 'float':
113
+ type_name = 'number'
114
+ types[type_name] = types.get(type_name, 0) + 1
115
+
116
+ def traverse(item):
117
+ if isinstance(item, dict):
118
+ for value in item.values():
119
+ traverse(value)
120
+ elif isinstance(item, list):
121
+ for element in item:
122
+ traverse(element)
123
+ else:
124
+ count_type(item)
125
+
126
+ traverse(obj)
127
+ return types
128
+
129
+ def generate_preview(self) -> List[tuple[int, str]]:
130
+ """Generate JSON preview (first 10 key/value pairs or 20 lines)."""
131
+ preview = []
132
+
133
+ if self.parse_error:
134
+ # Fallback to first 20 lines
135
+ for i, line in enumerate(self.lines[:20], 1):
136
+ preview.append((i, line))
137
+ return preview
138
+
139
+ # Show first 20 lines
140
+ for i, line in enumerate(self.lines[:20], 1):
141
+ preview.append((i, line))
142
+
143
+ return preview