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
reveal/tree_view.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Directory tree view for reveal."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from .base import get_analyzer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def show_directory_tree(path: str, depth: int = 3, show_hidden: bool = False) -> str:
|
|
10
|
+
"""Show directory tree with file info.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
path: Directory path
|
|
14
|
+
depth: Maximum depth to traverse
|
|
15
|
+
show_hidden: Whether to show hidden files/dirs
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Formatted tree string
|
|
19
|
+
"""
|
|
20
|
+
path = Path(path)
|
|
21
|
+
|
|
22
|
+
if not path.is_dir():
|
|
23
|
+
return f"Error: {path} is not a directory"
|
|
24
|
+
|
|
25
|
+
lines = [f"š {path.name or path}/\n"]
|
|
26
|
+
_walk_directory(path, lines, depth=depth, show_hidden=show_hidden)
|
|
27
|
+
|
|
28
|
+
# Add navigation hint
|
|
29
|
+
lines.append(f"\nā reveal {path}/<file>")
|
|
30
|
+
|
|
31
|
+
return '\n'.join(lines)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _walk_directory(path: Path, lines: List[str], prefix: str = '', depth: int = 3, show_hidden: bool = False):
|
|
35
|
+
"""Recursively walk directory and build tree."""
|
|
36
|
+
if depth <= 0:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
entries = sorted(path.iterdir(), key=lambda p: (not p.is_dir(), p.name))
|
|
41
|
+
except PermissionError:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Filter hidden files/dirs
|
|
45
|
+
if not show_hidden:
|
|
46
|
+
entries = [e for e in entries if not e.name.startswith('.')]
|
|
47
|
+
|
|
48
|
+
for i, entry in enumerate(entries):
|
|
49
|
+
is_last = (i == len(entries) - 1)
|
|
50
|
+
|
|
51
|
+
# Tree characters
|
|
52
|
+
if is_last:
|
|
53
|
+
connector = 'āāā '
|
|
54
|
+
extension = ' '
|
|
55
|
+
else:
|
|
56
|
+
connector = 'āāā '
|
|
57
|
+
extension = 'ā '
|
|
58
|
+
|
|
59
|
+
if entry.is_file():
|
|
60
|
+
# Show file with metadata
|
|
61
|
+
file_info = _get_file_info(entry)
|
|
62
|
+
lines.append(f"{prefix}{connector}{file_info}")
|
|
63
|
+
|
|
64
|
+
elif entry.is_dir():
|
|
65
|
+
# Show directory
|
|
66
|
+
lines.append(f"{prefix}{connector}{entry.name}/")
|
|
67
|
+
# Recurse into subdirectory
|
|
68
|
+
_walk_directory(entry, lines, prefix + extension, depth - 1, show_hidden)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_file_info(path: Path) -> str:
|
|
72
|
+
"""Get formatted file info for tree display.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Formatted string like "app.py (247 lines, Python)"
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
# Try to get analyzer for this file
|
|
79
|
+
analyzer_class = get_analyzer(str(path))
|
|
80
|
+
|
|
81
|
+
if analyzer_class:
|
|
82
|
+
# Use analyzer to get info
|
|
83
|
+
analyzer = analyzer_class(str(path))
|
|
84
|
+
meta = analyzer.get_metadata()
|
|
85
|
+
file_type = analyzer.type_name
|
|
86
|
+
|
|
87
|
+
return f"{path.name} ({meta['lines']} lines, {file_type})"
|
|
88
|
+
else:
|
|
89
|
+
# No analyzer - just show basic info
|
|
90
|
+
stat = os.stat(path)
|
|
91
|
+
size = _format_size(stat.st_size)
|
|
92
|
+
return f"{path.name} ({size})"
|
|
93
|
+
|
|
94
|
+
except Exception:
|
|
95
|
+
# If anything fails, just show filename
|
|
96
|
+
return path.name
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _format_size(size: int) -> str:
|
|
100
|
+
"""Format file size in human-readable form."""
|
|
101
|
+
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
102
|
+
if size < 1024.0:
|
|
103
|
+
return f"{size:.1f} {unit}"
|
|
104
|
+
size /= 1024.0
|
|
105
|
+
return f"{size:.1f} TB"
|
reveal/treesitter.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Tree-sitter based analyzer for multi-language support."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Dict, List, Any, Optional
|
|
5
|
+
from .base import FileAnalyzer
|
|
6
|
+
from tree_sitter_languages import get_parser
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TreeSitterAnalyzer(FileAnalyzer):
|
|
10
|
+
"""Base class for tree-sitter based analyzers.
|
|
11
|
+
|
|
12
|
+
Provides automatic extraction for ANY tree-sitter language!
|
|
13
|
+
|
|
14
|
+
Subclass just needs to set:
|
|
15
|
+
language (str): tree-sitter language name (e.g., 'python', 'rust', 'go')
|
|
16
|
+
|
|
17
|
+
Everything else is automatic:
|
|
18
|
+
- Structure extraction (imports, functions, classes, structs)
|
|
19
|
+
- Element extraction (get specific function/class)
|
|
20
|
+
- Line number tracking
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
@register('.go', name='Go', icon='š·')
|
|
24
|
+
class GoAnalyzer(TreeSitterAnalyzer):
|
|
25
|
+
language = 'go'
|
|
26
|
+
# Done! Full support in 3 lines.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
language: str = None # Set in subclass
|
|
30
|
+
|
|
31
|
+
def __init__(self, path: str):
|
|
32
|
+
super().__init__(path)
|
|
33
|
+
self.tree = None
|
|
34
|
+
|
|
35
|
+
if self.language:
|
|
36
|
+
self._parse_tree()
|
|
37
|
+
|
|
38
|
+
def _parse_tree(self):
|
|
39
|
+
"""Parse file with tree-sitter."""
|
|
40
|
+
try:
|
|
41
|
+
with warnings.catch_warnings():
|
|
42
|
+
warnings.filterwarnings('ignore', category=FutureWarning, module='tree_sitter')
|
|
43
|
+
parser = get_parser(self.language)
|
|
44
|
+
self.tree = parser.parse(self.content.encode('utf-8'))
|
|
45
|
+
except Exception as e:
|
|
46
|
+
# Parsing failed - fall back to text analysis
|
|
47
|
+
self.tree = None
|
|
48
|
+
|
|
49
|
+
def get_structure(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
50
|
+
"""Extract structure using tree-sitter.
|
|
51
|
+
|
|
52
|
+
Returns imports, functions, classes, structs, etc.
|
|
53
|
+
Works for ANY tree-sitter language!
|
|
54
|
+
"""
|
|
55
|
+
if not self.tree:
|
|
56
|
+
return {}
|
|
57
|
+
|
|
58
|
+
structure = {}
|
|
59
|
+
|
|
60
|
+
# Extract common elements
|
|
61
|
+
structure['imports'] = self._extract_imports()
|
|
62
|
+
structure['functions'] = self._extract_functions()
|
|
63
|
+
structure['classes'] = self._extract_classes()
|
|
64
|
+
structure['structs'] = self._extract_structs()
|
|
65
|
+
|
|
66
|
+
# Remove empty categories
|
|
67
|
+
return {k: v for k, v in structure.items() if v}
|
|
68
|
+
|
|
69
|
+
def _extract_imports(self) -> List[Dict[str, Any]]:
|
|
70
|
+
"""Extract import statements."""
|
|
71
|
+
imports = []
|
|
72
|
+
|
|
73
|
+
# Common import node types across languages
|
|
74
|
+
import_types = [
|
|
75
|
+
'import_statement', # Python, JavaScript
|
|
76
|
+
'import_declaration', # Go, Java
|
|
77
|
+
'use_declaration', # Rust
|
|
78
|
+
'using_directive', # C#
|
|
79
|
+
'import_from_statement', # Python
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
for import_type in import_types:
|
|
83
|
+
nodes = self._find_nodes_by_type(import_type)
|
|
84
|
+
for node in nodes:
|
|
85
|
+
imports.append({
|
|
86
|
+
'line': node.start_point[0] + 1,
|
|
87
|
+
'content': self._get_node_text(node),
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return imports
|
|
91
|
+
|
|
92
|
+
def _extract_functions(self) -> List[Dict[str, Any]]:
|
|
93
|
+
"""Extract function definitions."""
|
|
94
|
+
functions = []
|
|
95
|
+
|
|
96
|
+
# Common function node types
|
|
97
|
+
function_types = [
|
|
98
|
+
'function_definition', # Python
|
|
99
|
+
'function_declaration', # Go, C, JavaScript
|
|
100
|
+
'function_item', # Rust
|
|
101
|
+
'method_declaration', # Java, C#
|
|
102
|
+
'function', # Generic
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
for func_type in function_types:
|
|
106
|
+
nodes = self._find_nodes_by_type(func_type)
|
|
107
|
+
for node in nodes:
|
|
108
|
+
name = self._get_function_name(node)
|
|
109
|
+
if name:
|
|
110
|
+
functions.append({
|
|
111
|
+
'line': node.start_point[0] + 1,
|
|
112
|
+
'name': name,
|
|
113
|
+
'signature': self._get_signature(node),
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
return functions
|
|
117
|
+
|
|
118
|
+
def _extract_classes(self) -> List[Dict[str, Any]]:
|
|
119
|
+
"""Extract class definitions."""
|
|
120
|
+
classes = []
|
|
121
|
+
|
|
122
|
+
class_types = [
|
|
123
|
+
'class_definition', # Python
|
|
124
|
+
'class_declaration', # Java, C#, JavaScript
|
|
125
|
+
'struct_item', # Rust (treated as class)
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
for class_type in class_types:
|
|
129
|
+
nodes = self._find_nodes_by_type(class_type)
|
|
130
|
+
for node in nodes:
|
|
131
|
+
name = self._get_class_name(node)
|
|
132
|
+
if name:
|
|
133
|
+
classes.append({
|
|
134
|
+
'line': node.start_point[0] + 1,
|
|
135
|
+
'name': name,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
return classes
|
|
139
|
+
|
|
140
|
+
def _extract_structs(self) -> List[Dict[str, Any]]:
|
|
141
|
+
"""Extract struct definitions (for languages that have them)."""
|
|
142
|
+
structs = []
|
|
143
|
+
|
|
144
|
+
struct_types = [
|
|
145
|
+
'struct_item', # Rust
|
|
146
|
+
'struct_specifier', # C/C++
|
|
147
|
+
'struct_declaration', # Go
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
for struct_type in struct_types:
|
|
151
|
+
nodes = self._find_nodes_by_type(struct_type)
|
|
152
|
+
for node in nodes:
|
|
153
|
+
name = self._get_struct_name(node)
|
|
154
|
+
if name:
|
|
155
|
+
structs.append({
|
|
156
|
+
'line': node.start_point[0] + 1,
|
|
157
|
+
'name': name,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
return structs
|
|
161
|
+
|
|
162
|
+
def extract_element(self, element_type: str, name: str) -> Optional[Dict[str, Any]]:
|
|
163
|
+
"""Extract a specific element using tree-sitter.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
element_type: 'function', 'class', 'struct', etc.
|
|
167
|
+
name: Name of the element
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Dict with source, line numbers, etc.
|
|
171
|
+
"""
|
|
172
|
+
if not self.tree:
|
|
173
|
+
return super().extract_element(element_type, name)
|
|
174
|
+
|
|
175
|
+
# Map element type to node types
|
|
176
|
+
type_map = {
|
|
177
|
+
'function': ['function_definition', 'function_declaration', 'function_item', 'method_declaration'],
|
|
178
|
+
'class': ['class_definition', 'class_declaration'],
|
|
179
|
+
'struct': ['struct_item', 'struct_specifier', 'struct_declaration'],
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
node_types = type_map.get(element_type, [element_type])
|
|
183
|
+
|
|
184
|
+
# Find matching node
|
|
185
|
+
for node_type in node_types:
|
|
186
|
+
nodes = self._find_nodes_by_type(node_type)
|
|
187
|
+
for node in nodes:
|
|
188
|
+
node_name = self._get_node_name(node)
|
|
189
|
+
if node_name == name:
|
|
190
|
+
return {
|
|
191
|
+
'name': name,
|
|
192
|
+
'line_start': node.start_point[0] + 1,
|
|
193
|
+
'line_end': node.end_point[0] + 1,
|
|
194
|
+
'source': self._get_node_text(node),
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# Fall back to grep
|
|
198
|
+
return super().extract_element(element_type, name)
|
|
199
|
+
|
|
200
|
+
def _find_nodes_by_type(self, node_type: str) -> List:
|
|
201
|
+
"""Find all nodes of a given type in the tree."""
|
|
202
|
+
if not self.tree:
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
nodes = []
|
|
206
|
+
|
|
207
|
+
def walk(node):
|
|
208
|
+
if node.type == node_type:
|
|
209
|
+
nodes.append(node)
|
|
210
|
+
for child in node.children:
|
|
211
|
+
walk(child)
|
|
212
|
+
|
|
213
|
+
walk(self.tree.root_node)
|
|
214
|
+
return nodes
|
|
215
|
+
|
|
216
|
+
def _get_node_text(self, node) -> str:
|
|
217
|
+
"""Get the source text for a node.
|
|
218
|
+
|
|
219
|
+
IMPORTANT: Tree-sitter uses byte offsets, not character offsets!
|
|
220
|
+
Must slice the UTF-8 bytes, not the string, to handle multi-byte characters.
|
|
221
|
+
"""
|
|
222
|
+
start_byte = node.start_byte
|
|
223
|
+
end_byte = node.end_byte
|
|
224
|
+
# Convert to bytes, slice, then decode back to string
|
|
225
|
+
content_bytes = self.content.encode('utf-8')
|
|
226
|
+
return content_bytes[start_byte:end_byte].decode('utf-8')
|
|
227
|
+
|
|
228
|
+
def _get_node_name(self, node) -> Optional[str]:
|
|
229
|
+
"""Get the name of a node (function/class/struct name)."""
|
|
230
|
+
# Look for 'name' or 'identifier' child
|
|
231
|
+
for child in node.children:
|
|
232
|
+
if child.type in ('identifier', 'name'):
|
|
233
|
+
return self._get_node_text(child)
|
|
234
|
+
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
def _get_function_name(self, node) -> Optional[str]:
|
|
238
|
+
"""Extract function name from function node."""
|
|
239
|
+
return self._get_node_name(node)
|
|
240
|
+
|
|
241
|
+
def _get_class_name(self, node) -> Optional[str]:
|
|
242
|
+
"""Extract class name from class node."""
|
|
243
|
+
return self._get_node_name(node)
|
|
244
|
+
|
|
245
|
+
def _get_struct_name(self, node) -> Optional[str]:
|
|
246
|
+
"""Extract struct name from struct node."""
|
|
247
|
+
return self._get_node_name(node)
|
|
248
|
+
|
|
249
|
+
def _get_signature(self, node) -> str:
|
|
250
|
+
"""Get function signature (parameters and return type only)."""
|
|
251
|
+
# Look for parameters node to extract just signature part
|
|
252
|
+
params_text = ''
|
|
253
|
+
return_type = ''
|
|
254
|
+
|
|
255
|
+
for child in node.children:
|
|
256
|
+
if child.type in ('parameters', 'parameter_list', 'formal_parameters'):
|
|
257
|
+
params_text = self._get_node_text(child)
|
|
258
|
+
elif child.type in ('return_type', 'type'):
|
|
259
|
+
return_type = ' -> ' + self._get_node_text(child).strip(': ')
|
|
260
|
+
|
|
261
|
+
if params_text:
|
|
262
|
+
return params_text + return_type
|
|
263
|
+
|
|
264
|
+
# Fallback: try to extract from first line
|
|
265
|
+
text = self._get_node_text(node)
|
|
266
|
+
first_line = text.split('\n')[0].strip()
|
|
267
|
+
|
|
268
|
+
# Remove common prefixes (def, func, fn, function, etc.)
|
|
269
|
+
for prefix in ['def ', 'func ', 'fn ', 'function ', 'async def ', 'pub fn ', 'fn ', 'async fn ']:
|
|
270
|
+
if first_line.startswith(prefix):
|
|
271
|
+
first_line = first_line[len(prefix):]
|
|
272
|
+
break
|
|
273
|
+
|
|
274
|
+
# Extract just the signature part (name + params + return)
|
|
275
|
+
# Remove the name to leave just params + return type
|
|
276
|
+
if '(' in first_line:
|
|
277
|
+
name_end = first_line.index('(')
|
|
278
|
+
signature = first_line[name_end:].rstrip(':').strip()
|
|
279
|
+
return signature
|
|
280
|
+
|
|
281
|
+
return first_line
|