blasttrace 1.2.0__py2.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.

Potentially problematic release.


This version of blasttrace might be problematic. Click here for more details.

@@ -0,0 +1,110 @@
1
+ """
2
+ BlastTrace Dart & Flutter Mobile/Client Parser
3
+ Parses Dart models, fromJson/toJson serializers, and HTTP/Dio API route consumers
4
+ in Flutter mobile, tablet, and POS applications.
5
+ """
6
+
7
+ import os
8
+ import re
9
+ from typing import Dict, Any, List
10
+
11
+
12
+ def _normalize_route_path(raw_url: str) -> str:
13
+ cleaned = raw_url.strip().strip("\"'`")
14
+ cleaned = re.sub(r"^https?://[^/]+", "", cleaned)
15
+ cleaned = cleaned.split("?")[0]
16
+ cleaned = re.sub(r"\$([a-zA-Z0-9_]+)", ":param", cleaned)
17
+ cleaned = re.sub(r"\$\{([^}]+)\}", ":param", cleaned)
18
+ if not cleaned.startswith("/"):
19
+ cleaned = "/" + cleaned
20
+ if len(cleaned) > 1 and cleaned.endswith("/"):
21
+ cleaned = cleaned.rstrip("/")
22
+ return cleaned
23
+
24
+
25
+ def parse_dart_content(content: str, rel_path: str = "") -> Dict[str, Any]:
26
+ """
27
+ Extracts Flutter Dart classes, models, fromJson field accesses,
28
+ and HTTP API route consumer calls from Dart source code.
29
+ """
30
+ classes: List[Dict[str, Any]] = []
31
+ api_calls: List[Dict[str, Any]] = []
32
+ accessed_properties: List[str] = []
33
+
34
+ # 1. Parse Dart Classes and Models
35
+ class_regex = re.compile(r"class\s+([A-Za-z0-9_]+)(?:\s+extends\s+[A-Za-z0-9_]+)?\s*\{", re.MULTILINE)
36
+ for m in class_regex.finditer(content):
37
+ cname = m.group(1)
38
+ classes.append({
39
+ "name": cname,
40
+ "type": "dart_class",
41
+ "file": rel_path,
42
+ "line": content[:m.start()].count("\n") + 1,
43
+ })
44
+
45
+ # 2. Parse fromJson / JSON field deserialization: json['field_name'] or map['field_name']
46
+ json_field_regex = re.compile(r"(?:json|map|data)\[['\"]([a-zA-Z0-9_]+)['\"]\]")
47
+ for jm in json_field_regex.finditer(content):
48
+ field_name = jm.group(1)
49
+ if field_name not in accessed_properties:
50
+ accessed_properties.append(field_name)
51
+
52
+ # 3. Parse Dart field declarations: final String name; or int? quantity;
53
+ field_decl_regex = re.compile(r"^\s*(?:final\s+|const\s+)?(?:[A-Za-z0-9_<>?]+\s+)+([a-zA-Z0-9_]+)\s*;", re.MULTILINE)
54
+ for fm in field_decl_regex.finditer(content):
55
+ fname = fm.group(1)
56
+ if fname not in ("key", "super", "override", "build") and fname not in accessed_properties:
57
+ accessed_properties.append(fname)
58
+
59
+ # 4. Parse HTTP and Dio API calls:
60
+ # http.get(Uri.parse('...')), dio.get('...'), client.get('...'), api.get('...')
61
+ http_call_patterns = [
62
+ re.compile(r"""(?:http|client|dio|apiClient|api|service)\.(get|post|put|patch|delete)\s*\(\s*(?:Uri\.parse\s*\(\s*)?['"]([^'"]+)['"]""", re.IGNORECASE),
63
+ re.compile(r"""['"]((?:/api/v[0-9]+|/api/|/products|/sales|/sync|/orders|/users)[^'"]*)['"]"""),
64
+ ]
65
+
66
+ for m in http_call_patterns[0].finditer(content):
67
+ method = m.group(1).upper()
68
+ raw_url = m.group(2)
69
+ route_path = _normalize_route_path(raw_url)
70
+ api_calls.append({
71
+ "method": method,
72
+ "path": route_path,
73
+ "raw_url": raw_url,
74
+ "file": rel_path,
75
+ "line": content[:m.start()].count("\n") + 1,
76
+ })
77
+
78
+ for m in http_call_patterns[1].finditer(content):
79
+ raw_url = m.group(1)
80
+ route_path = _normalize_route_path(raw_url)
81
+ if not any(c["path"] == route_path for c in api_calls):
82
+ api_calls.append({
83
+ "method": "ANY",
84
+ "path": route_path,
85
+ "raw_url": raw_url,
86
+ "file": rel_path,
87
+ "line": content[:m.start()].count("\n") + 1,
88
+ })
89
+
90
+ return {
91
+ "file": rel_path,
92
+ "layer": "flutter_client",
93
+ "classes": classes,
94
+ "api_calls": api_calls,
95
+ "accessed_properties": accessed_properties,
96
+ }
97
+
98
+
99
+ def parse_dart_file(file_path: str, repo_root: str = "") -> Dict[str, Any]:
100
+ if not os.path.exists(file_path):
101
+ return {"error": f"File not found: {file_path}"}
102
+ rel_path = os.path.relpath(file_path, repo_root) if repo_root else file_path
103
+ rel_path = rel_path.replace("\\", "/")
104
+ try:
105
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
106
+ content = f.read()
107
+ return parse_dart_content(content, rel_path)
108
+ except Exception as exc:
109
+ return {"error": str(exc), "file": rel_path}
110
+
@@ -0,0 +1,196 @@
1
+ """
2
+ BlastTrace Go (Golang) AST Parser
3
+ Extracts Go structs, JSON / GORM field tags, REST API route handlers
4
+ (Gin, Fiber, Chi, standard net/http), and outbound HTTP queries.
5
+ """
6
+
7
+ import os
8
+ import re
9
+ from typing import Dict, Any, List, Optional, Set
10
+ from dataclasses import dataclass, field
11
+
12
+
13
+ @dataclass
14
+ class GoField:
15
+ name: str
16
+ type_name: str
17
+ json_name: Optional[str] = None
18
+ gorm_tag: Optional[str] = None
19
+
20
+
21
+ @dataclass
22
+ class GoStruct:
23
+ name: str
24
+ file_path: str
25
+ fields: List[GoField] = field(default_factory=list)
26
+
27
+
28
+ @dataclass
29
+ class GoRoute:
30
+ method: str
31
+ path: str
32
+ handler_name: str
33
+ framework: str = "gin" # gin, fiber, chi, net/http
34
+
35
+
36
+ @dataclass
37
+ class GoApiCall:
38
+ endpoint: str
39
+ method: str = "GET"
40
+ line_number: int = 1
41
+
42
+
43
+ @dataclass
44
+ class GoFunction:
45
+ name: str
46
+ receiver: Optional[str] = None
47
+ params: List[str] = field(default_factory=list)
48
+ return_types: List[str] = field(default_factory=list)
49
+
50
+
51
+ @dataclass
52
+ class GoParseResult:
53
+ structs: List[GoStruct] = field(default_factory=list)
54
+ routes: List[GoRoute] = field(default_factory=list)
55
+ api_calls: List[GoApiCall] = field(default_factory=list)
56
+ functions: List[GoFunction] = field(default_factory=list)
57
+
58
+
59
+ # Struct regex
60
+ _RE_STRUCT = re.compile(r"type\s+([A-Za-z0-9_]+)\s+struct\s*\{([^}]*)\}", re.DOTALL)
61
+ _RE_STRUCT_FIELD = re.compile(r"([A-Za-z0-9_]+)\s+([A-Za-z0-9_\*\[\]\.\{\}]+)(?:\s+`([^`]*)`)?")
62
+
63
+ # Route definitions
64
+ # Gin: r.GET("/path", handler)
65
+ _RE_GIN_ROUTE = re.compile(r"(?:[A-Za-z0-9_]+)\.(GET|POST|PUT|DELETE|PATCH)\(\s*['\"`](/[^'\"`]+)['\"`]\s*,\s*([A-Za-z0-9_\.]+)")
66
+ # Fiber: app.Get("/path", handler)
67
+ _RE_FIBER_ROUTE = re.compile(r"(?:[A-Za-z0-9_]+)\.(Get|Post|Put|Delete|Patch)\(\s*['\"`](/[^'\"`]+)['\"`]\s*,\s*([A-Za-z0-9_\.]+)")
68
+ # Chi: r.Get("/path", handler)
69
+ _RE_CHI_ROUTE = re.compile(r"(?:[A-Za-z0-9_]+)\.(Get|Post|Put|Delete|Patch|Handle|HandleFunc)\(\s*['\"`](/[^'\"`]+)['\"`]\s*,\s*([A-Za-z0-9_\.]+)")
70
+ # net/http: http.HandleFunc("/path", handler)
71
+ _RE_NETHTTP_ROUTE = re.compile(r"http\.HandleFunc\(\s*['\"`](/[^'\"`]+)['\"`]\s*,\s*([A-Za-z0-9_\.]+)")
72
+
73
+ # Outbound HTTP calls in Go:
74
+ # http.Get("url") or http.NewRequest("GET", "url")
75
+ _RE_OUTBOUND_HTTP = re.compile(r"http\.(Get|Post)\(\s*['\"`]([^'\"`]+)['\"`]")
76
+ _RE_NEW_REQUEST = re.compile(r"http\.NewRequest(?:WithContext)?\(\s*['\"`](GET|POST|PUT|DELETE|PATCH)['\"`]\s*,\s*['\"`]([^'\"`]+)['\"`]")
77
+
78
+ # Function definition:
79
+ # func (s *Service) GetUser(ctx context.Context, id int) (*User, error)
80
+ _RE_FUNC = re.compile(r"func\s*(?:\(\s*([A-Za-z0-9_\*\s]+)\s*\))?\s*([A-Za-z0-9_]+)\s*\((.*?)\)(?:\s*(?:\((.*?)\)|([A-Za-z0-9_\*\.\[\]]+)))?")
81
+
82
+
83
+ def _extract_json_tag(tag_str: str) -> Optional[str]:
84
+ if not tag_str:
85
+ return None
86
+ match = re.search(r'json:"([^",]+)', tag_str)
87
+ return match.group(1) if match else None
88
+
89
+
90
+ def _extract_gorm_tag(tag_str: str) -> Optional[str]:
91
+ if not tag_str:
92
+ return None
93
+ match = re.search(r'gorm:"([^"]+)"', tag_str)
94
+ return match.group(1) if match else None
95
+
96
+
97
+ def parse_go_content(content: str, file_path: str = "service.go") -> GoParseResult:
98
+ """Parses a Go source file (.go)."""
99
+ result = GoParseResult()
100
+
101
+ # 1. Parse Structs
102
+ for match in _RE_STRUCT.finditer(content):
103
+ struct_name = match.group(1)
104
+ body = match.group(2)
105
+ go_struct = GoStruct(name=struct_name, file_path=file_path)
106
+
107
+ for line in body.splitlines():
108
+ line = line.strip()
109
+ if not line or line.startswith("//"):
110
+ continue
111
+ fmatch = _RE_STRUCT_FIELD.match(line)
112
+ if fmatch:
113
+ fname = fmatch.group(1)
114
+ ftype = fmatch.group(2)
115
+ raw_tags = fmatch.group(3) or ""
116
+ jname = _extract_json_tag(raw_tags)
117
+ gtag = _extract_gorm_tag(raw_tags)
118
+ go_struct.fields.append(GoField(
119
+ name=fname,
120
+ type_name=ftype,
121
+ json_name=jname,
122
+ gorm_tag=gtag
123
+ ))
124
+ result.structs.append(go_struct)
125
+
126
+ # 2. Parse API Routes
127
+ # Gin
128
+ for match in _RE_GIN_ROUTE.finditer(content):
129
+ result.routes.append(GoRoute(
130
+ method=match.group(1).upper(),
131
+ path=match.group(2),
132
+ handler_name=match.group(3),
133
+ framework="gin"
134
+ ))
135
+ # Fiber
136
+ for match in _RE_FIBER_ROUTE.finditer(content):
137
+ result.routes.append(GoRoute(
138
+ method=match.group(1).upper(),
139
+ path=match.group(2),
140
+ handler_name=match.group(3),
141
+ framework="fiber"
142
+ ))
143
+ # Chi
144
+ for match in _RE_CHI_ROUTE.finditer(content):
145
+ result.routes.append(GoRoute(
146
+ method=match.group(1).upper(),
147
+ path=match.group(2),
148
+ handler_name=match.group(3),
149
+ framework="chi"
150
+ ))
151
+ # net/http
152
+ for match in _RE_NETHTTP_ROUTE.finditer(content):
153
+ result.routes.append(GoRoute(
154
+ method="ANY",
155
+ path=match.group(1),
156
+ handler_name=match.group(2),
157
+ framework="net/http"
158
+ ))
159
+
160
+ # 3. Parse Outbound HTTP Queries
161
+ for match in _RE_OUTBOUND_HTTP.finditer(content):
162
+ result.api_calls.append(GoApiCall(
163
+ method=match.group(1).upper(),
164
+ endpoint=match.group(2)
165
+ ))
166
+ for match in _RE_NEW_REQUEST.finditer(content):
167
+ result.api_calls.append(GoApiCall(
168
+ method=match.group(1).upper(),
169
+ endpoint=match.group(2)
170
+ ))
171
+
172
+ # 4. Parse Functions
173
+ for match in _RE_FUNC.finditer(content):
174
+ receiver = match.group(1).strip() if match.group(1) else None
175
+ fname = match.group(2)
176
+ raw_params = match.group(3) or ""
177
+ params = [p.strip() for p in raw_params.split(",") if p.strip()]
178
+ result.functions.append(GoFunction(
179
+ name=fname,
180
+ receiver=receiver,
181
+ params=params
182
+ ))
183
+
184
+ return result
185
+
186
+
187
+ def parse_go_file(file_path: str) -> Optional[GoParseResult]:
188
+ """Reads and parses a .go file."""
189
+ if not os.path.exists(file_path):
190
+ return None
191
+ try:
192
+ with open(file_path, "r", encoding="utf-8") as f:
193
+ content = f.read()
194
+ return parse_go_content(content, file_path)
195
+ except Exception:
196
+ return None
@@ -0,0 +1,122 @@
1
+ """
2
+ BlastTrace Kotlin & Android Mobile/Tablet Parser
3
+ Parses Kotlin data classes, SerializedName annotations, Retrofit HTTP annotations,
4
+ and OkHttp/Ktor client route consumers in native Android applications.
5
+ """
6
+
7
+ import os
8
+ import re
9
+ from typing import Dict, Any, List
10
+
11
+
12
+ def _normalize_route_path(raw_url: str) -> str:
13
+ cleaned = raw_url.strip().strip("\"'`")
14
+ cleaned = re.sub(r"^https?://[^/]+", "", cleaned)
15
+ cleaned = cleaned.split("?")[0]
16
+ cleaned = re.sub(r"\{[^}]+\}", ":param", cleaned)
17
+ cleaned = re.sub(r"\$([a-zA-Z0-9_]+)", ":param", cleaned)
18
+ if not cleaned.startswith("/"):
19
+ cleaned = "/" + cleaned
20
+ if len(cleaned) > 1 and cleaned.endswith("/"):
21
+ cleaned = cleaned.rstrip("/")
22
+ return cleaned
23
+
24
+
25
+ def parse_kotlin_content(content: str, rel_path: str = "") -> Dict[str, Any]:
26
+ """
27
+ Extracts Kotlin data classes, Retrofit endpoints, SerializedName mappings,
28
+ and HTTP API calls from Android source code.
29
+ """
30
+ classes: List[Dict[str, Any]] = []
31
+ api_calls: List[Dict[str, Any]] = []
32
+ accessed_properties: List[str] = []
33
+
34
+ # 1. Parse Kotlin Data Classes and Classes: data class Product(...), class OrderResponse
35
+ class_regex = re.compile(
36
+ r"""(?:data\s+)?class\s+([A-Za-z0-9_]+)(?:\s*\(([^)]*)\))?""",
37
+ re.MULTILINE,
38
+ )
39
+ for m in class_regex.finditer(content):
40
+ cname = m.group(1)
41
+ params = m.group(2) or ""
42
+ classes.append({
43
+ "name": cname,
44
+ "type": "kotlin_model",
45
+ "file": rel_path,
46
+ "line": content[:m.start()].count("\n") + 1,
47
+ })
48
+ # Extract constructor properties inside data class (...)
49
+ for p in params.split(","):
50
+ p_clean = p.strip()
51
+ field_match = re.search(r"""(?:val|var)\s+([a-zA-Z0-9_]+)""", p_clean)
52
+ if field_match:
53
+ fname = field_match.group(1)
54
+ if fname not in accessed_properties:
55
+ accessed_properties.append(fname)
56
+
57
+ # 2. Parse @SerializedName("field") or @SerialName("field")
58
+ ser_regex = re.compile(r"""@(?:SerializedName|SerialName|JsonProperty)\s*\(\s*["']([^"']+)["']\s*\)""")
59
+ for sm in ser_regex.finditer(content):
60
+ prop = sm.group(1)
61
+ if prop not in accessed_properties:
62
+ accessed_properties.append(prop)
63
+
64
+ # 3. Parse general property declarations: val balance: Double or var tier: String
65
+ prop_regex = re.compile(r"""(?:val|var)\s+([a-zA-Z0-9_]+)\s*:\s*[A-Za-z0-9_<>?]+""", re.MULTILINE)
66
+ for pm in prop_regex.finditer(content):
67
+ pname = pm.group(1)
68
+ if pname not in ("tag", "context", "binding", "viewModel", "compositeDisposable") and pname not in accessed_properties:
69
+ accessed_properties.append(pname)
70
+
71
+ # 4. Parse Retrofit API Interface annotations: @GET("/api/v1/..."), @POST("...")
72
+ retrofit_regex = re.compile(
73
+ r"""@(GET|POST|PUT|PATCH|DELETE)\s*\(\s*["']([^"']+)["']\s*\)""",
74
+ re.IGNORECASE,
75
+ )
76
+ for rm in retrofit_regex.finditer(content):
77
+ method = rm.group(1).upper()
78
+ raw_url = rm.group(2)
79
+ route_path = _normalize_route_path(raw_url)
80
+ api_calls.append({
81
+ "method": method,
82
+ "path": route_path,
83
+ "raw_url": raw_url,
84
+ "file": rel_path,
85
+ "line": content[:rm.start()].count("\n") + 1,
86
+ })
87
+
88
+ # 5. Parse Ktor / OkHttp / URL string literals
89
+ url_literal_regex = re.compile(r"""["']((?:/api/v[0-9]+|/api/|/products|/sales|/sync|/orders|/users)[^"']*)["']""")
90
+ for um in url_literal_regex.finditer(content):
91
+ raw_url = um.group(1)
92
+ route_path = _normalize_route_path(raw_url)
93
+ if not any(c["path"] == route_path for c in api_calls):
94
+ api_calls.append({
95
+ "method": "ANY",
96
+ "path": route_path,
97
+ "raw_url": raw_url,
98
+ "file": rel_path,
99
+ "line": content[:um.start()].count("\n") + 1,
100
+ })
101
+
102
+ return {
103
+ "file": rel_path,
104
+ "layer": "android_client",
105
+ "classes": classes,
106
+ "api_calls": api_calls,
107
+ "accessed_properties": accessed_properties,
108
+ }
109
+
110
+
111
+ def parse_kotlin_file(file_path: str, repo_root: str = "") -> Dict[str, Any]:
112
+ if not os.path.exists(file_path):
113
+ return {"error": f"File not found: {file_path}"}
114
+ rel_path = os.path.relpath(file_path, repo_root) if repo_root else file_path
115
+ rel_path = rel_path.replace("\\", "/")
116
+ try:
117
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
118
+ content = f.read()
119
+ return parse_kotlin_content(content, rel_path)
120
+ except Exception as exc:
121
+ return {"error": str(exc), "file": rel_path}
122
+