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.
@@ -0,0 +1,185 @@
1
+ """Nginx configuration file analyzer."""
2
+
3
+ import re
4
+ from typing import Dict, List, Any, Optional
5
+ from ..base import FileAnalyzer, register
6
+
7
+
8
+ @register('.conf', name='Nginx', icon='🌐')
9
+ class NginxAnalyzer(FileAnalyzer):
10
+ """Nginx configuration file analyzer.
11
+
12
+ Extracts server blocks, locations, upstreams, and key directives.
13
+ """
14
+
15
+ def get_structure(self) -> Dict[str, List[Dict[str, Any]]]:
16
+ """Extract nginx config structure."""
17
+ servers = []
18
+ locations = []
19
+ upstreams = []
20
+ comments = []
21
+
22
+ # Track current context for locations
23
+ current_server = None
24
+ in_server = False
25
+ brace_depth = 0
26
+
27
+ for i, line in enumerate(self.lines, 1):
28
+ stripped = line.strip()
29
+
30
+ # Track brace depth
31
+ brace_depth += stripped.count('{')
32
+ brace_depth -= stripped.count('}')
33
+
34
+ # Top-level comment headers (typically file documentation)
35
+ if stripped.startswith('#') and i <= 10 and len(stripped) > 3:
36
+ comments.append({
37
+ 'line': i,
38
+ 'text': stripped[1:].strip()
39
+ })
40
+
41
+ # Server block
42
+ if 'server {' in stripped or stripped.startswith('server {'):
43
+ in_server = True
44
+ server_info = {
45
+ 'line': i,
46
+ 'name': 'unknown',
47
+ 'port': 'unknown'
48
+ }
49
+ # Look ahead for server_name and listen
50
+ for j in range(i, min(i + 20, len(self.lines) + 1)):
51
+ next_line = self.lines[j-1].strip()
52
+ if next_line.startswith('server_name '):
53
+ match = re.match(r'server_name\s+(.*?);', next_line)
54
+ if match:
55
+ server_info['name'] = match.group(1)
56
+ elif next_line.startswith('listen '):
57
+ match = re.match(r'listen\s+(\S+)', next_line)
58
+ if match:
59
+ port = match.group(1).rstrip(';')
60
+ # Handle various listen formats
61
+ if port.startswith('443'):
62
+ server_info['port'] = '443 (SSL)'
63
+ elif port.startswith('80'):
64
+ server_info['port'] = '80'
65
+ else:
66
+ server_info['port'] = port
67
+ # Stop at closing brace of this server block
68
+ if next_line == '}' and j > i:
69
+ break
70
+
71
+ servers.append(server_info)
72
+ current_server = server_info
73
+
74
+ # Location block (inside server)
75
+ elif in_server and brace_depth > 0 and ('location ' in stripped):
76
+ match = re.match(r'location\s+(.+?)\s*\{', stripped)
77
+ if match:
78
+ path = match.group(1)
79
+ loc_info = {
80
+ 'line': i,
81
+ 'path': path,
82
+ 'server': current_server['name'] if current_server else 'unknown'
83
+ }
84
+
85
+ # Look ahead for proxy_pass or root
86
+ for j in range(i, min(i + 15, len(self.lines) + 1)):
87
+ next_line = self.lines[j-1].strip()
88
+ if next_line.startswith('proxy_pass '):
89
+ match_proxy = re.match(r'proxy_pass\s+(.*?);', next_line)
90
+ if match_proxy:
91
+ loc_info['target'] = match_proxy.group(1)
92
+ break
93
+ elif next_line.startswith('root '):
94
+ match_root = re.match(r'root\s+(.*?);', next_line)
95
+ if match_root:
96
+ loc_info['target'] = f"static: {match_root.group(1)}"
97
+ break
98
+
99
+ locations.append(loc_info)
100
+
101
+ # Upstream block
102
+ elif 'upstream ' in stripped and '{' in stripped:
103
+ match = re.match(r'upstream\s+(\S+)\s*\{', stripped)
104
+ if match:
105
+ upstreams.append({
106
+ 'line': i,
107
+ 'name': match.group(1)
108
+ })
109
+
110
+ # Reset server context when we exit server block
111
+ if in_server and brace_depth == 0:
112
+ in_server = False
113
+ current_server = None
114
+
115
+ return {
116
+ 'comments': comments,
117
+ 'servers': servers,
118
+ 'locations': locations,
119
+ 'upstreams': upstreams
120
+ }
121
+
122
+ def extract_element(self, element_type: str, name: str) -> Optional[Dict[str, Any]]:
123
+ """Extract a server or location block.
124
+
125
+ Args:
126
+ element_type: 'server', 'location', or 'upstream'
127
+ name: Name to find (server_name, location path, or upstream name)
128
+
129
+ Returns:
130
+ Dict with block content
131
+ """
132
+ start_line = None
133
+ search_pattern = None
134
+
135
+ # Build search pattern based on element type
136
+ if element_type == 'server':
137
+ # Search for server block with this server_name
138
+ for i, line in enumerate(self.lines, 1):
139
+ if 'server {' in line or line.strip().startswith('server {'):
140
+ # Look ahead for server_name
141
+ for j in range(i, min(i + 20, len(self.lines) + 1)):
142
+ if f'server_name {name}' in self.lines[j-1]:
143
+ start_line = i
144
+ break
145
+ if start_line:
146
+ break
147
+
148
+ elif element_type == 'location':
149
+ # Search for location block with this path
150
+ search_pattern = rf'location\s+{re.escape(name)}\s*\{{'
151
+ for i, line in enumerate(self.lines, 1):
152
+ if re.search(search_pattern, line):
153
+ start_line = i
154
+ break
155
+
156
+ elif element_type == 'upstream':
157
+ # Search for upstream block with this name
158
+ search_pattern = rf'upstream\s+{re.escape(name)}\s*\{{'
159
+ for i, line in enumerate(self.lines, 1):
160
+ if re.search(search_pattern, line):
161
+ start_line = i
162
+ break
163
+
164
+ if not start_line:
165
+ return super().extract_element(element_type, name)
166
+
167
+ # Find matching closing brace
168
+ brace_depth = 0
169
+ end_line = start_line
170
+ for i in range(start_line - 1, len(self.lines)):
171
+ line = self.lines[i]
172
+ brace_depth += line.count('{')
173
+ brace_depth -= line.count('}')
174
+ if brace_depth == 0 and i >= start_line:
175
+ end_line = i + 1
176
+ break
177
+
178
+ source = '\n'.join(self.lines[start_line-1:end_line])
179
+
180
+ return {
181
+ 'name': name,
182
+ 'line_start': start_line,
183
+ 'line_end': end_line,
184
+ 'source': source,
185
+ }
@@ -0,0 +1,15 @@
1
+ """Python file analyzer - tree-sitter based."""
2
+
3
+ from ..base import register
4
+ from ..treesitter import TreeSitterAnalyzer
5
+
6
+
7
+ @register('.py', name='Python', icon='🐍')
8
+ class PythonAnalyzer(TreeSitterAnalyzer):
9
+ """Python file analyzer.
10
+
11
+ Gets structure + extraction for FREE from TreeSitterAnalyzer!
12
+
13
+ Just 3 lines of code for full Python support!
14
+ """
15
+ language = 'python'
@@ -0,0 +1,13 @@
1
+ """Rust file analyzer - tree-sitter based."""
2
+
3
+ from ..base import register
4
+ from ..treesitter import TreeSitterAnalyzer
5
+
6
+
7
+ @register('.rs', name='Rust', icon='🦀')
8
+ class RustAnalyzer(TreeSitterAnalyzer):
9
+ """Rust file analyzer.
10
+
11
+ Full Rust support in 3 lines!
12
+ """
13
+ language = 'rust'
@@ -0,0 +1,96 @@
1
+ """TOML file analyzer."""
2
+
3
+ import re
4
+ from typing import Dict, List, Any, Optional
5
+ from ..base import FileAnalyzer, register
6
+
7
+
8
+ @register('.toml', name='TOML', icon='📋')
9
+ class TomlAnalyzer(FileAnalyzer):
10
+ """TOML file analyzer.
11
+
12
+ Extracts sections ([section]) and key-value pairs.
13
+ """
14
+
15
+ def get_structure(self) -> Dict[str, List[Dict[str, Any]]]:
16
+ """Extract TOML sections and top-level keys."""
17
+ sections = []
18
+ keys = []
19
+
20
+ current_section = None
21
+
22
+ for i, line in enumerate(self.lines, 1):
23
+ stripped = line.strip()
24
+
25
+ # Skip empty lines and comments
26
+ if not stripped or stripped.startswith('#'):
27
+ continue
28
+
29
+ # Section header: [section] or [[array]]
30
+ section_match = re.match(r'^\[+([^\]]+)\]+', stripped)
31
+ if section_match:
32
+ section_name = section_match.group(1).strip()
33
+ sections.append({
34
+ 'line': i,
35
+ 'name': section_name,
36
+ })
37
+ current_section = section_name
38
+ continue
39
+
40
+ # Key-value pair
41
+ key_match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_-]*)\s*=', stripped)
42
+ if key_match:
43
+ key_name = key_match.group(1)
44
+
45
+ # Only include top-level keys (no section)
46
+ if current_section is None:
47
+ keys.append({
48
+ 'line': i,
49
+ 'name': key_name,
50
+ })
51
+
52
+ result = {}
53
+ if sections:
54
+ result['sections'] = sections
55
+ if keys:
56
+ result['keys'] = keys
57
+
58
+ return result
59
+
60
+ def extract_element(self, element_type: str, name: str) -> Optional[Dict[str, Any]]:
61
+ """Extract a TOML section or key.
62
+
63
+ Args:
64
+ element_type: 'section' or 'key'
65
+ name: Section/key name to find
66
+
67
+ Returns:
68
+ Dict with section/key content
69
+ """
70
+ # Try to find as section first
71
+ for i, line in enumerate(self.lines, 1):
72
+ stripped = line.strip()
73
+
74
+ # Match section header
75
+ section_match = re.match(r'^\[+([^\]]+)\]+', stripped)
76
+ if section_match and section_match.group(1).strip() == name:
77
+ start_line = i
78
+
79
+ # Find end of section (next section or EOF)
80
+ end_line = len(self.lines) + 1
81
+ for j in range(i, len(self.lines)):
82
+ if re.match(r'^\[+[^\]]+\]+', self.lines[j].strip()):
83
+ end_line = j + 1
84
+ break
85
+
86
+ source = '\n'.join(self.lines[start_line-1:end_line-1])
87
+
88
+ return {
89
+ 'name': name,
90
+ 'line_start': start_line,
91
+ 'line_end': end_line - 1,
92
+ 'source': source,
93
+ }
94
+
95
+ # Fall back to grep-based search
96
+ return super().extract_element(element_type, name)
@@ -0,0 +1,24 @@
1
+ """TypeScript file analyzer - tree-sitter based."""
2
+
3
+ from ..base import register
4
+ from ..treesitter import TreeSitterAnalyzer
5
+
6
+
7
+ @register('.ts', name='TypeScript', icon='🔷')
8
+ @register('.tsx', name='TypeScript React', icon='⚛️')
9
+ class TypeScriptAnalyzer(TreeSitterAnalyzer):
10
+ """TypeScript file analyzer.
11
+
12
+ Full TypeScript support via tree-sitter!
13
+ Extracts:
14
+ - Import/export statements (ES6 modules)
15
+ - Function declarations
16
+ - Class definitions
17
+ - Interfaces
18
+ - Type definitions
19
+ - Arrow functions
20
+
21
+ Supports both .ts and .tsx (React) files.
22
+ Works on all platforms (Windows, Linux, macOS).
23
+ """
24
+ language = 'typescript'
@@ -0,0 +1,110 @@
1
+ """YAML and JSON file analyzers."""
2
+
3
+ import json
4
+ import re
5
+ from typing import Dict, List, Any, Optional
6
+ from ..base import FileAnalyzer, register
7
+
8
+
9
+ @register('.yaml', '.yml', name='YAML', icon='📋')
10
+ class YamlAnalyzer(FileAnalyzer):
11
+ """YAML file analyzer.
12
+
13
+ Extracts top-level keys.
14
+ """
15
+
16
+ def get_structure(self) -> Dict[str, List[Dict[str, Any]]]:
17
+ """Extract YAML top-level keys."""
18
+ keys = []
19
+
20
+ for i, line in enumerate(self.lines, 1):
21
+ # Match top-level key (no indentation)
22
+ match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:', line)
23
+ if match:
24
+ key_name = match.group(1)
25
+ keys.append({
26
+ 'line': i,
27
+ 'name': key_name,
28
+ })
29
+
30
+ return {'keys': keys}
31
+
32
+ def extract_element(self, element_type: str, name: str) -> Optional[Dict[str, Any]]:
33
+ """Extract a YAML key and its value.
34
+
35
+ Args:
36
+ element_type: 'key'
37
+ name: Key name to find
38
+
39
+ Returns:
40
+ Dict with key content
41
+ """
42
+ # Find the key
43
+ start_line = None
44
+
45
+ for i, line in enumerate(self.lines, 1):
46
+ match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:', line)
47
+ if match and match.group(1) == name:
48
+ start_line = i
49
+ break
50
+
51
+ if not start_line:
52
+ return super().extract_element(element_type, name)
53
+
54
+ # Find end of this key (next top-level key or end of file)
55
+ end_line = len(self.lines)
56
+ for i in range(start_line, len(self.lines)):
57
+ line = self.lines[i]
58
+ # Next top-level key?
59
+ if re.match(r'^[a-zA-Z_][a-zA-Z0-9_-]*\s*:', line):
60
+ end_line = i
61
+ break
62
+
63
+ source = '\n'.join(self.lines[start_line-1:end_line])
64
+
65
+ return {
66
+ 'name': name,
67
+ 'line_start': start_line,
68
+ 'line_end': end_line,
69
+ 'source': source,
70
+ }
71
+
72
+
73
+ @register('.json', name='JSON', icon='📊')
74
+ class JsonAnalyzer(FileAnalyzer):
75
+ """JSON file analyzer.
76
+
77
+ Extracts top-level keys.
78
+ """
79
+
80
+ def get_structure(self) -> Dict[str, List[Dict[str, Any]]]:
81
+ """Extract JSON top-level keys."""
82
+ try:
83
+ data = json.loads(self.content)
84
+
85
+ if not isinstance(data, dict):
86
+ return {}
87
+
88
+ keys = []
89
+ for key in data.keys():
90
+ # Find line number by searching
91
+ line_num = self._find_key_line(key)
92
+ keys.append({
93
+ 'line': line_num,
94
+ 'name': key,
95
+ })
96
+
97
+ return {'keys': keys}
98
+
99
+ except json.JSONDecodeError:
100
+ return {}
101
+
102
+ def _find_key_line(self, key: str) -> int:
103
+ """Find line number where key is defined."""
104
+ pattern = rf'"{re.escape(key)}"\s*:'
105
+
106
+ for i, line in enumerate(self.lines, 1):
107
+ if re.search(pattern, line):
108
+ return i
109
+
110
+ return 1 # Default to line 1