IncludeCPP 3.7.3__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.
- includecpp/__init__.py +59 -0
- includecpp/__init__.pyi +255 -0
- includecpp/__main__.py +4 -0
- includecpp/cli/__init__.py +4 -0
- includecpp/cli/commands.py +8270 -0
- includecpp/cli/config_parser.py +127 -0
- includecpp/core/__init__.py +19 -0
- includecpp/core/ai_integration.py +2132 -0
- includecpp/core/build_manager.py +2416 -0
- includecpp/core/cpp_api.py +376 -0
- includecpp/core/cpp_api.pyi +95 -0
- includecpp/core/cppy_converter.py +3448 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +2075 -0
- includecpp/core/cssl/__init__.py +42 -0
- includecpp/core/cssl/cssl_builtins.py +2271 -0
- includecpp/core/cssl/cssl_builtins.pyi +1393 -0
- includecpp/core/cssl/cssl_events.py +621 -0
- includecpp/core/cssl/cssl_modules.py +2803 -0
- includecpp/core/cssl/cssl_parser.py +2575 -0
- includecpp/core/cssl/cssl_runtime.py +3051 -0
- includecpp/core/cssl/cssl_syntax.py +488 -0
- includecpp/core/cssl/cssl_types.py +1512 -0
- includecpp/core/cssl_bridge.py +882 -0
- includecpp/core/cssl_bridge.pyi +488 -0
- includecpp/core/error_catalog.py +802 -0
- includecpp/core/error_formatter.py +1016 -0
- includecpp/core/exceptions.py +97 -0
- includecpp/core/path_discovery.py +77 -0
- includecpp/core/project_ui.py +3370 -0
- includecpp/core/settings_ui.py +326 -0
- includecpp/generator/__init__.py +1 -0
- includecpp/generator/parser.cpp +1903 -0
- includecpp/generator/parser.h +281 -0
- includecpp/generator/type_resolver.cpp +363 -0
- includecpp/generator/type_resolver.h +68 -0
- includecpp/py.typed +0 -0
- includecpp/templates/cpp.proj.template +18 -0
- includecpp/vscode/__init__.py +1 -0
- includecpp/vscode/cssl/__init__.py +1 -0
- includecpp/vscode/cssl/language-configuration.json +38 -0
- includecpp/vscode/cssl/package.json +50 -0
- includecpp/vscode/cssl/snippets/cssl.snippets.json +1080 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +341 -0
- includecpp-3.7.3.dist-info/METADATA +1076 -0
- includecpp-3.7.3.dist-info/RECORD +49 -0
- includecpp-3.7.3.dist-info/WHEEL +5 -0
- includecpp-3.7.3.dist-info/entry_points.txt +2 -0
- includecpp-3.7.3.dist-info/licenses/LICENSE +21 -0
- includecpp-3.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2803 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CSSL Standard Modules
|
|
3
|
+
Provides standard library modules accessible via service-include
|
|
4
|
+
|
|
5
|
+
Available Modules:
|
|
6
|
+
@Time - Date, time, and duration operations
|
|
7
|
+
@Secrets - Secure credential and secret management
|
|
8
|
+
@Math - Mathematical operations and constants
|
|
9
|
+
@Crypto - Cryptographic functions (hashing, encoding)
|
|
10
|
+
@Net - Network operations (HTTP, sockets)
|
|
11
|
+
@IO - File and stream I/O operations
|
|
12
|
+
@JSON - JSON parsing and serialization
|
|
13
|
+
@Regex - Regular expression operations
|
|
14
|
+
@System - System information and process control
|
|
15
|
+
@Log - Logging and diagnostics
|
|
16
|
+
@Cache - In-memory caching with TTL support
|
|
17
|
+
@Queue - Message queue and task scheduling
|
|
18
|
+
@Format - String formatting and templating
|
|
19
|
+
@Desktop - Desktop widget creation (registered separately)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import time
|
|
24
|
+
import math
|
|
25
|
+
import json
|
|
26
|
+
import re
|
|
27
|
+
import hashlib
|
|
28
|
+
import base64
|
|
29
|
+
import secrets
|
|
30
|
+
import threading
|
|
31
|
+
import queue
|
|
32
|
+
from datetime import datetime, timedelta
|
|
33
|
+
from typing import Any, Dict, List, Optional, Callable, Union
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CSSLModuleBase:
|
|
37
|
+
"""Base class for CSSL standard modules"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, runtime=None):
|
|
40
|
+
self.runtime = runtime
|
|
41
|
+
self._methods: Dict[str, Callable] = {}
|
|
42
|
+
self._register_methods()
|
|
43
|
+
|
|
44
|
+
def _register_methods(self):
|
|
45
|
+
"""Override to register module methods"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def __getattr__(self, name: str) -> Any:
|
|
49
|
+
if name.startswith('_'):
|
|
50
|
+
raise AttributeError(name)
|
|
51
|
+
if name in self._methods:
|
|
52
|
+
return self._methods[name]
|
|
53
|
+
raise AttributeError(f"Module has no method '{name}'")
|
|
54
|
+
|
|
55
|
+
def get_method(self, name: str) -> Optional[Callable]:
|
|
56
|
+
return self._methods.get(name)
|
|
57
|
+
|
|
58
|
+
def list_methods(self) -> List[str]:
|
|
59
|
+
return sorted(self._methods.keys())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# =============================================================================
|
|
63
|
+
# @Time Module - Date, time, and duration operations
|
|
64
|
+
# =============================================================================
|
|
65
|
+
|
|
66
|
+
class TimeModule(CSSLModuleBase):
|
|
67
|
+
"""
|
|
68
|
+
@Time - Date, time, and duration operations
|
|
69
|
+
|
|
70
|
+
Methods:
|
|
71
|
+
now() - Current timestamp (float)
|
|
72
|
+
timestamp() - Current timestamp (int)
|
|
73
|
+
date(fmt) - Current date string
|
|
74
|
+
time(fmt) - Current time string
|
|
75
|
+
datetime(fmt) - Current datetime string
|
|
76
|
+
parse(str, fmt) - Parse string to timestamp
|
|
77
|
+
format(ts, fmt) - Format timestamp to string
|
|
78
|
+
add(ts, days, hours, minutes, seconds) - Add duration
|
|
79
|
+
diff(ts1, ts2) - Difference in seconds
|
|
80
|
+
sleep(seconds) - Sleep for duration
|
|
81
|
+
year(), month(), day() - Current date components
|
|
82
|
+
hour(), minute(), second() - Current time components
|
|
83
|
+
weekday() - Day of week (0=Monday)
|
|
84
|
+
iso() - ISO 8601 formatted string
|
|
85
|
+
utc() - Current UTC timestamp
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def _register_methods(self):
|
|
89
|
+
self._methods['now'] = self.now
|
|
90
|
+
self._methods['timestamp'] = self.timestamp
|
|
91
|
+
self._methods['date'] = self.date
|
|
92
|
+
self._methods['time'] = self.time_str
|
|
93
|
+
self._methods['datetime'] = self.datetime_str
|
|
94
|
+
self._methods['parse'] = self.parse
|
|
95
|
+
self._methods['format'] = self.format
|
|
96
|
+
self._methods['add'] = self.add
|
|
97
|
+
self._methods['diff'] = self.diff
|
|
98
|
+
self._methods['sleep'] = self.sleep
|
|
99
|
+
self._methods['year'] = self.year
|
|
100
|
+
self._methods['month'] = self.month
|
|
101
|
+
self._methods['day'] = self.day
|
|
102
|
+
self._methods['hour'] = self.hour
|
|
103
|
+
self._methods['minute'] = self.minute
|
|
104
|
+
self._methods['second'] = self.second
|
|
105
|
+
self._methods['weekday'] = self.weekday
|
|
106
|
+
self._methods['iso'] = self.iso
|
|
107
|
+
self._methods['utc'] = self.utc
|
|
108
|
+
|
|
109
|
+
def now(self) -> float:
|
|
110
|
+
return time.time()
|
|
111
|
+
|
|
112
|
+
def timestamp(self) -> int:
|
|
113
|
+
return int(time.time())
|
|
114
|
+
|
|
115
|
+
def date(self, fmt: str = '%Y-%m-%d') -> str:
|
|
116
|
+
return datetime.now().strftime(fmt)
|
|
117
|
+
|
|
118
|
+
def time_str(self, fmt: str = '%H:%M:%S') -> str:
|
|
119
|
+
return datetime.now().strftime(fmt)
|
|
120
|
+
|
|
121
|
+
def datetime_str(self, fmt: str = '%Y-%m-%d %H:%M:%S') -> str:
|
|
122
|
+
return datetime.now().strftime(fmt)
|
|
123
|
+
|
|
124
|
+
def parse(self, date_str: str, fmt: str = '%Y-%m-%d %H:%M:%S') -> float:
|
|
125
|
+
return datetime.strptime(date_str, fmt).timestamp()
|
|
126
|
+
|
|
127
|
+
def format(self, ts: float, fmt: str = '%Y-%m-%d %H:%M:%S') -> str:
|
|
128
|
+
return datetime.fromtimestamp(ts).strftime(fmt)
|
|
129
|
+
|
|
130
|
+
def add(self, ts: float = None, days: int = 0, hours: int = 0,
|
|
131
|
+
minutes: int = 0, seconds: int = 0) -> float:
|
|
132
|
+
if ts is None:
|
|
133
|
+
ts = time.time()
|
|
134
|
+
delta = timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
|
|
135
|
+
return ts + delta.total_seconds()
|
|
136
|
+
|
|
137
|
+
def diff(self, ts1: float, ts2: float) -> float:
|
|
138
|
+
return abs(ts1 - ts2)
|
|
139
|
+
|
|
140
|
+
def sleep(self, seconds: float) -> None:
|
|
141
|
+
time.sleep(seconds)
|
|
142
|
+
|
|
143
|
+
def year(self) -> int:
|
|
144
|
+
return datetime.now().year
|
|
145
|
+
|
|
146
|
+
def month(self) -> int:
|
|
147
|
+
return datetime.now().month
|
|
148
|
+
|
|
149
|
+
def day(self) -> int:
|
|
150
|
+
return datetime.now().day
|
|
151
|
+
|
|
152
|
+
def hour(self) -> int:
|
|
153
|
+
return datetime.now().hour
|
|
154
|
+
|
|
155
|
+
def minute(self) -> int:
|
|
156
|
+
return datetime.now().minute
|
|
157
|
+
|
|
158
|
+
def second(self) -> int:
|
|
159
|
+
return datetime.now().second
|
|
160
|
+
|
|
161
|
+
def weekday(self) -> int:
|
|
162
|
+
return datetime.now().weekday()
|
|
163
|
+
|
|
164
|
+
def iso(self) -> str:
|
|
165
|
+
return datetime.now().isoformat()
|
|
166
|
+
|
|
167
|
+
def utc(self) -> float:
|
|
168
|
+
return datetime.utcnow().timestamp()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# =============================================================================
|
|
172
|
+
# @Secrets Module - Secure credential and secret management
|
|
173
|
+
# =============================================================================
|
|
174
|
+
|
|
175
|
+
class SecretsModule(CSSLModuleBase):
|
|
176
|
+
"""
|
|
177
|
+
@Secrets - Secure credential and secret management
|
|
178
|
+
|
|
179
|
+
Methods:
|
|
180
|
+
generate(length) - Generate secure random string
|
|
181
|
+
token(nbytes) - Generate secure token (hex)
|
|
182
|
+
token_bytes(nbytes) - Generate secure random bytes
|
|
183
|
+
token_urlsafe(nbytes) - Generate URL-safe token
|
|
184
|
+
choice(seq) - Secure random choice
|
|
185
|
+
randint(a, b) - Secure random integer
|
|
186
|
+
compare(a, b) - Constant-time string comparison
|
|
187
|
+
store(key, value) - Store secret (memory only)
|
|
188
|
+
retrieve(key) - Retrieve stored secret
|
|
189
|
+
delete(key) - Delete stored secret
|
|
190
|
+
env(name, default) - Get environment variable
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(self, runtime=None):
|
|
194
|
+
super().__init__(runtime)
|
|
195
|
+
self._store: Dict[str, str] = {}
|
|
196
|
+
|
|
197
|
+
def _register_methods(self):
|
|
198
|
+
self._methods['generate'] = self.generate
|
|
199
|
+
self._methods['token'] = self.token
|
|
200
|
+
self._methods['token_bytes'] = self.token_bytes
|
|
201
|
+
self._methods['token_urlsafe'] = self.token_urlsafe
|
|
202
|
+
self._methods['choice'] = self.choice
|
|
203
|
+
self._methods['randint'] = self.randint
|
|
204
|
+
self._methods['compare'] = self.compare
|
|
205
|
+
self._methods['store'] = self.store
|
|
206
|
+
self._methods['retrieve'] = self.retrieve
|
|
207
|
+
self._methods['delete'] = self.delete
|
|
208
|
+
self._methods['env'] = self.env
|
|
209
|
+
|
|
210
|
+
def generate(self, length: int = 32) -> str:
|
|
211
|
+
alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
212
|
+
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
|
213
|
+
|
|
214
|
+
def token(self, nbytes: int = 32) -> str:
|
|
215
|
+
return secrets.token_hex(nbytes)
|
|
216
|
+
|
|
217
|
+
def token_bytes(self, nbytes: int = 32) -> bytes:
|
|
218
|
+
return secrets.token_bytes(nbytes)
|
|
219
|
+
|
|
220
|
+
def token_urlsafe(self, nbytes: int = 32) -> str:
|
|
221
|
+
return secrets.token_urlsafe(nbytes)
|
|
222
|
+
|
|
223
|
+
def choice(self, seq: list) -> Any:
|
|
224
|
+
return secrets.choice(seq)
|
|
225
|
+
|
|
226
|
+
def randint(self, a: int, b: int) -> int:
|
|
227
|
+
return secrets.randbelow(b - a + 1) + a
|
|
228
|
+
|
|
229
|
+
def compare(self, a: str, b: str) -> bool:
|
|
230
|
+
return secrets.compare_digest(a, b)
|
|
231
|
+
|
|
232
|
+
def store(self, key: str, value: str) -> bool:
|
|
233
|
+
self._store[key] = value
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
def retrieve(self, key: str) -> Optional[str]:
|
|
237
|
+
return self._store.get(key)
|
|
238
|
+
|
|
239
|
+
def delete(self, key: str) -> bool:
|
|
240
|
+
if key in self._store:
|
|
241
|
+
del self._store[key]
|
|
242
|
+
return True
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
def env(self, name: str, default: str = None) -> Optional[str]:
|
|
246
|
+
return os.environ.get(name, default)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# =============================================================================
|
|
250
|
+
# @Math Module - Mathematical operations and constants
|
|
251
|
+
# =============================================================================
|
|
252
|
+
|
|
253
|
+
class MathModule(CSSLModuleBase):
|
|
254
|
+
"""
|
|
255
|
+
@Math - Mathematical operations and constants
|
|
256
|
+
|
|
257
|
+
Constants:
|
|
258
|
+
PI, E, TAU, INF, NAN
|
|
259
|
+
|
|
260
|
+
Methods:
|
|
261
|
+
abs(x) - Absolute value
|
|
262
|
+
ceil(x) - Ceiling
|
|
263
|
+
floor(x) - Floor
|
|
264
|
+
round(x, digits) - Round to digits
|
|
265
|
+
trunc(x) - Truncate
|
|
266
|
+
sqrt(x) - Square root
|
|
267
|
+
pow(x, y) - Power
|
|
268
|
+
exp(x) - Exponential
|
|
269
|
+
log(x, base) - Logarithm
|
|
270
|
+
log10(x) - Log base 10
|
|
271
|
+
log2(x) - Log base 2
|
|
272
|
+
sin(x), cos(x), tan(x) - Trigonometric
|
|
273
|
+
asin(x), acos(x), atan(x) - Inverse trig
|
|
274
|
+
sinh(x), cosh(x), tanh(x) - Hyperbolic
|
|
275
|
+
degrees(x) - Radians to degrees
|
|
276
|
+
radians(x) - Degrees to radians
|
|
277
|
+
min(args), max(args) - Min/max values
|
|
278
|
+
sum(list), avg(list) - Aggregations
|
|
279
|
+
factorial(n) - Factorial
|
|
280
|
+
gcd(a, b) - Greatest common divisor
|
|
281
|
+
lcm(a, b) - Least common multiple
|
|
282
|
+
clamp(x, lo, hi) - Clamp value to range
|
|
283
|
+
lerp(a, b, t) - Linear interpolation
|
|
284
|
+
random() - Random float [0, 1)
|
|
285
|
+
randint(a, b) - Random integer [a, b]
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def _register_methods(self):
|
|
289
|
+
# Constants
|
|
290
|
+
self._methods['PI'] = lambda: math.pi
|
|
291
|
+
self._methods['E'] = lambda: math.e
|
|
292
|
+
self._methods['TAU'] = lambda: math.tau
|
|
293
|
+
self._methods['INF'] = lambda: math.inf
|
|
294
|
+
self._methods['NAN'] = lambda: math.nan
|
|
295
|
+
|
|
296
|
+
# Basic operations
|
|
297
|
+
self._methods['abs'] = abs
|
|
298
|
+
self._methods['ceil'] = math.ceil
|
|
299
|
+
self._methods['floor'] = math.floor
|
|
300
|
+
self._methods['round'] = round
|
|
301
|
+
self._methods['trunc'] = math.trunc
|
|
302
|
+
self._methods['sqrt'] = math.sqrt
|
|
303
|
+
self._methods['pow'] = pow
|
|
304
|
+
self._methods['exp'] = math.exp
|
|
305
|
+
self._methods['log'] = self.log
|
|
306
|
+
self._methods['log10'] = math.log10
|
|
307
|
+
self._methods['log2'] = math.log2
|
|
308
|
+
|
|
309
|
+
# Trigonometry
|
|
310
|
+
self._methods['sin'] = math.sin
|
|
311
|
+
self._methods['cos'] = math.cos
|
|
312
|
+
self._methods['tan'] = math.tan
|
|
313
|
+
self._methods['asin'] = math.asin
|
|
314
|
+
self._methods['acos'] = math.acos
|
|
315
|
+
self._methods['atan'] = math.atan
|
|
316
|
+
self._methods['atan2'] = math.atan2
|
|
317
|
+
self._methods['sinh'] = math.sinh
|
|
318
|
+
self._methods['cosh'] = math.cosh
|
|
319
|
+
self._methods['tanh'] = math.tanh
|
|
320
|
+
self._methods['degrees'] = math.degrees
|
|
321
|
+
self._methods['radians'] = math.radians
|
|
322
|
+
|
|
323
|
+
# Aggregations
|
|
324
|
+
self._methods['min'] = min
|
|
325
|
+
self._methods['max'] = max
|
|
326
|
+
self._methods['sum'] = sum
|
|
327
|
+
self._methods['avg'] = self.avg
|
|
328
|
+
|
|
329
|
+
# Number theory
|
|
330
|
+
self._methods['factorial'] = math.factorial
|
|
331
|
+
self._methods['gcd'] = math.gcd
|
|
332
|
+
self._methods['lcm'] = self.lcm
|
|
333
|
+
self._methods['isfinite'] = math.isfinite
|
|
334
|
+
self._methods['isinf'] = math.isinf
|
|
335
|
+
self._methods['isnan'] = math.isnan
|
|
336
|
+
|
|
337
|
+
# Utility
|
|
338
|
+
self._methods['clamp'] = self.clamp
|
|
339
|
+
self._methods['lerp'] = self.lerp
|
|
340
|
+
self._methods['random'] = self.random
|
|
341
|
+
self._methods['randint'] = self.randint
|
|
342
|
+
|
|
343
|
+
def log(self, x: float, base: float = math.e) -> float:
|
|
344
|
+
return math.log(x, base)
|
|
345
|
+
|
|
346
|
+
def avg(self, items: list) -> float:
|
|
347
|
+
if not items:
|
|
348
|
+
return 0.0
|
|
349
|
+
return sum(items) / len(items)
|
|
350
|
+
|
|
351
|
+
def lcm(self, a: int, b: int) -> int:
|
|
352
|
+
return abs(a * b) // math.gcd(a, b)
|
|
353
|
+
|
|
354
|
+
def clamp(self, x: float, lo: float, hi: float) -> float:
|
|
355
|
+
return max(lo, min(hi, x))
|
|
356
|
+
|
|
357
|
+
def lerp(self, a: float, b: float, t: float) -> float:
|
|
358
|
+
return a + (b - a) * t
|
|
359
|
+
|
|
360
|
+
def random(self) -> float:
|
|
361
|
+
import random
|
|
362
|
+
return random.random()
|
|
363
|
+
|
|
364
|
+
def randint(self, a: int, b: int) -> int:
|
|
365
|
+
import random
|
|
366
|
+
return random.randint(a, b)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# =============================================================================
|
|
370
|
+
# @Crypto Module - Cryptographic functions
|
|
371
|
+
# =============================================================================
|
|
372
|
+
|
|
373
|
+
class CryptoModule(CSSLModuleBase):
|
|
374
|
+
"""
|
|
375
|
+
@Crypto - Cryptographic functions
|
|
376
|
+
|
|
377
|
+
Methods:
|
|
378
|
+
md5(data) - MD5 hash
|
|
379
|
+
sha1(data) - SHA-1 hash
|
|
380
|
+
sha256(data) - SHA-256 hash
|
|
381
|
+
sha384(data) - SHA-384 hash
|
|
382
|
+
sha512(data) - SHA-512 hash
|
|
383
|
+
hmac(key, data, algo) - HMAC signature
|
|
384
|
+
base64_encode(data) - Base64 encode
|
|
385
|
+
base64_decode(data) - Base64 decode
|
|
386
|
+
hex_encode(data) - Hex encode
|
|
387
|
+
hex_decode(data) - Hex decode
|
|
388
|
+
uuid() - Generate UUID4
|
|
389
|
+
uuid1() - Generate UUID1
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
def _register_methods(self):
|
|
393
|
+
self._methods['md5'] = self.md5
|
|
394
|
+
self._methods['sha1'] = self.sha1
|
|
395
|
+
self._methods['sha256'] = self.sha256
|
|
396
|
+
self._methods['sha384'] = self.sha384
|
|
397
|
+
self._methods['sha512'] = self.sha512
|
|
398
|
+
self._methods['hmac'] = self.hmac
|
|
399
|
+
self._methods['base64_encode'] = self.base64_encode
|
|
400
|
+
self._methods['base64_decode'] = self.base64_decode
|
|
401
|
+
self._methods['hex_encode'] = self.hex_encode
|
|
402
|
+
self._methods['hex_decode'] = self.hex_decode
|
|
403
|
+
self._methods['uuid'] = self.uuid
|
|
404
|
+
self._methods['uuid1'] = self.uuid1
|
|
405
|
+
|
|
406
|
+
def _to_bytes(self, data: Union[str, bytes]) -> bytes:
|
|
407
|
+
if isinstance(data, str):
|
|
408
|
+
return data.encode('utf-8')
|
|
409
|
+
return data
|
|
410
|
+
|
|
411
|
+
def md5(self, data: Union[str, bytes]) -> str:
|
|
412
|
+
return hashlib.md5(self._to_bytes(data)).hexdigest()
|
|
413
|
+
|
|
414
|
+
def sha1(self, data: Union[str, bytes]) -> str:
|
|
415
|
+
return hashlib.sha1(self._to_bytes(data)).hexdigest()
|
|
416
|
+
|
|
417
|
+
def sha256(self, data: Union[str, bytes]) -> str:
|
|
418
|
+
return hashlib.sha256(self._to_bytes(data)).hexdigest()
|
|
419
|
+
|
|
420
|
+
def sha384(self, data: Union[str, bytes]) -> str:
|
|
421
|
+
return hashlib.sha384(self._to_bytes(data)).hexdigest()
|
|
422
|
+
|
|
423
|
+
def sha512(self, data: Union[str, bytes]) -> str:
|
|
424
|
+
return hashlib.sha512(self._to_bytes(data)).hexdigest()
|
|
425
|
+
|
|
426
|
+
def hmac(self, key: Union[str, bytes], data: Union[str, bytes],
|
|
427
|
+
algo: str = 'sha256') -> str:
|
|
428
|
+
import hmac as hmac_lib
|
|
429
|
+
hash_func = getattr(hashlib, algo, hashlib.sha256)
|
|
430
|
+
return hmac_lib.new(
|
|
431
|
+
self._to_bytes(key),
|
|
432
|
+
self._to_bytes(data),
|
|
433
|
+
hash_func
|
|
434
|
+
).hexdigest()
|
|
435
|
+
|
|
436
|
+
def base64_encode(self, data: Union[str, bytes]) -> str:
|
|
437
|
+
return base64.b64encode(self._to_bytes(data)).decode('utf-8')
|
|
438
|
+
|
|
439
|
+
def base64_decode(self, data: str) -> str:
|
|
440
|
+
return base64.b64decode(data).decode('utf-8')
|
|
441
|
+
|
|
442
|
+
def hex_encode(self, data: Union[str, bytes]) -> str:
|
|
443
|
+
return self._to_bytes(data).hex()
|
|
444
|
+
|
|
445
|
+
def hex_decode(self, data: str) -> str:
|
|
446
|
+
return bytes.fromhex(data).decode('utf-8')
|
|
447
|
+
|
|
448
|
+
def uuid(self) -> str:
|
|
449
|
+
import uuid
|
|
450
|
+
return str(uuid.uuid4())
|
|
451
|
+
|
|
452
|
+
def uuid1(self) -> str:
|
|
453
|
+
import uuid
|
|
454
|
+
return str(uuid.uuid1())
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
# =============================================================================
|
|
458
|
+
# @Net Module - Network operations
|
|
459
|
+
# =============================================================================
|
|
460
|
+
|
|
461
|
+
class NetModule(CSSLModuleBase):
|
|
462
|
+
"""
|
|
463
|
+
@Net - Network operations
|
|
464
|
+
|
|
465
|
+
Methods:
|
|
466
|
+
get(url, headers) - HTTP GET request
|
|
467
|
+
post(url, data, headers) - HTTP POST request
|
|
468
|
+
put(url, data, headers) - HTTP PUT request
|
|
469
|
+
delete(url, headers) - HTTP DELETE request
|
|
470
|
+
download(url, path) - Download file
|
|
471
|
+
hostname() - Get local hostname
|
|
472
|
+
ip() - Get local IP address
|
|
473
|
+
ping(host) - Check if host is reachable
|
|
474
|
+
url_encode(data) - URL encode string/dict
|
|
475
|
+
url_decode(data) - URL decode string
|
|
476
|
+
parse_url(url) - Parse URL into components
|
|
477
|
+
"""
|
|
478
|
+
|
|
479
|
+
def _register_methods(self):
|
|
480
|
+
self._methods['get'] = self.http_get
|
|
481
|
+
self._methods['post'] = self.http_post
|
|
482
|
+
self._methods['put'] = self.http_put
|
|
483
|
+
self._methods['delete'] = self.http_delete
|
|
484
|
+
self._methods['download'] = self.download
|
|
485
|
+
self._methods['hostname'] = self.hostname
|
|
486
|
+
self._methods['ip'] = self.ip
|
|
487
|
+
self._methods['ping'] = self.ping
|
|
488
|
+
self._methods['url_encode'] = self.url_encode
|
|
489
|
+
self._methods['url_decode'] = self.url_decode
|
|
490
|
+
self._methods['parse_url'] = self.parse_url
|
|
491
|
+
|
|
492
|
+
def http_get(self, url: str, headers: Dict = None) -> Dict:
|
|
493
|
+
try:
|
|
494
|
+
import urllib.request
|
|
495
|
+
import urllib.error
|
|
496
|
+
req = urllib.request.Request(url, headers=headers or {})
|
|
497
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
498
|
+
return {
|
|
499
|
+
'status': resp.status,
|
|
500
|
+
'headers': dict(resp.headers),
|
|
501
|
+
'body': resp.read().decode('utf-8')
|
|
502
|
+
}
|
|
503
|
+
except urllib.error.HTTPError as e:
|
|
504
|
+
return {'status': e.code, 'error': str(e), 'body': ''}
|
|
505
|
+
except Exception as e:
|
|
506
|
+
return {'status': 0, 'error': str(e), 'body': ''}
|
|
507
|
+
|
|
508
|
+
def http_post(self, url: str, data: Union[str, Dict] = None,
|
|
509
|
+
headers: Dict = None) -> Dict:
|
|
510
|
+
try:
|
|
511
|
+
import urllib.request
|
|
512
|
+
import urllib.error
|
|
513
|
+
if isinstance(data, dict):
|
|
514
|
+
data = json.dumps(data).encode('utf-8')
|
|
515
|
+
headers = headers or {}
|
|
516
|
+
headers['Content-Type'] = 'application/json'
|
|
517
|
+
elif isinstance(data, str):
|
|
518
|
+
data = data.encode('utf-8')
|
|
519
|
+
req = urllib.request.Request(url, data=data, headers=headers or {})
|
|
520
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
521
|
+
return {
|
|
522
|
+
'status': resp.status,
|
|
523
|
+
'headers': dict(resp.headers),
|
|
524
|
+
'body': resp.read().decode('utf-8')
|
|
525
|
+
}
|
|
526
|
+
except urllib.error.HTTPError as e:
|
|
527
|
+
return {'status': e.code, 'error': str(e), 'body': ''}
|
|
528
|
+
except Exception as e:
|
|
529
|
+
return {'status': 0, 'error': str(e), 'body': ''}
|
|
530
|
+
|
|
531
|
+
def http_put(self, url: str, data: Union[str, Dict] = None,
|
|
532
|
+
headers: Dict = None) -> Dict:
|
|
533
|
+
try:
|
|
534
|
+
import urllib.request
|
|
535
|
+
if isinstance(data, dict):
|
|
536
|
+
data = json.dumps(data).encode('utf-8')
|
|
537
|
+
headers = headers or {}
|
|
538
|
+
headers['Content-Type'] = 'application/json'
|
|
539
|
+
elif isinstance(data, str):
|
|
540
|
+
data = data.encode('utf-8')
|
|
541
|
+
req = urllib.request.Request(url, data=data, headers=headers or {},
|
|
542
|
+
method='PUT')
|
|
543
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
544
|
+
return {
|
|
545
|
+
'status': resp.status,
|
|
546
|
+
'headers': dict(resp.headers),
|
|
547
|
+
'body': resp.read().decode('utf-8')
|
|
548
|
+
}
|
|
549
|
+
except Exception as e:
|
|
550
|
+
return {'status': 0, 'error': str(e), 'body': ''}
|
|
551
|
+
|
|
552
|
+
def http_delete(self, url: str, headers: Dict = None) -> Dict:
|
|
553
|
+
try:
|
|
554
|
+
import urllib.request
|
|
555
|
+
req = urllib.request.Request(url, headers=headers or {},
|
|
556
|
+
method='DELETE')
|
|
557
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
558
|
+
return {
|
|
559
|
+
'status': resp.status,
|
|
560
|
+
'headers': dict(resp.headers),
|
|
561
|
+
'body': resp.read().decode('utf-8')
|
|
562
|
+
}
|
|
563
|
+
except Exception as e:
|
|
564
|
+
return {'status': 0, 'error': str(e), 'body': ''}
|
|
565
|
+
|
|
566
|
+
def download(self, url: str, path: str) -> bool:
|
|
567
|
+
try:
|
|
568
|
+
import urllib.request
|
|
569
|
+
urllib.request.urlretrieve(url, path)
|
|
570
|
+
return True
|
|
571
|
+
except Exception:
|
|
572
|
+
return False
|
|
573
|
+
|
|
574
|
+
def hostname(self) -> str:
|
|
575
|
+
import socket
|
|
576
|
+
return socket.gethostname()
|
|
577
|
+
|
|
578
|
+
def ip(self) -> str:
|
|
579
|
+
import socket
|
|
580
|
+
try:
|
|
581
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
582
|
+
s.connect(('8.8.8.8', 80))
|
|
583
|
+
ip = s.getsockname()[0]
|
|
584
|
+
s.close()
|
|
585
|
+
return ip
|
|
586
|
+
except Exception:
|
|
587
|
+
return '127.0.0.1'
|
|
588
|
+
|
|
589
|
+
def ping(self, host: str) -> bool:
|
|
590
|
+
import subprocess
|
|
591
|
+
import platform
|
|
592
|
+
param = '-n' if platform.system().lower() == 'windows' else '-c'
|
|
593
|
+
try:
|
|
594
|
+
result = subprocess.run(
|
|
595
|
+
['ping', param, '1', host],
|
|
596
|
+
capture_output=True,
|
|
597
|
+
timeout=5
|
|
598
|
+
)
|
|
599
|
+
return result.returncode == 0
|
|
600
|
+
except Exception:
|
|
601
|
+
return False
|
|
602
|
+
|
|
603
|
+
def url_encode(self, data: Union[str, Dict]) -> str:
|
|
604
|
+
import urllib.parse
|
|
605
|
+
if isinstance(data, dict):
|
|
606
|
+
return urllib.parse.urlencode(data)
|
|
607
|
+
return urllib.parse.quote(str(data))
|
|
608
|
+
|
|
609
|
+
def url_decode(self, data: str) -> str:
|
|
610
|
+
import urllib.parse
|
|
611
|
+
return urllib.parse.unquote(data)
|
|
612
|
+
|
|
613
|
+
def parse_url(self, url: str) -> Dict:
|
|
614
|
+
import urllib.parse
|
|
615
|
+
parsed = urllib.parse.urlparse(url)
|
|
616
|
+
return {
|
|
617
|
+
'scheme': parsed.scheme,
|
|
618
|
+
'netloc': parsed.netloc,
|
|
619
|
+
'path': parsed.path,
|
|
620
|
+
'params': parsed.params,
|
|
621
|
+
'query': parsed.query,
|
|
622
|
+
'fragment': parsed.fragment,
|
|
623
|
+
'hostname': parsed.hostname,
|
|
624
|
+
'port': parsed.port
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
# =============================================================================
|
|
629
|
+
# @IO Module - File and stream I/O operations
|
|
630
|
+
# =============================================================================
|
|
631
|
+
|
|
632
|
+
class IOModule(CSSLModuleBase):
|
|
633
|
+
"""
|
|
634
|
+
@IO - File and stream I/O operations
|
|
635
|
+
|
|
636
|
+
Methods:
|
|
637
|
+
read(path) - Read entire file
|
|
638
|
+
read_lines(path) - Read file as lines
|
|
639
|
+
read_bytes(path) - Read file as bytes
|
|
640
|
+
write(path, content) - Write content to file
|
|
641
|
+
write_lines(path, lines) - Write lines to file
|
|
642
|
+
write_bytes(path, data) - Write bytes to file
|
|
643
|
+
append(path, content) - Append to file
|
|
644
|
+
exists(path) - Check if path exists
|
|
645
|
+
isfile(path) - Check if path is file
|
|
646
|
+
isdir(path) - Check if path is directory
|
|
647
|
+
mkdir(path) - Create directory
|
|
648
|
+
mkdirs(path) - Create directory tree
|
|
649
|
+
remove(path) - Remove file
|
|
650
|
+
rmdir(path) - Remove directory
|
|
651
|
+
rename(old, new) - Rename file/directory
|
|
652
|
+
copy(src, dst) - Copy file
|
|
653
|
+
move(src, dst) - Move file
|
|
654
|
+
listdir(path) - List directory contents
|
|
655
|
+
glob(pattern) - Find files by pattern
|
|
656
|
+
size(path) - Get file size
|
|
657
|
+
mtime(path) - Get modification time
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
def _register_methods(self):
|
|
661
|
+
self._methods['read'] = self.read
|
|
662
|
+
self._methods['read_lines'] = self.read_lines
|
|
663
|
+
self._methods['read_bytes'] = self.read_bytes
|
|
664
|
+
self._methods['write'] = self.write
|
|
665
|
+
self._methods['write_lines'] = self.write_lines
|
|
666
|
+
self._methods['write_bytes'] = self.write_bytes
|
|
667
|
+
self._methods['append'] = self.append
|
|
668
|
+
self._methods['exists'] = os.path.exists
|
|
669
|
+
self._methods['isfile'] = os.path.isfile
|
|
670
|
+
self._methods['isdir'] = os.path.isdir
|
|
671
|
+
self._methods['mkdir'] = self.mkdir
|
|
672
|
+
self._methods['mkdirs'] = self.mkdirs
|
|
673
|
+
self._methods['remove'] = self.remove
|
|
674
|
+
self._methods['rmdir'] = self.rmdir
|
|
675
|
+
self._methods['rename'] = os.rename
|
|
676
|
+
self._methods['copy'] = self.copy
|
|
677
|
+
self._methods['move'] = self.move
|
|
678
|
+
self._methods['listdir'] = os.listdir
|
|
679
|
+
self._methods['glob'] = self.glob
|
|
680
|
+
self._methods['size'] = self.size
|
|
681
|
+
self._methods['mtime'] = self.mtime
|
|
682
|
+
|
|
683
|
+
def read(self, path: str, encoding: str = 'utf-8') -> str:
|
|
684
|
+
with open(path, 'r', encoding=encoding) as f:
|
|
685
|
+
return f.read()
|
|
686
|
+
|
|
687
|
+
def read_lines(self, path: str, encoding: str = 'utf-8') -> List[str]:
|
|
688
|
+
with open(path, 'r', encoding=encoding) as f:
|
|
689
|
+
return f.readlines()
|
|
690
|
+
|
|
691
|
+
def read_bytes(self, path: str) -> bytes:
|
|
692
|
+
with open(path, 'rb') as f:
|
|
693
|
+
return f.read()
|
|
694
|
+
|
|
695
|
+
def write(self, path: str, content: str, encoding: str = 'utf-8') -> bool:
|
|
696
|
+
with open(path, 'w', encoding=encoding) as f:
|
|
697
|
+
f.write(content)
|
|
698
|
+
return True
|
|
699
|
+
|
|
700
|
+
def write_lines(self, path: str, lines: List[str],
|
|
701
|
+
encoding: str = 'utf-8') -> bool:
|
|
702
|
+
with open(path, 'w', encoding=encoding) as f:
|
|
703
|
+
f.writelines(lines)
|
|
704
|
+
return True
|
|
705
|
+
|
|
706
|
+
def write_bytes(self, path: str, data: bytes) -> bool:
|
|
707
|
+
with open(path, 'wb') as f:
|
|
708
|
+
f.write(data)
|
|
709
|
+
return True
|
|
710
|
+
|
|
711
|
+
def append(self, path: str, content: str, encoding: str = 'utf-8') -> bool:
|
|
712
|
+
with open(path, 'a', encoding=encoding) as f:
|
|
713
|
+
f.write(content)
|
|
714
|
+
return True
|
|
715
|
+
|
|
716
|
+
def mkdir(self, path: str) -> bool:
|
|
717
|
+
os.makedirs(path, exist_ok=True)
|
|
718
|
+
return True
|
|
719
|
+
|
|
720
|
+
def mkdirs(self, path: str) -> bool:
|
|
721
|
+
os.makedirs(path, exist_ok=True)
|
|
722
|
+
return True
|
|
723
|
+
|
|
724
|
+
def remove(self, path: str) -> bool:
|
|
725
|
+
if os.path.isfile(path):
|
|
726
|
+
os.remove(path)
|
|
727
|
+
return True
|
|
728
|
+
return False
|
|
729
|
+
|
|
730
|
+
def rmdir(self, path: str) -> bool:
|
|
731
|
+
import shutil
|
|
732
|
+
if os.path.isdir(path):
|
|
733
|
+
shutil.rmtree(path)
|
|
734
|
+
return True
|
|
735
|
+
return False
|
|
736
|
+
|
|
737
|
+
def copy(self, src: str, dst: str) -> bool:
|
|
738
|
+
import shutil
|
|
739
|
+
shutil.copy2(src, dst)
|
|
740
|
+
return True
|
|
741
|
+
|
|
742
|
+
def move(self, src: str, dst: str) -> bool:
|
|
743
|
+
import shutil
|
|
744
|
+
shutil.move(src, dst)
|
|
745
|
+
return True
|
|
746
|
+
|
|
747
|
+
def glob(self, pattern: str) -> List[str]:
|
|
748
|
+
import glob as glob_module
|
|
749
|
+
return glob_module.glob(pattern, recursive=True)
|
|
750
|
+
|
|
751
|
+
def size(self, path: str) -> int:
|
|
752
|
+
return os.path.getsize(path)
|
|
753
|
+
|
|
754
|
+
def mtime(self, path: str) -> float:
|
|
755
|
+
return os.path.getmtime(path)
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
# =============================================================================
|
|
759
|
+
# @JSON Module - JSON parsing and serialization
|
|
760
|
+
# =============================================================================
|
|
761
|
+
|
|
762
|
+
class JSONModule(CSSLModuleBase):
|
|
763
|
+
"""
|
|
764
|
+
@JSON - JSON parsing and serialization
|
|
765
|
+
|
|
766
|
+
Methods:
|
|
767
|
+
parse(string) - Parse JSON string to object
|
|
768
|
+
stringify(obj, indent) - Convert object to JSON string
|
|
769
|
+
read(path) - Read JSON file
|
|
770
|
+
write(path, obj, indent) - Write JSON file
|
|
771
|
+
valid(string) - Check if string is valid JSON
|
|
772
|
+
get(obj, path, default) - Get nested value by path
|
|
773
|
+
set(obj, path, value) - Set nested value by path
|
|
774
|
+
merge(obj1, obj2) - Deep merge two objects
|
|
775
|
+
"""
|
|
776
|
+
|
|
777
|
+
def _register_methods(self):
|
|
778
|
+
self._methods['parse'] = self.parse
|
|
779
|
+
self._methods['stringify'] = self.stringify
|
|
780
|
+
self._methods['read'] = self.read_file
|
|
781
|
+
self._methods['write'] = self.write_file
|
|
782
|
+
self._methods['valid'] = self.valid
|
|
783
|
+
self._methods['get'] = self.get_path
|
|
784
|
+
self._methods['set'] = self.set_path
|
|
785
|
+
self._methods['merge'] = self.merge
|
|
786
|
+
|
|
787
|
+
def parse(self, string: str) -> Any:
|
|
788
|
+
return json.loads(string)
|
|
789
|
+
|
|
790
|
+
def stringify(self, obj: Any, indent: int = None) -> str:
|
|
791
|
+
return json.dumps(obj, indent=indent, ensure_ascii=False)
|
|
792
|
+
|
|
793
|
+
def read_file(self, path: str) -> Any:
|
|
794
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
795
|
+
return json.load(f)
|
|
796
|
+
|
|
797
|
+
def write_file(self, path: str, obj: Any, indent: int = 2) -> bool:
|
|
798
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
799
|
+
json.dump(obj, f, indent=indent, ensure_ascii=False)
|
|
800
|
+
return True
|
|
801
|
+
|
|
802
|
+
def valid(self, string: str) -> bool:
|
|
803
|
+
try:
|
|
804
|
+
json.loads(string)
|
|
805
|
+
return True
|
|
806
|
+
except Exception:
|
|
807
|
+
return False
|
|
808
|
+
|
|
809
|
+
def get_path(self, obj: Any, path: str, default: Any = None) -> Any:
|
|
810
|
+
keys = path.split('.')
|
|
811
|
+
current = obj
|
|
812
|
+
for key in keys:
|
|
813
|
+
if isinstance(current, dict) and key in current:
|
|
814
|
+
current = current[key]
|
|
815
|
+
elif isinstance(current, list):
|
|
816
|
+
try:
|
|
817
|
+
current = current[int(key)]
|
|
818
|
+
except (ValueError, IndexError):
|
|
819
|
+
return default
|
|
820
|
+
else:
|
|
821
|
+
return default
|
|
822
|
+
return current
|
|
823
|
+
|
|
824
|
+
def set_path(self, obj: Dict, path: str, value: Any) -> Dict:
|
|
825
|
+
keys = path.split('.')
|
|
826
|
+
current = obj
|
|
827
|
+
for key in keys[:-1]:
|
|
828
|
+
if key not in current:
|
|
829
|
+
current[key] = {}
|
|
830
|
+
current = current[key]
|
|
831
|
+
current[keys[-1]] = value
|
|
832
|
+
return obj
|
|
833
|
+
|
|
834
|
+
def merge(self, obj1: Dict, obj2: Dict) -> Dict:
|
|
835
|
+
result = obj1.copy()
|
|
836
|
+
for key, value in obj2.items():
|
|
837
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
838
|
+
result[key] = self.merge(result[key], value)
|
|
839
|
+
else:
|
|
840
|
+
result[key] = value
|
|
841
|
+
return result
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
# =============================================================================
|
|
845
|
+
# @Regex Module - Regular expression operations
|
|
846
|
+
# =============================================================================
|
|
847
|
+
|
|
848
|
+
class RegexModule(CSSLModuleBase):
|
|
849
|
+
"""
|
|
850
|
+
@Regex - Regular expression operations
|
|
851
|
+
|
|
852
|
+
Methods:
|
|
853
|
+
match(pattern, string) - Match pattern at start
|
|
854
|
+
search(pattern, string) - Search for pattern anywhere
|
|
855
|
+
findall(pattern, string) - Find all matches
|
|
856
|
+
finditer(pattern, string)- Iterate over matches
|
|
857
|
+
sub(pattern, repl, string, count) - Substitute matches
|
|
858
|
+
split(pattern, string) - Split by pattern
|
|
859
|
+
escape(string) - Escape special characters
|
|
860
|
+
compile(pattern, flags) - Compile pattern
|
|
861
|
+
test(pattern, string) - Test if pattern matches
|
|
862
|
+
"""
|
|
863
|
+
|
|
864
|
+
def _register_methods(self):
|
|
865
|
+
self._methods['match'] = self.match
|
|
866
|
+
self._methods['search'] = self.search
|
|
867
|
+
self._methods['findall'] = re.findall
|
|
868
|
+
self._methods['finditer'] = self.finditer
|
|
869
|
+
self._methods['sub'] = re.sub
|
|
870
|
+
self._methods['split'] = re.split
|
|
871
|
+
self._methods['escape'] = re.escape
|
|
872
|
+
self._methods['compile'] = re.compile
|
|
873
|
+
self._methods['test'] = self.test
|
|
874
|
+
|
|
875
|
+
def match(self, pattern: str, string: str) -> Optional[Dict]:
|
|
876
|
+
m = re.match(pattern, string)
|
|
877
|
+
if m:
|
|
878
|
+
return {
|
|
879
|
+
'match': m.group(),
|
|
880
|
+
'groups': m.groups(),
|
|
881
|
+
'start': m.start(),
|
|
882
|
+
'end': m.end(),
|
|
883
|
+
'span': m.span()
|
|
884
|
+
}
|
|
885
|
+
return None
|
|
886
|
+
|
|
887
|
+
def search(self, pattern: str, string: str) -> Optional[Dict]:
|
|
888
|
+
m = re.search(pattern, string)
|
|
889
|
+
if m:
|
|
890
|
+
return {
|
|
891
|
+
'match': m.group(),
|
|
892
|
+
'groups': m.groups(),
|
|
893
|
+
'start': m.start(),
|
|
894
|
+
'end': m.end(),
|
|
895
|
+
'span': m.span()
|
|
896
|
+
}
|
|
897
|
+
return None
|
|
898
|
+
|
|
899
|
+
def finditer(self, pattern: str, string: str) -> List[Dict]:
|
|
900
|
+
return [{
|
|
901
|
+
'match': m.group(),
|
|
902
|
+
'groups': m.groups(),
|
|
903
|
+
'start': m.start(),
|
|
904
|
+
'end': m.end()
|
|
905
|
+
} for m in re.finditer(pattern, string)]
|
|
906
|
+
|
|
907
|
+
def test(self, pattern: str, string: str) -> bool:
|
|
908
|
+
return re.search(pattern, string) is not None
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
# =============================================================================
|
|
912
|
+
# @System Module - System information and process control
|
|
913
|
+
# =============================================================================
|
|
914
|
+
|
|
915
|
+
class SystemModule(CSSLModuleBase):
|
|
916
|
+
"""
|
|
917
|
+
@System - System information and process control
|
|
918
|
+
|
|
919
|
+
Methods:
|
|
920
|
+
platform() - Operating system name
|
|
921
|
+
version() - OS version
|
|
922
|
+
arch() - CPU architecture
|
|
923
|
+
hostname() - Computer hostname
|
|
924
|
+
user() - Current username
|
|
925
|
+
home() - User home directory
|
|
926
|
+
cwd() - Current working directory
|
|
927
|
+
chdir(path) - Change directory
|
|
928
|
+
env(name, default) - Get environment variable
|
|
929
|
+
setenv(name, value) - Set environment variable
|
|
930
|
+
exec(cmd) - Execute shell command
|
|
931
|
+
spawn(cmd) - Spawn background process
|
|
932
|
+
pid() - Current process ID
|
|
933
|
+
cpus() - Number of CPUs
|
|
934
|
+
memory() - Memory info
|
|
935
|
+
uptime() - System uptime
|
|
936
|
+
exit(code) - Exit with code
|
|
937
|
+
"""
|
|
938
|
+
|
|
939
|
+
def _register_methods(self):
|
|
940
|
+
self._methods['platform'] = self.platform
|
|
941
|
+
self._methods['version'] = self.version
|
|
942
|
+
self._methods['arch'] = self.arch
|
|
943
|
+
self._methods['hostname'] = self.hostname
|
|
944
|
+
self._methods['user'] = self.user
|
|
945
|
+
self._methods['home'] = self.home
|
|
946
|
+
self._methods['cwd'] = os.getcwd
|
|
947
|
+
self._methods['chdir'] = os.chdir
|
|
948
|
+
self._methods['env'] = self.env
|
|
949
|
+
self._methods['setenv'] = self.setenv
|
|
950
|
+
self._methods['exec'] = self.exec_cmd
|
|
951
|
+
self._methods['spawn'] = self.spawn
|
|
952
|
+
self._methods['pid'] = os.getpid
|
|
953
|
+
self._methods['cpus'] = self.cpus
|
|
954
|
+
self._methods['memory'] = self.memory
|
|
955
|
+
self._methods['uptime'] = self.uptime
|
|
956
|
+
self._methods['exit'] = self.exit_sys
|
|
957
|
+
|
|
958
|
+
def platform(self) -> str:
|
|
959
|
+
import platform
|
|
960
|
+
return platform.system()
|
|
961
|
+
|
|
962
|
+
def version(self) -> str:
|
|
963
|
+
import platform
|
|
964
|
+
return platform.version()
|
|
965
|
+
|
|
966
|
+
def arch(self) -> str:
|
|
967
|
+
import platform
|
|
968
|
+
return platform.machine()
|
|
969
|
+
|
|
970
|
+
def hostname(self) -> str:
|
|
971
|
+
import socket
|
|
972
|
+
return socket.gethostname()
|
|
973
|
+
|
|
974
|
+
def user(self) -> str:
|
|
975
|
+
return os.environ.get('USER') or os.environ.get('USERNAME', '')
|
|
976
|
+
|
|
977
|
+
def home(self) -> str:
|
|
978
|
+
return os.path.expanduser('~')
|
|
979
|
+
|
|
980
|
+
def env(self, name: str, default: str = None) -> Optional[str]:
|
|
981
|
+
return os.environ.get(name, default)
|
|
982
|
+
|
|
983
|
+
def setenv(self, name: str, value: str) -> bool:
|
|
984
|
+
os.environ[name] = value
|
|
985
|
+
return True
|
|
986
|
+
|
|
987
|
+
def exec_cmd(self, cmd: str) -> Dict:
|
|
988
|
+
import subprocess
|
|
989
|
+
try:
|
|
990
|
+
result = subprocess.run(
|
|
991
|
+
cmd, shell=True, capture_output=True, text=True, timeout=300
|
|
992
|
+
)
|
|
993
|
+
return {
|
|
994
|
+
'returncode': result.returncode,
|
|
995
|
+
'stdout': result.stdout,
|
|
996
|
+
'stderr': result.stderr
|
|
997
|
+
}
|
|
998
|
+
except subprocess.TimeoutExpired:
|
|
999
|
+
return {'returncode': -1, 'stdout': '', 'stderr': 'Command timed out'}
|
|
1000
|
+
except Exception as e:
|
|
1001
|
+
return {'returncode': -1, 'stdout': '', 'stderr': str(e)}
|
|
1002
|
+
|
|
1003
|
+
def spawn(self, cmd: str) -> int:
|
|
1004
|
+
import subprocess
|
|
1005
|
+
proc = subprocess.Popen(cmd, shell=True)
|
|
1006
|
+
return proc.pid
|
|
1007
|
+
|
|
1008
|
+
def cpus(self) -> int:
|
|
1009
|
+
return os.cpu_count() or 1
|
|
1010
|
+
|
|
1011
|
+
def memory(self) -> Dict:
|
|
1012
|
+
try:
|
|
1013
|
+
import psutil
|
|
1014
|
+
mem = psutil.virtual_memory()
|
|
1015
|
+
return {
|
|
1016
|
+
'total': mem.total,
|
|
1017
|
+
'available': mem.available,
|
|
1018
|
+
'used': mem.used,
|
|
1019
|
+
'percent': mem.percent
|
|
1020
|
+
}
|
|
1021
|
+
except ImportError:
|
|
1022
|
+
return {'total': 0, 'available': 0, 'used': 0, 'percent': 0}
|
|
1023
|
+
|
|
1024
|
+
def uptime(self) -> float:
|
|
1025
|
+
try:
|
|
1026
|
+
import psutil
|
|
1027
|
+
return time.time() - psutil.boot_time()
|
|
1028
|
+
except ImportError:
|
|
1029
|
+
return 0.0
|
|
1030
|
+
|
|
1031
|
+
def exit_sys(self, code: int = 0) -> None:
|
|
1032
|
+
raise SystemExit(code)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
# =============================================================================
|
|
1036
|
+
# @Log Module - Logging and diagnostics
|
|
1037
|
+
# =============================================================================
|
|
1038
|
+
|
|
1039
|
+
class LogModule(CSSLModuleBase):
|
|
1040
|
+
"""
|
|
1041
|
+
@Log - Logging and diagnostics
|
|
1042
|
+
|
|
1043
|
+
Methods:
|
|
1044
|
+
debug(msg) - Debug level message
|
|
1045
|
+
info(msg) - Info level message
|
|
1046
|
+
warn(msg) - Warning level message
|
|
1047
|
+
error(msg) - Error level message
|
|
1048
|
+
fatal(msg) - Fatal level message
|
|
1049
|
+
log(level, msg) - Log with custom level
|
|
1050
|
+
setLevel(level) - Set minimum log level
|
|
1051
|
+
setOutput(path) - Set log file output
|
|
1052
|
+
setFormat(fmt) - Set log format
|
|
1053
|
+
clear() - Clear log buffer
|
|
1054
|
+
history(count) - Get recent log entries
|
|
1055
|
+
"""
|
|
1056
|
+
|
|
1057
|
+
LEVELS = {'DEBUG': 0, 'INFO': 1, 'WARN': 2, 'ERROR': 3, 'FATAL': 4}
|
|
1058
|
+
|
|
1059
|
+
def __init__(self, runtime=None):
|
|
1060
|
+
super().__init__(runtime)
|
|
1061
|
+
self._level = 'DEBUG'
|
|
1062
|
+
self._output = None
|
|
1063
|
+
self._format = '[{timestamp}] [{level}] {message}'
|
|
1064
|
+
self._history: List[Dict] = []
|
|
1065
|
+
self._max_history = 1000
|
|
1066
|
+
|
|
1067
|
+
def _register_methods(self):
|
|
1068
|
+
self._methods['debug'] = self.debug
|
|
1069
|
+
self._methods['info'] = self.info
|
|
1070
|
+
self._methods['warn'] = self.warn
|
|
1071
|
+
self._methods['error'] = self.error
|
|
1072
|
+
self._methods['fatal'] = self.fatal
|
|
1073
|
+
self._methods['log'] = self.log
|
|
1074
|
+
self._methods['setLevel'] = self.setLevel
|
|
1075
|
+
self._methods['setOutput'] = self.setOutput
|
|
1076
|
+
self._methods['setFormat'] = self.setFormat
|
|
1077
|
+
self._methods['clear'] = self.clear
|
|
1078
|
+
self._methods['history'] = self.history
|
|
1079
|
+
|
|
1080
|
+
def _write(self, level: str, msg: str):
|
|
1081
|
+
if self.LEVELS.get(level, 0) < self.LEVELS.get(self._level, 0):
|
|
1082
|
+
return
|
|
1083
|
+
|
|
1084
|
+
entry = {
|
|
1085
|
+
'timestamp': datetime.now().isoformat(),
|
|
1086
|
+
'level': level,
|
|
1087
|
+
'message': msg
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
formatted = self._format.format(**entry)
|
|
1091
|
+
print(formatted)
|
|
1092
|
+
|
|
1093
|
+
self._history.append(entry)
|
|
1094
|
+
if len(self._history) > self._max_history:
|
|
1095
|
+
self._history.pop(0)
|
|
1096
|
+
|
|
1097
|
+
if self._output:
|
|
1098
|
+
try:
|
|
1099
|
+
with open(self._output, 'a', encoding='utf-8') as f:
|
|
1100
|
+
f.write(formatted + '\n')
|
|
1101
|
+
except Exception:
|
|
1102
|
+
pass
|
|
1103
|
+
|
|
1104
|
+
def debug(self, msg: str):
|
|
1105
|
+
self._write('DEBUG', msg)
|
|
1106
|
+
|
|
1107
|
+
def info(self, msg: str):
|
|
1108
|
+
self._write('INFO', msg)
|
|
1109
|
+
|
|
1110
|
+
def warn(self, msg: str):
|
|
1111
|
+
self._write('WARN', msg)
|
|
1112
|
+
|
|
1113
|
+
def error(self, msg: str):
|
|
1114
|
+
self._write('ERROR', msg)
|
|
1115
|
+
|
|
1116
|
+
def fatal(self, msg: str):
|
|
1117
|
+
self._write('FATAL', msg)
|
|
1118
|
+
|
|
1119
|
+
def log(self, level: str, msg: str):
|
|
1120
|
+
self._write(level.upper(), msg)
|
|
1121
|
+
|
|
1122
|
+
def setLevel(self, level: str) -> bool:
|
|
1123
|
+
if level.upper() in self.LEVELS:
|
|
1124
|
+
self._level = level.upper()
|
|
1125
|
+
return True
|
|
1126
|
+
return False
|
|
1127
|
+
|
|
1128
|
+
def setOutput(self, path: str) -> bool:
|
|
1129
|
+
self._output = path
|
|
1130
|
+
return True
|
|
1131
|
+
|
|
1132
|
+
def setFormat(self, fmt: str) -> bool:
|
|
1133
|
+
self._format = fmt
|
|
1134
|
+
return True
|
|
1135
|
+
|
|
1136
|
+
def clear(self):
|
|
1137
|
+
self._history.clear()
|
|
1138
|
+
|
|
1139
|
+
def history(self, count: int = 100) -> List[Dict]:
|
|
1140
|
+
return self._history[-count:]
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
# =============================================================================
|
|
1144
|
+
# @Cache Module - In-memory caching with TTL support
|
|
1145
|
+
# =============================================================================
|
|
1146
|
+
|
|
1147
|
+
class CacheModule(CSSLModuleBase):
|
|
1148
|
+
"""
|
|
1149
|
+
@Cache - In-memory caching with TTL support
|
|
1150
|
+
|
|
1151
|
+
Methods:
|
|
1152
|
+
get(key, default) - Get cached value
|
|
1153
|
+
set(key, value, ttl) - Set value with optional TTL (seconds)
|
|
1154
|
+
has(key) - Check if key exists and not expired
|
|
1155
|
+
delete(key) - Delete cached value
|
|
1156
|
+
clear() - Clear all cached values
|
|
1157
|
+
keys() - Get all cache keys
|
|
1158
|
+
size() - Get number of cached items
|
|
1159
|
+
stats() - Get cache statistics
|
|
1160
|
+
cleanup() - Remove expired entries
|
|
1161
|
+
"""
|
|
1162
|
+
|
|
1163
|
+
def __init__(self, runtime=None):
|
|
1164
|
+
super().__init__(runtime)
|
|
1165
|
+
self._cache: Dict[str, Dict] = {}
|
|
1166
|
+
self._hits = 0
|
|
1167
|
+
self._misses = 0
|
|
1168
|
+
|
|
1169
|
+
def _register_methods(self):
|
|
1170
|
+
self._methods['get'] = self.get
|
|
1171
|
+
self._methods['set'] = self.set
|
|
1172
|
+
self._methods['has'] = self.has
|
|
1173
|
+
self._methods['delete'] = self.delete
|
|
1174
|
+
self._methods['clear'] = self.clear
|
|
1175
|
+
self._methods['keys'] = self.keys
|
|
1176
|
+
self._methods['size'] = self.size
|
|
1177
|
+
self._methods['stats'] = self.stats
|
|
1178
|
+
self._methods['cleanup'] = self.cleanup
|
|
1179
|
+
|
|
1180
|
+
def _is_expired(self, entry: Dict) -> bool:
|
|
1181
|
+
if entry.get('ttl') is None:
|
|
1182
|
+
return False
|
|
1183
|
+
return time.time() > entry['expires']
|
|
1184
|
+
|
|
1185
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
1186
|
+
entry = self._cache.get(key)
|
|
1187
|
+
if entry is None or self._is_expired(entry):
|
|
1188
|
+
self._misses += 1
|
|
1189
|
+
if entry:
|
|
1190
|
+
del self._cache[key]
|
|
1191
|
+
return default
|
|
1192
|
+
self._hits += 1
|
|
1193
|
+
return entry['value']
|
|
1194
|
+
|
|
1195
|
+
def set(self, key: str, value: Any, ttl: int = None) -> bool:
|
|
1196
|
+
entry = {'value': value, 'ttl': ttl}
|
|
1197
|
+
if ttl is not None:
|
|
1198
|
+
entry['expires'] = time.time() + ttl
|
|
1199
|
+
self._cache[key] = entry
|
|
1200
|
+
return True
|
|
1201
|
+
|
|
1202
|
+
def has(self, key: str) -> bool:
|
|
1203
|
+
entry = self._cache.get(key)
|
|
1204
|
+
if entry is None:
|
|
1205
|
+
return False
|
|
1206
|
+
if self._is_expired(entry):
|
|
1207
|
+
del self._cache[key]
|
|
1208
|
+
return False
|
|
1209
|
+
return True
|
|
1210
|
+
|
|
1211
|
+
def delete(self, key: str) -> bool:
|
|
1212
|
+
if key in self._cache:
|
|
1213
|
+
del self._cache[key]
|
|
1214
|
+
return True
|
|
1215
|
+
return False
|
|
1216
|
+
|
|
1217
|
+
def clear(self):
|
|
1218
|
+
self._cache.clear()
|
|
1219
|
+
self._hits = 0
|
|
1220
|
+
self._misses = 0
|
|
1221
|
+
|
|
1222
|
+
def keys(self) -> List[str]:
|
|
1223
|
+
self.cleanup()
|
|
1224
|
+
return list(self._cache.keys())
|
|
1225
|
+
|
|
1226
|
+
def size(self) -> int:
|
|
1227
|
+
self.cleanup()
|
|
1228
|
+
return len(self._cache)
|
|
1229
|
+
|
|
1230
|
+
def stats(self) -> Dict:
|
|
1231
|
+
return {
|
|
1232
|
+
'size': len(self._cache),
|
|
1233
|
+
'hits': self._hits,
|
|
1234
|
+
'misses': self._misses,
|
|
1235
|
+
'hit_rate': self._hits / (self._hits + self._misses) if (self._hits + self._misses) > 0 else 0
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
def cleanup(self):
|
|
1239
|
+
expired = [k for k, v in self._cache.items() if self._is_expired(v)]
|
|
1240
|
+
for k in expired:
|
|
1241
|
+
del self._cache[k]
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
# =============================================================================
|
|
1245
|
+
# @Queue Module - Message queue and task scheduling
|
|
1246
|
+
# =============================================================================
|
|
1247
|
+
|
|
1248
|
+
class QueueModule(CSSLModuleBase):
|
|
1249
|
+
"""
|
|
1250
|
+
@Queue - Message queue and task scheduling
|
|
1251
|
+
|
|
1252
|
+
Methods:
|
|
1253
|
+
create(name) - Create a named queue
|
|
1254
|
+
push(name, item) - Push item to queue
|
|
1255
|
+
pop(name, timeout) - Pop item from queue (blocking)
|
|
1256
|
+
peek(name) - Peek at front item without removing
|
|
1257
|
+
size(name) - Get queue size
|
|
1258
|
+
empty(name) - Check if queue is empty
|
|
1259
|
+
clear(name) - Clear queue
|
|
1260
|
+
list() - List all queue names
|
|
1261
|
+
delete(name) - Delete a queue
|
|
1262
|
+
schedule(func, delay) - Schedule function execution
|
|
1263
|
+
interval(func, interval) - Repeat function at interval
|
|
1264
|
+
cancel(task_id) - Cancel scheduled task
|
|
1265
|
+
"""
|
|
1266
|
+
|
|
1267
|
+
def __init__(self, runtime=None):
|
|
1268
|
+
super().__init__(runtime)
|
|
1269
|
+
self._queues: Dict[str, queue.Queue] = {}
|
|
1270
|
+
self._tasks: Dict[str, threading.Timer] = {}
|
|
1271
|
+
self._task_counter = 0
|
|
1272
|
+
|
|
1273
|
+
def _register_methods(self):
|
|
1274
|
+
self._methods['create'] = self.create
|
|
1275
|
+
self._methods['push'] = self.push
|
|
1276
|
+
self._methods['pop'] = self.pop
|
|
1277
|
+
self._methods['peek'] = self.peek
|
|
1278
|
+
self._methods['size'] = self.size
|
|
1279
|
+
self._methods['empty'] = self.empty
|
|
1280
|
+
self._methods['clear'] = self.clear
|
|
1281
|
+
self._methods['list'] = self.list_queues
|
|
1282
|
+
self._methods['delete'] = self.delete
|
|
1283
|
+
self._methods['schedule'] = self.schedule
|
|
1284
|
+
self._methods['interval'] = self.interval
|
|
1285
|
+
self._methods['cancel'] = self.cancel
|
|
1286
|
+
|
|
1287
|
+
def create(self, name: str) -> bool:
|
|
1288
|
+
if name not in self._queues:
|
|
1289
|
+
self._queues[name] = queue.Queue()
|
|
1290
|
+
return True
|
|
1291
|
+
|
|
1292
|
+
def push(self, name: str, item: Any) -> bool:
|
|
1293
|
+
self.create(name)
|
|
1294
|
+
self._queues[name].put(item)
|
|
1295
|
+
return True
|
|
1296
|
+
|
|
1297
|
+
def pop(self, name: str, timeout: float = None) -> Any:
|
|
1298
|
+
if name not in self._queues:
|
|
1299
|
+
return None
|
|
1300
|
+
try:
|
|
1301
|
+
return self._queues[name].get(timeout=timeout)
|
|
1302
|
+
except queue.Empty:
|
|
1303
|
+
return None
|
|
1304
|
+
|
|
1305
|
+
def peek(self, name: str) -> Any:
|
|
1306
|
+
if name not in self._queues:
|
|
1307
|
+
return None
|
|
1308
|
+
q = self._queues[name]
|
|
1309
|
+
if q.empty():
|
|
1310
|
+
return None
|
|
1311
|
+
# Peek without removing
|
|
1312
|
+
with q.mutex:
|
|
1313
|
+
if q.queue:
|
|
1314
|
+
return q.queue[0]
|
|
1315
|
+
return None
|
|
1316
|
+
|
|
1317
|
+
def size(self, name: str) -> int:
|
|
1318
|
+
if name not in self._queues:
|
|
1319
|
+
return 0
|
|
1320
|
+
return self._queues[name].qsize()
|
|
1321
|
+
|
|
1322
|
+
def empty(self, name: str) -> bool:
|
|
1323
|
+
if name not in self._queues:
|
|
1324
|
+
return True
|
|
1325
|
+
return self._queues[name].empty()
|
|
1326
|
+
|
|
1327
|
+
def clear(self, name: str) -> bool:
|
|
1328
|
+
if name in self._queues:
|
|
1329
|
+
with self._queues[name].mutex:
|
|
1330
|
+
self._queues[name].queue.clear()
|
|
1331
|
+
return True
|
|
1332
|
+
|
|
1333
|
+
def list_queues(self) -> List[str]:
|
|
1334
|
+
return list(self._queues.keys())
|
|
1335
|
+
|
|
1336
|
+
def delete(self, name: str) -> bool:
|
|
1337
|
+
if name in self._queues:
|
|
1338
|
+
del self._queues[name]
|
|
1339
|
+
return True
|
|
1340
|
+
return False
|
|
1341
|
+
|
|
1342
|
+
def schedule(self, func: Callable, delay: float) -> str:
|
|
1343
|
+
self._task_counter += 1
|
|
1344
|
+
task_id = f"task_{self._task_counter}"
|
|
1345
|
+
timer = threading.Timer(delay, func)
|
|
1346
|
+
self._tasks[task_id] = timer
|
|
1347
|
+
timer.start()
|
|
1348
|
+
return task_id
|
|
1349
|
+
|
|
1350
|
+
def interval(self, func: Callable, interval_secs: float) -> str:
|
|
1351
|
+
self._task_counter += 1
|
|
1352
|
+
task_id = f"interval_{self._task_counter}"
|
|
1353
|
+
|
|
1354
|
+
def repeat():
|
|
1355
|
+
if task_id in self._tasks:
|
|
1356
|
+
func()
|
|
1357
|
+
timer = threading.Timer(interval_secs, repeat)
|
|
1358
|
+
self._tasks[task_id] = timer
|
|
1359
|
+
timer.start()
|
|
1360
|
+
|
|
1361
|
+
timer = threading.Timer(interval_secs, repeat)
|
|
1362
|
+
self._tasks[task_id] = timer
|
|
1363
|
+
timer.start()
|
|
1364
|
+
return task_id
|
|
1365
|
+
|
|
1366
|
+
def cancel(self, task_id: str) -> bool:
|
|
1367
|
+
if task_id in self._tasks:
|
|
1368
|
+
self._tasks[task_id].cancel()
|
|
1369
|
+
del self._tasks[task_id]
|
|
1370
|
+
return True
|
|
1371
|
+
return False
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
# =============================================================================
|
|
1375
|
+
# @Format Module - String formatting and templating
|
|
1376
|
+
# =============================================================================
|
|
1377
|
+
|
|
1378
|
+
class FormatModule(CSSLModuleBase):
|
|
1379
|
+
"""
|
|
1380
|
+
@Format - String formatting and templating
|
|
1381
|
+
|
|
1382
|
+
Methods:
|
|
1383
|
+
sprintf(fmt, args) - C-style sprintf formatting
|
|
1384
|
+
format(template, kwargs) - Python format string
|
|
1385
|
+
template(tmpl, vars) - Simple ${var} template substitution
|
|
1386
|
+
pad(str, width, char, align) - Pad string to width
|
|
1387
|
+
truncate(str, maxlen, ellipsis) - Truncate with ellipsis
|
|
1388
|
+
wrap(str, width) - Word wrap text
|
|
1389
|
+
indent(str, spaces) - Indent each line
|
|
1390
|
+
dedent(str) - Remove common leading whitespace
|
|
1391
|
+
upper(str) - Convert to uppercase
|
|
1392
|
+
lower(str) - Convert to lowercase
|
|
1393
|
+
title(str) - Convert to title case
|
|
1394
|
+
camel(str) - Convert to camelCase
|
|
1395
|
+
snake(str) - Convert to snake_case
|
|
1396
|
+
kebab(str) - Convert to kebab-case
|
|
1397
|
+
bytes(n, decimals) - Format bytes as human readable
|
|
1398
|
+
number(n, decimals, sep) - Format number with separators
|
|
1399
|
+
currency(n, symbol) - Format as currency
|
|
1400
|
+
percent(n, decimals) - Format as percentage
|
|
1401
|
+
"""
|
|
1402
|
+
|
|
1403
|
+
def _register_methods(self):
|
|
1404
|
+
self._methods['sprintf'] = self.sprintf
|
|
1405
|
+
self._methods['format'] = self.format_str
|
|
1406
|
+
self._methods['template'] = self.template
|
|
1407
|
+
self._methods['pad'] = self.pad
|
|
1408
|
+
self._methods['truncate'] = self.truncate
|
|
1409
|
+
self._methods['wrap'] = self.wrap
|
|
1410
|
+
self._methods['indent'] = self.indent
|
|
1411
|
+
self._methods['dedent'] = self.dedent
|
|
1412
|
+
self._methods['upper'] = str.upper
|
|
1413
|
+
self._methods['lower'] = str.lower
|
|
1414
|
+
self._methods['title'] = str.title
|
|
1415
|
+
self._methods['camel'] = self.camel
|
|
1416
|
+
self._methods['snake'] = self.snake
|
|
1417
|
+
self._methods['kebab'] = self.kebab
|
|
1418
|
+
self._methods['bytes'] = self.format_bytes
|
|
1419
|
+
self._methods['number'] = self.format_number
|
|
1420
|
+
self._methods['currency'] = self.currency
|
|
1421
|
+
self._methods['percent'] = self.percent
|
|
1422
|
+
|
|
1423
|
+
def sprintf(self, fmt: str, *args) -> str:
|
|
1424
|
+
return fmt % args
|
|
1425
|
+
|
|
1426
|
+
def format_str(self, template: str, **kwargs) -> str:
|
|
1427
|
+
return template.format(**kwargs)
|
|
1428
|
+
|
|
1429
|
+
def template(self, tmpl: str, vars: Dict) -> str:
|
|
1430
|
+
result = tmpl
|
|
1431
|
+
for key, value in vars.items():
|
|
1432
|
+
result = result.replace('${' + key + '}', str(value))
|
|
1433
|
+
return result
|
|
1434
|
+
|
|
1435
|
+
def pad(self, s: str, width: int, char: str = ' ', align: str = 'right') -> str:
|
|
1436
|
+
if align == 'left':
|
|
1437
|
+
return s.ljust(width, char)
|
|
1438
|
+
elif align == 'center':
|
|
1439
|
+
return s.center(width, char)
|
|
1440
|
+
return s.rjust(width, char)
|
|
1441
|
+
|
|
1442
|
+
def truncate(self, s: str, maxlen: int, ellipsis: str = '...') -> str:
|
|
1443
|
+
if len(s) <= maxlen:
|
|
1444
|
+
return s
|
|
1445
|
+
return s[:maxlen - len(ellipsis)] + ellipsis
|
|
1446
|
+
|
|
1447
|
+
def wrap(self, s: str, width: int = 80) -> str:
|
|
1448
|
+
import textwrap
|
|
1449
|
+
return '\n'.join(textwrap.wrap(s, width))
|
|
1450
|
+
|
|
1451
|
+
def indent(self, s: str, spaces: int = 4) -> str:
|
|
1452
|
+
prefix = ' ' * spaces
|
|
1453
|
+
return '\n'.join(prefix + line for line in s.split('\n'))
|
|
1454
|
+
|
|
1455
|
+
def dedent(self, s: str) -> str:
|
|
1456
|
+
import textwrap
|
|
1457
|
+
return textwrap.dedent(s)
|
|
1458
|
+
|
|
1459
|
+
def camel(self, s: str) -> str:
|
|
1460
|
+
words = re.split(r'[-_\s]+', s)
|
|
1461
|
+
return words[0].lower() + ''.join(w.capitalize() for w in words[1:])
|
|
1462
|
+
|
|
1463
|
+
def snake(self, s: str) -> str:
|
|
1464
|
+
s = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', s)
|
|
1465
|
+
s = re.sub(r'([a-z\d])([A-Z])', r'\1_\2', s)
|
|
1466
|
+
return re.sub(r'[-\s]+', '_', s).lower()
|
|
1467
|
+
|
|
1468
|
+
def kebab(self, s: str) -> str:
|
|
1469
|
+
return self.snake(s).replace('_', '-')
|
|
1470
|
+
|
|
1471
|
+
def format_bytes(self, n: int, decimals: int = 2) -> str:
|
|
1472
|
+
for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']:
|
|
1473
|
+
if abs(n) < 1024:
|
|
1474
|
+
return f"{n:.{decimals}f} {unit}"
|
|
1475
|
+
n /= 1024
|
|
1476
|
+
return f"{n:.{decimals}f} EB"
|
|
1477
|
+
|
|
1478
|
+
def format_number(self, n: float, decimals: int = 2, sep: str = ',') -> str:
|
|
1479
|
+
parts = f"{n:.{decimals}f}".split('.')
|
|
1480
|
+
parts[0] = re.sub(r'(\d)(?=(\d{3})+$)', r'\1' + sep, parts[0])
|
|
1481
|
+
return '.'.join(parts)
|
|
1482
|
+
|
|
1483
|
+
def currency(self, n: float, symbol: str = '$') -> str:
|
|
1484
|
+
return f"{symbol}{self.format_number(abs(n), 2)}"
|
|
1485
|
+
|
|
1486
|
+
def percent(self, n: float, decimals: int = 1) -> str:
|
|
1487
|
+
return f"{n * 100:.{decimals}f}%"
|
|
1488
|
+
|
|
1489
|
+
|
|
1490
|
+
# =============================================================================
|
|
1491
|
+
# @Console Module - Terminal/Console operations
|
|
1492
|
+
# =============================================================================
|
|
1493
|
+
|
|
1494
|
+
class ConsoleModule(CSSLModuleBase):
|
|
1495
|
+
"""
|
|
1496
|
+
@Console - Terminal/Console operations
|
|
1497
|
+
|
|
1498
|
+
Methods:
|
|
1499
|
+
clear() - Clear console screen
|
|
1500
|
+
print(text, color) - Print with optional color
|
|
1501
|
+
println(text, color) - Print line with optional color
|
|
1502
|
+
table(data, headers) - Print formatted table
|
|
1503
|
+
progress(current, total, width) - Show progress bar
|
|
1504
|
+
spinner(message) - Show loading spinner
|
|
1505
|
+
prompt(message, default) - Prompt for input
|
|
1506
|
+
confirm(message) - Yes/No confirmation
|
|
1507
|
+
select(message, options) - Select from options menu
|
|
1508
|
+
color(text, color) - Colorize text
|
|
1509
|
+
bold(text) - Bold text
|
|
1510
|
+
dim(text) - Dim text
|
|
1511
|
+
underline(text) - Underlined text
|
|
1512
|
+
cursor_hide() - Hide cursor
|
|
1513
|
+
cursor_show() - Show cursor
|
|
1514
|
+
cursor_move(x, y) - Move cursor to position
|
|
1515
|
+
beep() - Terminal beep
|
|
1516
|
+
"""
|
|
1517
|
+
|
|
1518
|
+
COLORS = {
|
|
1519
|
+
'black': '30', 'red': '31', 'green': '32', 'yellow': '33',
|
|
1520
|
+
'blue': '34', 'magenta': '35', 'cyan': '36', 'white': '37',
|
|
1521
|
+
'bright_black': '90', 'bright_red': '91', 'bright_green': '92',
|
|
1522
|
+
'bright_yellow': '93', 'bright_blue': '94', 'bright_magenta': '95',
|
|
1523
|
+
'bright_cyan': '96', 'bright_white': '97',
|
|
1524
|
+
'reset': '0', 'bold': '1', 'dim': '2', 'italic': '3',
|
|
1525
|
+
'underline': '4', 'blink': '5', 'reverse': '7'
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
def __init__(self, runtime=None):
|
|
1529
|
+
super().__init__(runtime)
|
|
1530
|
+
self._spinner_running = False
|
|
1531
|
+
self._spinner_thread = None
|
|
1532
|
+
|
|
1533
|
+
def _register_methods(self):
|
|
1534
|
+
self._methods['clear'] = self.clear
|
|
1535
|
+
self._methods['print'] = self.print_styled
|
|
1536
|
+
self._methods['println'] = self.println_styled
|
|
1537
|
+
self._methods['table'] = self.print_table
|
|
1538
|
+
self._methods['progress'] = self.show_progress
|
|
1539
|
+
self._methods['spinner'] = self.show_spinner
|
|
1540
|
+
self._methods['spinner_stop'] = self.stop_spinner
|
|
1541
|
+
self._methods['prompt'] = self.prompt
|
|
1542
|
+
self._methods['confirm'] = self.confirm
|
|
1543
|
+
self._methods['select'] = self.select_menu
|
|
1544
|
+
self._methods['color'] = self.colorize
|
|
1545
|
+
self._methods['bold'] = self.bold
|
|
1546
|
+
self._methods['dim'] = self.dim
|
|
1547
|
+
self._methods['underline'] = self.underline_text
|
|
1548
|
+
self._methods['cursor_hide'] = self.cursor_hide
|
|
1549
|
+
self._methods['cursor_show'] = self.cursor_show
|
|
1550
|
+
self._methods['cursor_move'] = self.cursor_move
|
|
1551
|
+
self._methods['beep'] = self.beep
|
|
1552
|
+
|
|
1553
|
+
def clear(self):
|
|
1554
|
+
"""Clear the console screen."""
|
|
1555
|
+
if os.name == 'nt':
|
|
1556
|
+
os.system('cls')
|
|
1557
|
+
else:
|
|
1558
|
+
print('\033[2J\033[H', end='')
|
|
1559
|
+
|
|
1560
|
+
def print_styled(self, text: str, color: str = None) -> None:
|
|
1561
|
+
"""Print text with optional color."""
|
|
1562
|
+
if color:
|
|
1563
|
+
text = self.colorize(text, color)
|
|
1564
|
+
print(text, end='')
|
|
1565
|
+
|
|
1566
|
+
def println_styled(self, text: str, color: str = None) -> None:
|
|
1567
|
+
"""Print line with optional color."""
|
|
1568
|
+
if color:
|
|
1569
|
+
text = self.colorize(text, color)
|
|
1570
|
+
print(text)
|
|
1571
|
+
|
|
1572
|
+
def print_table(self, data: List[Dict], headers: List[str] = None) -> None:
|
|
1573
|
+
"""Print a formatted table."""
|
|
1574
|
+
if not data:
|
|
1575
|
+
print("(no data)")
|
|
1576
|
+
return
|
|
1577
|
+
|
|
1578
|
+
# Auto-detect headers from first row if not provided
|
|
1579
|
+
if headers is None:
|
|
1580
|
+
if isinstance(data[0], dict):
|
|
1581
|
+
headers = list(data[0].keys())
|
|
1582
|
+
else:
|
|
1583
|
+
headers = [f"Col {i+1}" for i in range(len(data[0]))]
|
|
1584
|
+
|
|
1585
|
+
# Calculate column widths
|
|
1586
|
+
widths = [len(h) for h in headers]
|
|
1587
|
+
for row in data:
|
|
1588
|
+
if isinstance(row, dict):
|
|
1589
|
+
values = [str(row.get(h, '')) for h in headers]
|
|
1590
|
+
else:
|
|
1591
|
+
values = [str(v) for v in row]
|
|
1592
|
+
for i, v in enumerate(values):
|
|
1593
|
+
if i < len(widths):
|
|
1594
|
+
widths[i] = max(widths[i], len(v))
|
|
1595
|
+
|
|
1596
|
+
# Print header
|
|
1597
|
+
sep = '+-' + '-+-'.join('-' * w for w in widths) + '-+'
|
|
1598
|
+
print(sep)
|
|
1599
|
+
header_line = '| ' + ' | '.join(h.ljust(widths[i]) for i, h in enumerate(headers)) + ' |'
|
|
1600
|
+
print(header_line)
|
|
1601
|
+
print(sep)
|
|
1602
|
+
|
|
1603
|
+
# Print rows
|
|
1604
|
+
for row in data:
|
|
1605
|
+
if isinstance(row, dict):
|
|
1606
|
+
values = [str(row.get(h, '')) for h in headers]
|
|
1607
|
+
else:
|
|
1608
|
+
values = [str(v) for v in row]
|
|
1609
|
+
row_line = '| ' + ' | '.join(v.ljust(widths[i]) for i, v in enumerate(values) if i < len(widths)) + ' |'
|
|
1610
|
+
print(row_line)
|
|
1611
|
+
|
|
1612
|
+
print(sep)
|
|
1613
|
+
|
|
1614
|
+
def show_progress(self, current: int, total: int, width: int = 40, show_text: bool = True) -> None:
|
|
1615
|
+
"""Show a progress bar."""
|
|
1616
|
+
if total <= 0:
|
|
1617
|
+
return
|
|
1618
|
+
percent = min(100, current * 100 // total)
|
|
1619
|
+
filled = int(width * current // total)
|
|
1620
|
+
bar = '█' * filled + '░' * (width - filled)
|
|
1621
|
+
if show_text:
|
|
1622
|
+
print(f'\r[{bar}] {percent}% ({current}/{total})', end='', flush=True)
|
|
1623
|
+
else:
|
|
1624
|
+
print(f'\r[{bar}]', end='', flush=True)
|
|
1625
|
+
if current >= total:
|
|
1626
|
+
print()
|
|
1627
|
+
|
|
1628
|
+
def show_spinner(self, message: str = "Loading") -> None:
|
|
1629
|
+
"""Show a loading spinner."""
|
|
1630
|
+
self._spinner_running = True
|
|
1631
|
+
spinners = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
1632
|
+
|
|
1633
|
+
def spin():
|
|
1634
|
+
i = 0
|
|
1635
|
+
while self._spinner_running:
|
|
1636
|
+
print(f'\r{spinners[i % len(spinners)]} {message}...', end='', flush=True)
|
|
1637
|
+
time.sleep(0.1)
|
|
1638
|
+
i += 1
|
|
1639
|
+
print('\r' + ' ' * (len(message) + 10) + '\r', end='')
|
|
1640
|
+
|
|
1641
|
+
self._spinner_thread = threading.Thread(target=spin, daemon=True)
|
|
1642
|
+
self._spinner_thread.start()
|
|
1643
|
+
|
|
1644
|
+
def stop_spinner(self) -> None:
|
|
1645
|
+
"""Stop the loading spinner."""
|
|
1646
|
+
self._spinner_running = False
|
|
1647
|
+
if self._spinner_thread:
|
|
1648
|
+
self._spinner_thread.join(timeout=1)
|
|
1649
|
+
self._spinner_thread = None
|
|
1650
|
+
|
|
1651
|
+
def prompt(self, message: str, default: str = '') -> str:
|
|
1652
|
+
"""Prompt for user input."""
|
|
1653
|
+
if default:
|
|
1654
|
+
result = input(f"{message} [{default}]: ")
|
|
1655
|
+
return result if result else default
|
|
1656
|
+
return input(f"{message}: ")
|
|
1657
|
+
|
|
1658
|
+
def confirm(self, message: str) -> bool:
|
|
1659
|
+
"""Yes/No confirmation prompt."""
|
|
1660
|
+
result = input(f"{message} (y/n): ").lower().strip()
|
|
1661
|
+
return result in ('y', 'yes', 'ja', 'j', '1', 'true')
|
|
1662
|
+
|
|
1663
|
+
def select_menu(self, message: str, options: List[str]) -> int:
|
|
1664
|
+
"""Select from options menu. Returns index of selected option."""
|
|
1665
|
+
print(f"\n{message}")
|
|
1666
|
+
for i, opt in enumerate(options):
|
|
1667
|
+
print(f" {i + 1}. {opt}")
|
|
1668
|
+
while True:
|
|
1669
|
+
try:
|
|
1670
|
+
choice = int(input("\nWahl: "))
|
|
1671
|
+
if 1 <= choice <= len(options):
|
|
1672
|
+
return choice - 1
|
|
1673
|
+
except ValueError:
|
|
1674
|
+
pass
|
|
1675
|
+
print("Ungültige Auswahl, bitte erneut versuchen.")
|
|
1676
|
+
|
|
1677
|
+
def colorize(self, text: str, color: str) -> str:
|
|
1678
|
+
"""Apply ANSI color to text."""
|
|
1679
|
+
code = self.COLORS.get(color.lower(), color)
|
|
1680
|
+
return f'\033[{code}m{text}\033[0m'
|
|
1681
|
+
|
|
1682
|
+
def bold(self, text: str) -> str:
|
|
1683
|
+
"""Make text bold."""
|
|
1684
|
+
return f'\033[1m{text}\033[0m'
|
|
1685
|
+
|
|
1686
|
+
def dim(self, text: str) -> str:
|
|
1687
|
+
"""Make text dim."""
|
|
1688
|
+
return f'\033[2m{text}\033[0m'
|
|
1689
|
+
|
|
1690
|
+
def underline_text(self, text: str) -> str:
|
|
1691
|
+
"""Underline text."""
|
|
1692
|
+
return f'\033[4m{text}\033[0m'
|
|
1693
|
+
|
|
1694
|
+
def cursor_hide(self) -> None:
|
|
1695
|
+
"""Hide cursor."""
|
|
1696
|
+
print('\033[?25l', end='')
|
|
1697
|
+
|
|
1698
|
+
def cursor_show(self) -> None:
|
|
1699
|
+
"""Show cursor."""
|
|
1700
|
+
print('\033[?25h', end='')
|
|
1701
|
+
|
|
1702
|
+
def cursor_move(self, x: int, y: int) -> None:
|
|
1703
|
+
"""Move cursor to position."""
|
|
1704
|
+
print(f'\033[{y};{x}H', end='')
|
|
1705
|
+
|
|
1706
|
+
def beep(self) -> None:
|
|
1707
|
+
"""Terminal beep."""
|
|
1708
|
+
print('\a', end='')
|
|
1709
|
+
|
|
1710
|
+
|
|
1711
|
+
# =============================================================================
|
|
1712
|
+
# @Process Module - Process and subprocess management
|
|
1713
|
+
# =============================================================================
|
|
1714
|
+
|
|
1715
|
+
class ProcessModule(CSSLModuleBase):
|
|
1716
|
+
"""
|
|
1717
|
+
@Process - Process and subprocess management
|
|
1718
|
+
|
|
1719
|
+
Methods:
|
|
1720
|
+
run(cmd, timeout, cwd) - Run command and wait for result
|
|
1721
|
+
spawn(cmd, cwd) - Spawn background process
|
|
1722
|
+
kill(pid) - Kill process by PID
|
|
1723
|
+
list() - List running processes
|
|
1724
|
+
pid() - Get current process ID
|
|
1725
|
+
ppid() - Get parent process ID
|
|
1726
|
+
exists(pid) - Check if process exists
|
|
1727
|
+
wait(pid, timeout) - Wait for process to finish
|
|
1728
|
+
shell(cmd) - Run in system shell
|
|
1729
|
+
popen(cmd, cwd) - Open process for streaming
|
|
1730
|
+
read_stdout(handle) - Read from process stdout
|
|
1731
|
+
write_stdin(handle, data) - Write to process stdin
|
|
1732
|
+
close(handle) - Close process handle
|
|
1733
|
+
"""
|
|
1734
|
+
|
|
1735
|
+
def __init__(self, runtime=None):
|
|
1736
|
+
super().__init__(runtime)
|
|
1737
|
+
self._handles: Dict[int, Any] = {}
|
|
1738
|
+
self._handle_counter = 0
|
|
1739
|
+
|
|
1740
|
+
def _register_methods(self):
|
|
1741
|
+
self._methods['run'] = self.run_command
|
|
1742
|
+
self._methods['spawn'] = self.spawn_process
|
|
1743
|
+
self._methods['kill'] = self.kill_process
|
|
1744
|
+
self._methods['list'] = self.list_processes
|
|
1745
|
+
self._methods['pid'] = lambda: os.getpid()
|
|
1746
|
+
self._methods['ppid'] = lambda: os.getppid()
|
|
1747
|
+
self._methods['exists'] = self.process_exists
|
|
1748
|
+
self._methods['wait'] = self.wait_for_process
|
|
1749
|
+
self._methods['shell'] = self.shell_command
|
|
1750
|
+
self._methods['popen'] = self.popen_process
|
|
1751
|
+
self._methods['read_stdout'] = self.read_stdout
|
|
1752
|
+
self._methods['write_stdin'] = self.write_stdin
|
|
1753
|
+
self._methods['close'] = self.close_handle
|
|
1754
|
+
|
|
1755
|
+
def run_command(self, cmd: Union[str, List[str]], timeout: float = 300, cwd: str = None) -> Dict:
|
|
1756
|
+
"""Run command and wait for result."""
|
|
1757
|
+
import subprocess
|
|
1758
|
+
try:
|
|
1759
|
+
result = subprocess.run(
|
|
1760
|
+
cmd,
|
|
1761
|
+
shell=isinstance(cmd, str),
|
|
1762
|
+
capture_output=True,
|
|
1763
|
+
text=True,
|
|
1764
|
+
timeout=timeout,
|
|
1765
|
+
cwd=cwd
|
|
1766
|
+
)
|
|
1767
|
+
return {
|
|
1768
|
+
'returncode': result.returncode,
|
|
1769
|
+
'stdout': result.stdout,
|
|
1770
|
+
'stderr': result.stderr,
|
|
1771
|
+
'success': result.returncode == 0
|
|
1772
|
+
}
|
|
1773
|
+
except subprocess.TimeoutExpired:
|
|
1774
|
+
return {'returncode': -1, 'stdout': '', 'stderr': 'Command timed out', 'success': False}
|
|
1775
|
+
except Exception as e:
|
|
1776
|
+
return {'returncode': -1, 'stdout': '', 'stderr': str(e), 'success': False}
|
|
1777
|
+
|
|
1778
|
+
def spawn_process(self, cmd: Union[str, List[str]], cwd: str = None) -> int:
|
|
1779
|
+
"""Spawn background process, returns PID."""
|
|
1780
|
+
import subprocess
|
|
1781
|
+
proc = subprocess.Popen(
|
|
1782
|
+
cmd,
|
|
1783
|
+
shell=isinstance(cmd, str),
|
|
1784
|
+
cwd=cwd,
|
|
1785
|
+
stdout=subprocess.DEVNULL,
|
|
1786
|
+
stderr=subprocess.DEVNULL
|
|
1787
|
+
)
|
|
1788
|
+
return proc.pid
|
|
1789
|
+
|
|
1790
|
+
def kill_process(self, pid: int) -> bool:
|
|
1791
|
+
"""Kill process by PID."""
|
|
1792
|
+
try:
|
|
1793
|
+
os.kill(pid, 9) # SIGKILL
|
|
1794
|
+
return True
|
|
1795
|
+
except OSError:
|
|
1796
|
+
return False
|
|
1797
|
+
|
|
1798
|
+
def list_processes(self) -> List[Dict]:
|
|
1799
|
+
"""List running processes."""
|
|
1800
|
+
try:
|
|
1801
|
+
import psutil
|
|
1802
|
+
processes = []
|
|
1803
|
+
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
|
|
1804
|
+
try:
|
|
1805
|
+
info = proc.info
|
|
1806
|
+
processes.append({
|
|
1807
|
+
'pid': info['pid'],
|
|
1808
|
+
'name': info['name'],
|
|
1809
|
+
'cpu': info['cpu_percent'],
|
|
1810
|
+
'memory': info['memory_percent']
|
|
1811
|
+
})
|
|
1812
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
1813
|
+
pass
|
|
1814
|
+
return processes
|
|
1815
|
+
except ImportError:
|
|
1816
|
+
# Fallback without psutil
|
|
1817
|
+
import subprocess
|
|
1818
|
+
if os.name == 'nt':
|
|
1819
|
+
result = subprocess.run(['tasklist', '/fo', 'csv'], capture_output=True, text=True)
|
|
1820
|
+
lines = result.stdout.strip().split('\n')[1:]
|
|
1821
|
+
processes = []
|
|
1822
|
+
for line in lines:
|
|
1823
|
+
parts = line.strip('"').split('","')
|
|
1824
|
+
if len(parts) >= 2:
|
|
1825
|
+
processes.append({'name': parts[0], 'pid': int(parts[1])})
|
|
1826
|
+
return processes
|
|
1827
|
+
else:
|
|
1828
|
+
result = subprocess.run(['ps', 'aux'], capture_output=True, text=True)
|
|
1829
|
+
lines = result.stdout.strip().split('\n')[1:]
|
|
1830
|
+
processes = []
|
|
1831
|
+
for line in lines:
|
|
1832
|
+
parts = line.split()
|
|
1833
|
+
if len(parts) >= 11:
|
|
1834
|
+
processes.append({
|
|
1835
|
+
'user': parts[0],
|
|
1836
|
+
'pid': int(parts[1]),
|
|
1837
|
+
'cpu': float(parts[2]),
|
|
1838
|
+
'memory': float(parts[3]),
|
|
1839
|
+
'name': parts[10]
|
|
1840
|
+
})
|
|
1841
|
+
return processes
|
|
1842
|
+
|
|
1843
|
+
def process_exists(self, pid: int) -> bool:
|
|
1844
|
+
"""Check if process exists."""
|
|
1845
|
+
try:
|
|
1846
|
+
os.kill(pid, 0)
|
|
1847
|
+
return True
|
|
1848
|
+
except OSError:
|
|
1849
|
+
return False
|
|
1850
|
+
|
|
1851
|
+
def wait_for_process(self, pid: int, timeout: float = None) -> int:
|
|
1852
|
+
"""Wait for process to finish, returns exit code."""
|
|
1853
|
+
import subprocess
|
|
1854
|
+
try:
|
|
1855
|
+
if os.name == 'nt':
|
|
1856
|
+
# Windows
|
|
1857
|
+
import subprocess
|
|
1858
|
+
result = subprocess.run(['tasklist', '/fi', f'pid eq {pid}'], capture_output=True, text=True)
|
|
1859
|
+
start = time.time()
|
|
1860
|
+
while str(pid) in result.stdout:
|
|
1861
|
+
if timeout and (time.time() - start) > timeout:
|
|
1862
|
+
return -1
|
|
1863
|
+
time.sleep(0.5)
|
|
1864
|
+
result = subprocess.run(['tasklist', '/fi', f'pid eq {pid}'], capture_output=True, text=True)
|
|
1865
|
+
return 0
|
|
1866
|
+
else:
|
|
1867
|
+
# Unix
|
|
1868
|
+
os.waitpid(pid, 0)
|
|
1869
|
+
return 0
|
|
1870
|
+
except Exception:
|
|
1871
|
+
return -1
|
|
1872
|
+
|
|
1873
|
+
def shell_command(self, cmd: str) -> int:
|
|
1874
|
+
"""Run command in system shell, returns exit code."""
|
|
1875
|
+
return os.system(cmd)
|
|
1876
|
+
|
|
1877
|
+
def popen_process(self, cmd: Union[str, List[str]], cwd: str = None) -> int:
|
|
1878
|
+
"""Open process for streaming, returns handle ID."""
|
|
1879
|
+
import subprocess
|
|
1880
|
+
proc = subprocess.Popen(
|
|
1881
|
+
cmd,
|
|
1882
|
+
shell=isinstance(cmd, str),
|
|
1883
|
+
cwd=cwd,
|
|
1884
|
+
stdout=subprocess.PIPE,
|
|
1885
|
+
stderr=subprocess.PIPE,
|
|
1886
|
+
stdin=subprocess.PIPE,
|
|
1887
|
+
text=True
|
|
1888
|
+
)
|
|
1889
|
+
self._handle_counter += 1
|
|
1890
|
+
self._handles[self._handle_counter] = proc
|
|
1891
|
+
return self._handle_counter
|
|
1892
|
+
|
|
1893
|
+
def read_stdout(self, handle: int, timeout: float = None) -> Optional[str]:
|
|
1894
|
+
"""Read from process stdout."""
|
|
1895
|
+
proc = self._handles.get(handle)
|
|
1896
|
+
if not proc:
|
|
1897
|
+
return None
|
|
1898
|
+
try:
|
|
1899
|
+
import select
|
|
1900
|
+
if hasattr(select, 'select'):
|
|
1901
|
+
# Unix
|
|
1902
|
+
ready, _, _ = select.select([proc.stdout], [], [], timeout or 0)
|
|
1903
|
+
if ready:
|
|
1904
|
+
return proc.stdout.readline()
|
|
1905
|
+
return proc.stdout.readline()
|
|
1906
|
+
except Exception:
|
|
1907
|
+
return None
|
|
1908
|
+
|
|
1909
|
+
def write_stdin(self, handle: int, data: str) -> bool:
|
|
1910
|
+
"""Write to process stdin."""
|
|
1911
|
+
proc = self._handles.get(handle)
|
|
1912
|
+
if not proc:
|
|
1913
|
+
return False
|
|
1914
|
+
try:
|
|
1915
|
+
proc.stdin.write(data)
|
|
1916
|
+
proc.stdin.flush()
|
|
1917
|
+
return True
|
|
1918
|
+
except Exception:
|
|
1919
|
+
return False
|
|
1920
|
+
|
|
1921
|
+
def close_handle(self, handle: int) -> bool:
|
|
1922
|
+
"""Close process handle."""
|
|
1923
|
+
proc = self._handles.get(handle)
|
|
1924
|
+
if not proc:
|
|
1925
|
+
return False
|
|
1926
|
+
try:
|
|
1927
|
+
proc.terminate()
|
|
1928
|
+
del self._handles[handle]
|
|
1929
|
+
return True
|
|
1930
|
+
except Exception:
|
|
1931
|
+
return False
|
|
1932
|
+
|
|
1933
|
+
|
|
1934
|
+
# =============================================================================
|
|
1935
|
+
# @Config Module - Configuration file management
|
|
1936
|
+
# =============================================================================
|
|
1937
|
+
|
|
1938
|
+
class ConfigModule(CSSLModuleBase):
|
|
1939
|
+
"""
|
|
1940
|
+
@Config - Configuration file management (JSON, INI, ENV)
|
|
1941
|
+
|
|
1942
|
+
Methods:
|
|
1943
|
+
load(path) - Load config file (auto-detect format)
|
|
1944
|
+
save(path, data) - Save config file
|
|
1945
|
+
loadJSON(path) - Load JSON config
|
|
1946
|
+
saveJSON(path, data) - Save JSON config
|
|
1947
|
+
loadINI(path) - Load INI config
|
|
1948
|
+
saveINI(path, data) - Save INI config
|
|
1949
|
+
loadENV(path) - Load .env file
|
|
1950
|
+
saveENV(path, data) - Save .env file
|
|
1951
|
+
get(key, default) - Get config value by key path
|
|
1952
|
+
set(key, value) - Set config value by key path
|
|
1953
|
+
has(key) - Check if key exists
|
|
1954
|
+
delete(key) - Delete config key
|
|
1955
|
+
merge(config) - Merge config into current
|
|
1956
|
+
reload() - Reload from file
|
|
1957
|
+
env(name, default) - Get environment variable
|
|
1958
|
+
setenv(name, value) - Set environment variable
|
|
1959
|
+
"""
|
|
1960
|
+
|
|
1961
|
+
def __init__(self, runtime=None):
|
|
1962
|
+
super().__init__(runtime)
|
|
1963
|
+
self._config: Dict[str, Any] = {}
|
|
1964
|
+
self._file_path: Optional[str] = None
|
|
1965
|
+
|
|
1966
|
+
def _register_methods(self):
|
|
1967
|
+
self._methods['load'] = self.load
|
|
1968
|
+
self._methods['save'] = self.save
|
|
1969
|
+
self._methods['loadJSON'] = self.load_json
|
|
1970
|
+
self._methods['saveJSON'] = self.save_json
|
|
1971
|
+
self._methods['loadINI'] = self.load_ini
|
|
1972
|
+
self._methods['saveINI'] = self.save_ini
|
|
1973
|
+
self._methods['loadENV'] = self.load_env
|
|
1974
|
+
self._methods['saveENV'] = self.save_env
|
|
1975
|
+
self._methods['get'] = self.get_value
|
|
1976
|
+
self._methods['set'] = self.set_value
|
|
1977
|
+
self._methods['has'] = self.has_key
|
|
1978
|
+
self._methods['delete'] = self.delete_key
|
|
1979
|
+
self._methods['merge'] = self.merge_config
|
|
1980
|
+
self._methods['reload'] = self.reload
|
|
1981
|
+
self._methods['env'] = self.get_env
|
|
1982
|
+
self._methods['setenv'] = self.set_env
|
|
1983
|
+
self._methods['all'] = lambda: dict(self._config)
|
|
1984
|
+
|
|
1985
|
+
def load(self, path: str) -> Dict:
|
|
1986
|
+
"""Load config file, auto-detecting format."""
|
|
1987
|
+
self._file_path = path
|
|
1988
|
+
ext = os.path.splitext(path)[1].lower()
|
|
1989
|
+
|
|
1990
|
+
if ext == '.json':
|
|
1991
|
+
return self.load_json(path)
|
|
1992
|
+
elif ext in ('.ini', '.cfg', '.conf'):
|
|
1993
|
+
return self.load_ini(path)
|
|
1994
|
+
elif ext == '.env' or os.path.basename(path).startswith('.env'):
|
|
1995
|
+
return self.load_env(path)
|
|
1996
|
+
else:
|
|
1997
|
+
# Try JSON first, then INI
|
|
1998
|
+
try:
|
|
1999
|
+
return self.load_json(path)
|
|
2000
|
+
except:
|
|
2001
|
+
try:
|
|
2002
|
+
return self.load_ini(path)
|
|
2003
|
+
except:
|
|
2004
|
+
return {}
|
|
2005
|
+
|
|
2006
|
+
def save(self, path: str = None, data: Dict = None) -> bool:
|
|
2007
|
+
"""Save config file."""
|
|
2008
|
+
path = path or self._file_path
|
|
2009
|
+
if not path:
|
|
2010
|
+
return False
|
|
2011
|
+
|
|
2012
|
+
data = data if data is not None else self._config
|
|
2013
|
+
ext = os.path.splitext(path)[1].lower()
|
|
2014
|
+
|
|
2015
|
+
if ext == '.json':
|
|
2016
|
+
return self.save_json(path, data)
|
|
2017
|
+
elif ext in ('.ini', '.cfg', '.conf'):
|
|
2018
|
+
return self.save_ini(path, data)
|
|
2019
|
+
elif ext == '.env' or os.path.basename(path).startswith('.env'):
|
|
2020
|
+
return self.save_env(path, data)
|
|
2021
|
+
else:
|
|
2022
|
+
return self.save_json(path, data)
|
|
2023
|
+
|
|
2024
|
+
def load_json(self, path: str) -> Dict:
|
|
2025
|
+
"""Load JSON config."""
|
|
2026
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
2027
|
+
self._config = json.load(f)
|
|
2028
|
+
self._file_path = path
|
|
2029
|
+
return self._config
|
|
2030
|
+
|
|
2031
|
+
def save_json(self, path: str, data: Dict = None) -> bool:
|
|
2032
|
+
"""Save JSON config."""
|
|
2033
|
+
data = data if data is not None else self._config
|
|
2034
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
2035
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
2036
|
+
return True
|
|
2037
|
+
|
|
2038
|
+
def load_ini(self, path: str) -> Dict:
|
|
2039
|
+
"""Load INI config."""
|
|
2040
|
+
import configparser
|
|
2041
|
+
parser = configparser.ConfigParser()
|
|
2042
|
+
parser.read(path, encoding='utf-8')
|
|
2043
|
+
|
|
2044
|
+
config = {}
|
|
2045
|
+
for section in parser.sections():
|
|
2046
|
+
config[section] = dict(parser.items(section))
|
|
2047
|
+
|
|
2048
|
+
# Handle DEFAULT section
|
|
2049
|
+
if parser.defaults():
|
|
2050
|
+
config['DEFAULT'] = dict(parser.defaults())
|
|
2051
|
+
|
|
2052
|
+
self._config = config
|
|
2053
|
+
self._file_path = path
|
|
2054
|
+
return config
|
|
2055
|
+
|
|
2056
|
+
def save_ini(self, path: str, data: Dict = None) -> bool:
|
|
2057
|
+
"""Save INI config."""
|
|
2058
|
+
import configparser
|
|
2059
|
+
data = data if data is not None else self._config
|
|
2060
|
+
parser = configparser.ConfigParser()
|
|
2061
|
+
|
|
2062
|
+
for section, values in data.items():
|
|
2063
|
+
if section.upper() == 'DEFAULT':
|
|
2064
|
+
for key, value in values.items():
|
|
2065
|
+
parser['DEFAULT'][key] = str(value)
|
|
2066
|
+
else:
|
|
2067
|
+
parser[section] = {k: str(v) for k, v in values.items()}
|
|
2068
|
+
|
|
2069
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
2070
|
+
parser.write(f)
|
|
2071
|
+
return True
|
|
2072
|
+
|
|
2073
|
+
def load_env(self, path: str) -> Dict:
|
|
2074
|
+
"""Load .env file."""
|
|
2075
|
+
config = {}
|
|
2076
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
2077
|
+
for line in f:
|
|
2078
|
+
line = line.strip()
|
|
2079
|
+
if line and not line.startswith('#') and '=' in line:
|
|
2080
|
+
key, value = line.split('=', 1)
|
|
2081
|
+
key = key.strip()
|
|
2082
|
+
value = value.strip().strip('"').strip("'")
|
|
2083
|
+
config[key] = value
|
|
2084
|
+
# Also set in environment
|
|
2085
|
+
os.environ[key] = value
|
|
2086
|
+
|
|
2087
|
+
self._config = config
|
|
2088
|
+
self._file_path = path
|
|
2089
|
+
return config
|
|
2090
|
+
|
|
2091
|
+
def save_env(self, path: str, data: Dict = None) -> bool:
|
|
2092
|
+
"""Save .env file."""
|
|
2093
|
+
data = data if data is not None else self._config
|
|
2094
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
2095
|
+
for key, value in data.items():
|
|
2096
|
+
# Quote values with spaces
|
|
2097
|
+
if ' ' in str(value):
|
|
2098
|
+
value = f'"{value}"'
|
|
2099
|
+
f.write(f"{key}={value}\n")
|
|
2100
|
+
return True
|
|
2101
|
+
|
|
2102
|
+
def get_value(self, key: str, default: Any = None) -> Any:
|
|
2103
|
+
"""Get config value by key path (e.g., 'section.key')."""
|
|
2104
|
+
keys = key.split('.')
|
|
2105
|
+
current = self._config
|
|
2106
|
+
for k in keys:
|
|
2107
|
+
if isinstance(current, dict) and k in current:
|
|
2108
|
+
current = current[k]
|
|
2109
|
+
else:
|
|
2110
|
+
return default
|
|
2111
|
+
return current
|
|
2112
|
+
|
|
2113
|
+
def set_value(self, key: str, value: Any) -> bool:
|
|
2114
|
+
"""Set config value by key path."""
|
|
2115
|
+
keys = key.split('.')
|
|
2116
|
+
current = self._config
|
|
2117
|
+
for k in keys[:-1]:
|
|
2118
|
+
if k not in current:
|
|
2119
|
+
current[k] = {}
|
|
2120
|
+
current = current[k]
|
|
2121
|
+
current[keys[-1]] = value
|
|
2122
|
+
return True
|
|
2123
|
+
|
|
2124
|
+
def has_key(self, key: str) -> bool:
|
|
2125
|
+
"""Check if key exists."""
|
|
2126
|
+
return self.get_value(key, _MISSING) is not _MISSING
|
|
2127
|
+
|
|
2128
|
+
def delete_key(self, key: str) -> bool:
|
|
2129
|
+
"""Delete config key."""
|
|
2130
|
+
keys = key.split('.')
|
|
2131
|
+
current = self._config
|
|
2132
|
+
for k in keys[:-1]:
|
|
2133
|
+
if k not in current:
|
|
2134
|
+
return False
|
|
2135
|
+
current = current[k]
|
|
2136
|
+
if keys[-1] in current:
|
|
2137
|
+
del current[keys[-1]]
|
|
2138
|
+
return True
|
|
2139
|
+
return False
|
|
2140
|
+
|
|
2141
|
+
def merge_config(self, config: Dict) -> Dict:
|
|
2142
|
+
"""Merge config into current."""
|
|
2143
|
+
def deep_merge(base, overlay):
|
|
2144
|
+
for key, value in overlay.items():
|
|
2145
|
+
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
|
2146
|
+
deep_merge(base[key], value)
|
|
2147
|
+
else:
|
|
2148
|
+
base[key] = value
|
|
2149
|
+
deep_merge(self._config, config)
|
|
2150
|
+
return self._config
|
|
2151
|
+
|
|
2152
|
+
def reload(self) -> Dict:
|
|
2153
|
+
"""Reload from file."""
|
|
2154
|
+
if self._file_path and os.path.exists(self._file_path):
|
|
2155
|
+
return self.load(self._file_path)
|
|
2156
|
+
return self._config
|
|
2157
|
+
|
|
2158
|
+
def get_env(self, name: str, default: str = None) -> Optional[str]:
|
|
2159
|
+
"""Get environment variable."""
|
|
2160
|
+
return os.environ.get(name, default)
|
|
2161
|
+
|
|
2162
|
+
def set_env(self, name: str, value: str) -> bool:
|
|
2163
|
+
"""Set environment variable."""
|
|
2164
|
+
os.environ[name] = value
|
|
2165
|
+
return True
|
|
2166
|
+
|
|
2167
|
+
|
|
2168
|
+
# Sentinel for missing values
|
|
2169
|
+
class _MissingSentinel:
|
|
2170
|
+
pass
|
|
2171
|
+
_MISSING = _MissingSentinel()
|
|
2172
|
+
|
|
2173
|
+
|
|
2174
|
+
# =============================================================================
|
|
2175
|
+
# @Server Module - HTTP Server for APIs and static files
|
|
2176
|
+
# =============================================================================
|
|
2177
|
+
|
|
2178
|
+
class ServerModule(CSSLModuleBase):
|
|
2179
|
+
"""
|
|
2180
|
+
@Server - HTTP Server for web APIs and static files
|
|
2181
|
+
|
|
2182
|
+
Methods:
|
|
2183
|
+
run(port, host) - Start HTTP server
|
|
2184
|
+
stop() - Stop the server
|
|
2185
|
+
api(path, method) - Register API endpoint
|
|
2186
|
+
static(path, dir) - Serve static files
|
|
2187
|
+
showServer() - Display server information
|
|
2188
|
+
getConnections() - Get active connections count
|
|
2189
|
+
getRoutes() - List registered routes
|
|
2190
|
+
status() - Get server status
|
|
2191
|
+
setErrorHandler(func) - Set custom error handler
|
|
2192
|
+
setCORS(origins) - Configure CORS
|
|
2193
|
+
|
|
2194
|
+
Example:
|
|
2195
|
+
@Server <== get('Server');
|
|
2196
|
+
@Server.run(port=3030);
|
|
2197
|
+
|
|
2198
|
+
@Server.api('/status') <== {
|
|
2199
|
+
define handler {
|
|
2200
|
+
return { "status": "ok" };
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
"""
|
|
2204
|
+
|
|
2205
|
+
def __init__(self, runtime=None):
|
|
2206
|
+
super().__init__(runtime)
|
|
2207
|
+
self._server = None
|
|
2208
|
+
self._server_thread = None
|
|
2209
|
+
self._port = 8080
|
|
2210
|
+
self._host = '0.0.0.0'
|
|
2211
|
+
self._running = False
|
|
2212
|
+
self._routes: Dict[str, Dict] = {}
|
|
2213
|
+
self._static_dirs: Dict[str, str] = {}
|
|
2214
|
+
self._connections = 0
|
|
2215
|
+
self._cors_origins = ['*']
|
|
2216
|
+
self._error_handler = None
|
|
2217
|
+
|
|
2218
|
+
def _register_methods(self):
|
|
2219
|
+
self._methods['run'] = self.run
|
|
2220
|
+
self._methods['stop'] = self.stop
|
|
2221
|
+
self._methods['api'] = self.api
|
|
2222
|
+
self._methods['static'] = self.static
|
|
2223
|
+
self._methods['showServer'] = self.show_server
|
|
2224
|
+
self._methods['getConnections'] = self.get_connections
|
|
2225
|
+
self._methods['getRoutes'] = self.get_routes
|
|
2226
|
+
self._methods['status'] = self.status
|
|
2227
|
+
self._methods['setErrorHandler'] = self.set_error_handler
|
|
2228
|
+
self._methods['setCORS'] = self.set_cors
|
|
2229
|
+
|
|
2230
|
+
def run(self, port: int = 8080, host: str = '0.0.0.0') -> bool:
|
|
2231
|
+
"""Start the HTTP server."""
|
|
2232
|
+
if self._running:
|
|
2233
|
+
print(f"[Server] Already running on {self._host}:{self._port}")
|
|
2234
|
+
return False
|
|
2235
|
+
|
|
2236
|
+
self._port = port
|
|
2237
|
+
self._host = host
|
|
2238
|
+
|
|
2239
|
+
try:
|
|
2240
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
2241
|
+
import socket
|
|
2242
|
+
|
|
2243
|
+
module_ref = self
|
|
2244
|
+
|
|
2245
|
+
class CSSLRequestHandler(BaseHTTPRequestHandler):
|
|
2246
|
+
def log_message(self, format, *args):
|
|
2247
|
+
# Custom logging
|
|
2248
|
+
print(f"[Server] {self.address_string()} - {format % args}")
|
|
2249
|
+
|
|
2250
|
+
def _send_response(self, status: int, body: Any, content_type: str = 'application/json'):
|
|
2251
|
+
self.send_response(status)
|
|
2252
|
+
self.send_header('Content-Type', content_type)
|
|
2253
|
+
# CORS headers
|
|
2254
|
+
origin = self.headers.get('Origin', '*')
|
|
2255
|
+
if '*' in module_ref._cors_origins or origin in module_ref._cors_origins:
|
|
2256
|
+
self.send_header('Access-Control-Allow-Origin', origin)
|
|
2257
|
+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
|
2258
|
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
|
2259
|
+
self.end_headers()
|
|
2260
|
+
|
|
2261
|
+
if isinstance(body, dict) or isinstance(body, list):
|
|
2262
|
+
body = json.dumps(body, ensure_ascii=False)
|
|
2263
|
+
if isinstance(body, str):
|
|
2264
|
+
body = body.encode('utf-8')
|
|
2265
|
+
self.wfile.write(body)
|
|
2266
|
+
|
|
2267
|
+
def _handle_request(self, method: str):
|
|
2268
|
+
module_ref._connections += 1
|
|
2269
|
+
try:
|
|
2270
|
+
path = self.path.split('?')[0]
|
|
2271
|
+
|
|
2272
|
+
# Check API routes
|
|
2273
|
+
route_key = f"{method}:{path}"
|
|
2274
|
+
if route_key in module_ref._routes:
|
|
2275
|
+
route = module_ref._routes[route_key]
|
|
2276
|
+
handler = route.get('handler')
|
|
2277
|
+
|
|
2278
|
+
# Parse request body
|
|
2279
|
+
content_length = int(self.headers.get('Content-Length', 0))
|
|
2280
|
+
body = None
|
|
2281
|
+
if content_length > 0:
|
|
2282
|
+
raw_body = self.rfile.read(content_length)
|
|
2283
|
+
try:
|
|
2284
|
+
body = json.loads(raw_body.decode('utf-8'))
|
|
2285
|
+
except:
|
|
2286
|
+
body = raw_body.decode('utf-8')
|
|
2287
|
+
|
|
2288
|
+
# Build request object
|
|
2289
|
+
request = {
|
|
2290
|
+
'method': method,
|
|
2291
|
+
'path': path,
|
|
2292
|
+
'headers': dict(self.headers),
|
|
2293
|
+
'query': self._parse_query(),
|
|
2294
|
+
'body': body
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
# Call handler
|
|
2298
|
+
if callable(handler):
|
|
2299
|
+
try:
|
|
2300
|
+
result = handler(request)
|
|
2301
|
+
self._send_response(200, result)
|
|
2302
|
+
except Exception as e:
|
|
2303
|
+
if module_ref._error_handler:
|
|
2304
|
+
result = module_ref._error_handler(e)
|
|
2305
|
+
self._send_response(500, result)
|
|
2306
|
+
else:
|
|
2307
|
+
self._send_response(500, {'error': str(e)})
|
|
2308
|
+
else:
|
|
2309
|
+
self._send_response(200, handler)
|
|
2310
|
+
return
|
|
2311
|
+
|
|
2312
|
+
# Check static directories
|
|
2313
|
+
for url_path, dir_path in module_ref._static_dirs.items():
|
|
2314
|
+
if path.startswith(url_path):
|
|
2315
|
+
file_path = path[len(url_path):].lstrip('/')
|
|
2316
|
+
full_path = os.path.join(dir_path, file_path)
|
|
2317
|
+
if os.path.isfile(full_path):
|
|
2318
|
+
self._serve_file(full_path)
|
|
2319
|
+
return
|
|
2320
|
+
|
|
2321
|
+
# 404 Not Found
|
|
2322
|
+
self._send_response(404, {'error': 'Not Found', 'path': path})
|
|
2323
|
+
|
|
2324
|
+
finally:
|
|
2325
|
+
module_ref._connections -= 1
|
|
2326
|
+
|
|
2327
|
+
def _parse_query(self) -> Dict:
|
|
2328
|
+
from urllib.parse import urlparse, parse_qs
|
|
2329
|
+
query = urlparse(self.path).query
|
|
2330
|
+
return {k: v[0] if len(v) == 1 else v for k, v in parse_qs(query).items()}
|
|
2331
|
+
|
|
2332
|
+
def _serve_file(self, path: str):
|
|
2333
|
+
import mimetypes
|
|
2334
|
+
mime_type = mimetypes.guess_type(path)[0] or 'application/octet-stream'
|
|
2335
|
+
try:
|
|
2336
|
+
with open(path, 'rb') as f:
|
|
2337
|
+
content = f.read()
|
|
2338
|
+
self._send_response(200, content, mime_type)
|
|
2339
|
+
except Exception as e:
|
|
2340
|
+
self._send_response(500, {'error': str(e)})
|
|
2341
|
+
|
|
2342
|
+
def do_GET(self):
|
|
2343
|
+
self._handle_request('GET')
|
|
2344
|
+
|
|
2345
|
+
def do_POST(self):
|
|
2346
|
+
self._handle_request('POST')
|
|
2347
|
+
|
|
2348
|
+
def do_PUT(self):
|
|
2349
|
+
self._handle_request('PUT')
|
|
2350
|
+
|
|
2351
|
+
def do_DELETE(self):
|
|
2352
|
+
self._handle_request('DELETE')
|
|
2353
|
+
|
|
2354
|
+
def do_OPTIONS(self):
|
|
2355
|
+
# CORS preflight
|
|
2356
|
+
self.send_response(200)
|
|
2357
|
+
origin = self.headers.get('Origin', '*')
|
|
2358
|
+
if '*' in module_ref._cors_origins or origin in module_ref._cors_origins:
|
|
2359
|
+
self.send_header('Access-Control-Allow-Origin', origin)
|
|
2360
|
+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
|
|
2361
|
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
|
2362
|
+
self.send_header('Access-Control-Max-Age', '86400')
|
|
2363
|
+
self.end_headers()
|
|
2364
|
+
|
|
2365
|
+
# Create server with SO_REUSEADDR
|
|
2366
|
+
class ReusableTCPServer(HTTPServer):
|
|
2367
|
+
allow_reuse_address = True
|
|
2368
|
+
|
|
2369
|
+
self._server = ReusableTCPServer((host, port), CSSLRequestHandler)
|
|
2370
|
+
self._running = True
|
|
2371
|
+
|
|
2372
|
+
# Start server in thread
|
|
2373
|
+
def serve():
|
|
2374
|
+
print(f"[Server] Started on http://{host}:{port}")
|
|
2375
|
+
self._server.serve_forever()
|
|
2376
|
+
|
|
2377
|
+
self._server_thread = threading.Thread(target=serve, daemon=True)
|
|
2378
|
+
self._server_thread.start()
|
|
2379
|
+
|
|
2380
|
+
return True
|
|
2381
|
+
|
|
2382
|
+
except Exception as e:
|
|
2383
|
+
print(f"[Server] Failed to start: {e}")
|
|
2384
|
+
return False
|
|
2385
|
+
|
|
2386
|
+
def stop(self) -> bool:
|
|
2387
|
+
"""Stop the HTTP server."""
|
|
2388
|
+
if not self._running or not self._server:
|
|
2389
|
+
print("[Server] Not running")
|
|
2390
|
+
return False
|
|
2391
|
+
|
|
2392
|
+
try:
|
|
2393
|
+
self._server.shutdown()
|
|
2394
|
+
self._running = False
|
|
2395
|
+
print("[Server] Stopped")
|
|
2396
|
+
return True
|
|
2397
|
+
except Exception as e:
|
|
2398
|
+
print(f"[Server] Error stopping: {e}")
|
|
2399
|
+
return False
|
|
2400
|
+
|
|
2401
|
+
def api(self, path: str, method: str = 'GET', handler: Callable = None) -> 'ApiRouteBuilder':
|
|
2402
|
+
"""
|
|
2403
|
+
Register an API endpoint.
|
|
2404
|
+
|
|
2405
|
+
Can be used as:
|
|
2406
|
+
@Server.api('/status', handler=myFunc)
|
|
2407
|
+
or:
|
|
2408
|
+
@Server.api('/status') <== { define handler { return {...}; } };
|
|
2409
|
+
"""
|
|
2410
|
+
method = method.upper()
|
|
2411
|
+
route_key = f"{method}:{path}"
|
|
2412
|
+
|
|
2413
|
+
if handler:
|
|
2414
|
+
self._routes[route_key] = {
|
|
2415
|
+
'path': path,
|
|
2416
|
+
'method': method,
|
|
2417
|
+
'handler': handler
|
|
2418
|
+
}
|
|
2419
|
+
print(f"[Server] Registered {method} {path}")
|
|
2420
|
+
return True
|
|
2421
|
+
|
|
2422
|
+
# Return builder for <== assignment
|
|
2423
|
+
return ApiRouteBuilder(self, path, method)
|
|
2424
|
+
|
|
2425
|
+
def static(self, url_path: str, directory: str) -> bool:
|
|
2426
|
+
"""Serve static files from a directory."""
|
|
2427
|
+
if not os.path.isdir(directory):
|
|
2428
|
+
print(f"[Server] Directory not found: {directory}")
|
|
2429
|
+
return False
|
|
2430
|
+
|
|
2431
|
+
# Ensure url_path starts with /
|
|
2432
|
+
if not url_path.startswith('/'):
|
|
2433
|
+
url_path = '/' + url_path
|
|
2434
|
+
|
|
2435
|
+
self._static_dirs[url_path] = directory
|
|
2436
|
+
print(f"[Server] Static files: {url_path} -> {directory}")
|
|
2437
|
+
return True
|
|
2438
|
+
|
|
2439
|
+
def show_server(self) -> Dict:
|
|
2440
|
+
"""Display server information."""
|
|
2441
|
+
info = {
|
|
2442
|
+
'running': self._running,
|
|
2443
|
+
'host': self._host,
|
|
2444
|
+
'port': self._port,
|
|
2445
|
+
'url': f"http://{self._host}:{self._port}" if self._running else None,
|
|
2446
|
+
'routes': len(self._routes),
|
|
2447
|
+
'static_dirs': len(self._static_dirs),
|
|
2448
|
+
'connections': self._connections
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
print(f"\n{'='*50}")
|
|
2452
|
+
print(f" CSSL Server Status")
|
|
2453
|
+
print(f"{'='*50}")
|
|
2454
|
+
print(f" Status: {'Running' if self._running else 'Stopped'}")
|
|
2455
|
+
if self._running:
|
|
2456
|
+
print(f" URL: http://{self._host}:{self._port}")
|
|
2457
|
+
print(f" Routes: {len(self._routes)}")
|
|
2458
|
+
print(f" Static Dirs: {len(self._static_dirs)}")
|
|
2459
|
+
print(f" Connections: {self._connections}")
|
|
2460
|
+
print(f"{'='*50}\n")
|
|
2461
|
+
|
|
2462
|
+
return info
|
|
2463
|
+
|
|
2464
|
+
def get_connections(self) -> int:
|
|
2465
|
+
"""Get number of active connections."""
|
|
2466
|
+
return self._connections
|
|
2467
|
+
|
|
2468
|
+
def get_routes(self) -> List[Dict]:
|
|
2469
|
+
"""Get list of registered routes."""
|
|
2470
|
+
return [
|
|
2471
|
+
{'method': r['method'], 'path': r['path']}
|
|
2472
|
+
for r in self._routes.values()
|
|
2473
|
+
]
|
|
2474
|
+
|
|
2475
|
+
def status(self) -> Dict:
|
|
2476
|
+
"""Get server status."""
|
|
2477
|
+
return {
|
|
2478
|
+
'running': self._running,
|
|
2479
|
+
'host': self._host,
|
|
2480
|
+
'port': self._port,
|
|
2481
|
+
'routes': len(self._routes),
|
|
2482
|
+
'connections': self._connections
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
def set_error_handler(self, handler: Callable) -> bool:
|
|
2486
|
+
"""Set custom error handler."""
|
|
2487
|
+
self._error_handler = handler
|
|
2488
|
+
return True
|
|
2489
|
+
|
|
2490
|
+
def set_cors(self, origins: Union[str, List[str]]) -> bool:
|
|
2491
|
+
"""Configure CORS allowed origins."""
|
|
2492
|
+
if isinstance(origins, str):
|
|
2493
|
+
origins = [origins]
|
|
2494
|
+
self._cors_origins = origins
|
|
2495
|
+
return True
|
|
2496
|
+
|
|
2497
|
+
|
|
2498
|
+
class ApiRouteBuilder:
|
|
2499
|
+
"""Builder class for API route registration with <== syntax."""
|
|
2500
|
+
|
|
2501
|
+
def __init__(self, server: ServerModule, path: str, method: str):
|
|
2502
|
+
self._server = server
|
|
2503
|
+
self._path = path
|
|
2504
|
+
self._method = method
|
|
2505
|
+
|
|
2506
|
+
def __call__(self, handler: Callable) -> bool:
|
|
2507
|
+
"""Called when used as @Server.api('/path')(handler)"""
|
|
2508
|
+
return self._server.api(self._path, self._method, handler)
|
|
2509
|
+
|
|
2510
|
+
def set_handler(self, handler: Any) -> bool:
|
|
2511
|
+
"""Called by runtime for <== assignment"""
|
|
2512
|
+
route_key = f"{self._method}:{self._path}"
|
|
2513
|
+
self._server._routes[route_key] = {
|
|
2514
|
+
'path': self._path,
|
|
2515
|
+
'method': self._method,
|
|
2516
|
+
'handler': handler
|
|
2517
|
+
}
|
|
2518
|
+
print(f"[Server] Registered {self._method} {self._path}")
|
|
2519
|
+
return True
|
|
2520
|
+
|
|
2521
|
+
|
|
2522
|
+
# =============================================================================
|
|
2523
|
+
# @APK Module - App Registration and Management
|
|
2524
|
+
# =============================================================================
|
|
2525
|
+
|
|
2526
|
+
class APKModule(CSSLModuleBase):
|
|
2527
|
+
"""
|
|
2528
|
+
@APK - App package management and registration
|
|
2529
|
+
|
|
2530
|
+
Methods:
|
|
2531
|
+
createAppService(config) - Register an app from CSSL
|
|
2532
|
+
registerApp(app_info) - Register app directly
|
|
2533
|
+
getAppInfo(app_id) - Get app metadata
|
|
2534
|
+
listApps() - List all registered apps
|
|
2535
|
+
launchApp(app_id) - Launch an app
|
|
2536
|
+
isInstalled(app_id) - Check if app is installed
|
|
2537
|
+
|
|
2538
|
+
Usage in CSSL:
|
|
2539
|
+
@APK.createAppService() <== {
|
|
2540
|
+
script = "app.py";
|
|
2541
|
+
service = "myapp.cll";
|
|
2542
|
+
execute_on_boot = false;
|
|
2543
|
+
}
|
|
2544
|
+
"""
|
|
2545
|
+
|
|
2546
|
+
def __init__(self, runtime=None):
|
|
2547
|
+
super().__init__(runtime)
|
|
2548
|
+
self._registry = None
|
|
2549
|
+
self._root_dir = None
|
|
2550
|
+
|
|
2551
|
+
def _register_methods(self):
|
|
2552
|
+
self._methods['createAppService'] = self.createAppService
|
|
2553
|
+
self._methods['registerApp'] = self.registerApp
|
|
2554
|
+
self._methods['getAppInfo'] = self.getAppInfo
|
|
2555
|
+
self._methods['listApps'] = self.listApps
|
|
2556
|
+
self._methods['launchApp'] = self.launchApp
|
|
2557
|
+
self._methods['isInstalled'] = self.isInstalled
|
|
2558
|
+
|
|
2559
|
+
def _get_registry(self):
|
|
2560
|
+
"""Lazy load AppRegistry"""
|
|
2561
|
+
if self._registry is None:
|
|
2562
|
+
try:
|
|
2563
|
+
import sys
|
|
2564
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
2565
|
+
from common.clients.apk import AppRegistry
|
|
2566
|
+
self._registry = AppRegistry()
|
|
2567
|
+
self._registry.scan()
|
|
2568
|
+
except ImportError:
|
|
2569
|
+
pass
|
|
2570
|
+
return self._registry
|
|
2571
|
+
|
|
2572
|
+
def _get_root_dir(self) -> str:
|
|
2573
|
+
"""Get root32 directory"""
|
|
2574
|
+
if self._root_dir is None:
|
|
2575
|
+
if self.runtime and hasattr(self.runtime, 'kernel') and hasattr(self.runtime.kernel, 'RootDirectory'):
|
|
2576
|
+
self._root_dir = self.runtime.kernel.RootDirectory
|
|
2577
|
+
else:
|
|
2578
|
+
self._root_dir = os.path.expanduser("~/.cso/root32")
|
|
2579
|
+
return self._root_dir
|
|
2580
|
+
|
|
2581
|
+
def createAppService(self, config: Dict = None) -> 'AppServiceBuilder':
|
|
2582
|
+
"""
|
|
2583
|
+
Create an app service registration.
|
|
2584
|
+
|
|
2585
|
+
Returns a builder that can be used with <== assignment.
|
|
2586
|
+
|
|
2587
|
+
Usage:
|
|
2588
|
+
@APK.createAppService() <== { script="app.py", service="myapp.cll" }
|
|
2589
|
+
"""
|
|
2590
|
+
return AppServiceBuilder(self, config or {})
|
|
2591
|
+
|
|
2592
|
+
def registerApp(self, app_info: Dict) -> bool:
|
|
2593
|
+
"""
|
|
2594
|
+
Register an app directly.
|
|
2595
|
+
|
|
2596
|
+
Args:
|
|
2597
|
+
app_info: Dict with name, version, category, icon, module_path
|
|
2598
|
+
"""
|
|
2599
|
+
registry = self._get_registry()
|
|
2600
|
+
if not registry:
|
|
2601
|
+
return False
|
|
2602
|
+
|
|
2603
|
+
try:
|
|
2604
|
+
from common.clients.apk import AppInfo
|
|
2605
|
+
info = AppInfo(
|
|
2606
|
+
app_id=app_info.get('id', app_info.get('name', '').lower()),
|
|
2607
|
+
name=app_info.get('name', ''),
|
|
2608
|
+
version=app_info.get('version', '1.0.0'),
|
|
2609
|
+
category=app_info.get('category', 'Apps'),
|
|
2610
|
+
description=app_info.get('description', ''),
|
|
2611
|
+
icon=app_info.get('icon'),
|
|
2612
|
+
module_path=app_info.get('module_path')
|
|
2613
|
+
)
|
|
2614
|
+
return registry.register_app(info)
|
|
2615
|
+
except Exception as e:
|
|
2616
|
+
print(f"[APK] Error registering app: {e}")
|
|
2617
|
+
return False
|
|
2618
|
+
|
|
2619
|
+
def getAppInfo(self, app_id: str) -> Optional[Dict]:
|
|
2620
|
+
"""Get app info by ID"""
|
|
2621
|
+
registry = self._get_registry()
|
|
2622
|
+
if not registry:
|
|
2623
|
+
return None
|
|
2624
|
+
|
|
2625
|
+
info = registry.get_app(app_id)
|
|
2626
|
+
if info:
|
|
2627
|
+
return {
|
|
2628
|
+
'id': info.app_id,
|
|
2629
|
+
'name': info.name,
|
|
2630
|
+
'version': info.version,
|
|
2631
|
+
'category': info.category,
|
|
2632
|
+
'description': info.description,
|
|
2633
|
+
'icon': info.icon
|
|
2634
|
+
}
|
|
2635
|
+
return None
|
|
2636
|
+
|
|
2637
|
+
def listApps(self) -> List[Dict]:
|
|
2638
|
+
"""List all registered apps"""
|
|
2639
|
+
registry = self._get_registry()
|
|
2640
|
+
if not registry:
|
|
2641
|
+
return []
|
|
2642
|
+
|
|
2643
|
+
return [
|
|
2644
|
+
{
|
|
2645
|
+
'id': info.app_id,
|
|
2646
|
+
'name': info.name,
|
|
2647
|
+
'category': info.category
|
|
2648
|
+
}
|
|
2649
|
+
for info in registry.get_all_apps()
|
|
2650
|
+
]
|
|
2651
|
+
|
|
2652
|
+
def launchApp(self, app_id: str) -> bool:
|
|
2653
|
+
"""Launch an app by ID (placeholder - needs desktop integration)"""
|
|
2654
|
+
print(f"[APK] TODO: Launch app {app_id}")
|
|
2655
|
+
return False
|
|
2656
|
+
|
|
2657
|
+
def isInstalled(self, app_id: str) -> bool:
|
|
2658
|
+
"""Check if app is installed"""
|
|
2659
|
+
registry = self._get_registry()
|
|
2660
|
+
if not registry:
|
|
2661
|
+
return False
|
|
2662
|
+
return app_id in registry
|
|
2663
|
+
|
|
2664
|
+
|
|
2665
|
+
class AppServiceBuilder:
|
|
2666
|
+
"""Builder class for app service registration with <== syntax."""
|
|
2667
|
+
|
|
2668
|
+
def __init__(self, module: APKModule, config: Dict):
|
|
2669
|
+
self._module = module
|
|
2670
|
+
self._config = config
|
|
2671
|
+
|
|
2672
|
+
def set_handler(self, config: Dict) -> bool:
|
|
2673
|
+
"""Called by runtime for <== assignment"""
|
|
2674
|
+
# Merge configs
|
|
2675
|
+
full_config = {**self._config, **config}
|
|
2676
|
+
|
|
2677
|
+
script = full_config.get('script', '')
|
|
2678
|
+
service = full_config.get('service', '')
|
|
2679
|
+
execute_on_boot = full_config.get('execute_on_boot', False)
|
|
2680
|
+
|
|
2681
|
+
# Derive service name from .cll filename
|
|
2682
|
+
service_name = service.replace('.cll', '') if service else ''
|
|
2683
|
+
|
|
2684
|
+
if not service_name:
|
|
2685
|
+
print("[APK] Error: service name is required")
|
|
2686
|
+
return False
|
|
2687
|
+
|
|
2688
|
+
# Look for .cll-meta file
|
|
2689
|
+
root_dir = self._module._get_root_dir()
|
|
2690
|
+
meta_path = os.path.join(root_dir, "sys", "intern", f"{service_name}.cll-meta")
|
|
2691
|
+
|
|
2692
|
+
if os.path.exists(meta_path):
|
|
2693
|
+
# Parse metadata and register
|
|
2694
|
+
app_info = self._parse_meta_file(meta_path)
|
|
2695
|
+
if app_info:
|
|
2696
|
+
app_info['script'] = script
|
|
2697
|
+
success = self._module.registerApp(app_info)
|
|
2698
|
+
|
|
2699
|
+
if success:
|
|
2700
|
+
print(f"[APK] Registered app: {app_info.get('name', service_name)}")
|
|
2701
|
+
|
|
2702
|
+
if execute_on_boot:
|
|
2703
|
+
print(f"[APK] TODO: Schedule {service_name} for boot execution")
|
|
2704
|
+
|
|
2705
|
+
return success
|
|
2706
|
+
|
|
2707
|
+
print(f"[APK] Meta file not found: {meta_path}")
|
|
2708
|
+
return False
|
|
2709
|
+
|
|
2710
|
+
def _parse_meta_file(self, path: str) -> Optional[Dict]:
|
|
2711
|
+
"""Parse .cll-meta file"""
|
|
2712
|
+
try:
|
|
2713
|
+
data = {}
|
|
2714
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
2715
|
+
for line in f:
|
|
2716
|
+
line = line.strip()
|
|
2717
|
+
if '=' in line and not line.startswith('#'):
|
|
2718
|
+
key, value = line.split('=', 1)
|
|
2719
|
+
data[key.strip()] = value.strip()
|
|
2720
|
+
|
|
2721
|
+
return {
|
|
2722
|
+
'id': data.get('Name', '').lower().replace(' ', '_'),
|
|
2723
|
+
'name': data.get('Name', ''),
|
|
2724
|
+
'version': data.get('Version', '1.0.0'),
|
|
2725
|
+
'category': data.get('Category', 'Apps'),
|
|
2726
|
+
'description': data.get('Description', ''),
|
|
2727
|
+
'icon': data.get('Icon'),
|
|
2728
|
+
'module_path': data.get('Module')
|
|
2729
|
+
}
|
|
2730
|
+
except Exception as e:
|
|
2731
|
+
print(f"[APK] Error parsing {path}: {e}")
|
|
2732
|
+
return None
|
|
2733
|
+
|
|
2734
|
+
|
|
2735
|
+
# =============================================================================
|
|
2736
|
+
# Module Registry
|
|
2737
|
+
# =============================================================================
|
|
2738
|
+
|
|
2739
|
+
class CSSLModuleRegistry:
|
|
2740
|
+
"""Registry of all CSSL standard modules"""
|
|
2741
|
+
|
|
2742
|
+
_instance = None
|
|
2743
|
+
_modules: Dict[str, CSSLModuleBase] = {}
|
|
2744
|
+
|
|
2745
|
+
def __new__(cls):
|
|
2746
|
+
if cls._instance is None:
|
|
2747
|
+
cls._instance = super().__new__(cls)
|
|
2748
|
+
cls._instance._init_modules()
|
|
2749
|
+
return cls._instance
|
|
2750
|
+
|
|
2751
|
+
def _init_modules(self):
|
|
2752
|
+
"""Initialize all standard modules"""
|
|
2753
|
+
self._modules = {
|
|
2754
|
+
'Time': TimeModule(),
|
|
2755
|
+
'Secrets': SecretsModule(),
|
|
2756
|
+
'Math': MathModule(),
|
|
2757
|
+
'Crypto': CryptoModule(),
|
|
2758
|
+
'Net': NetModule(),
|
|
2759
|
+
'IO': IOModule(),
|
|
2760
|
+
'JSON': JSONModule(),
|
|
2761
|
+
'Regex': RegexModule(),
|
|
2762
|
+
'System': SystemModule(),
|
|
2763
|
+
'Log': LogModule(),
|
|
2764
|
+
'Cache': CacheModule(),
|
|
2765
|
+
'Queue': QueueModule(),
|
|
2766
|
+
'Format': FormatModule(),
|
|
2767
|
+
'Console': ConsoleModule(),
|
|
2768
|
+
'Process': ProcessModule(),
|
|
2769
|
+
'Config': ConfigModule(),
|
|
2770
|
+
'Server': ServerModule(),
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
# Register Desktop module (lazy loaded)
|
|
2774
|
+
try:
|
|
2775
|
+
from .cssl_desktop import get_desktop_module
|
|
2776
|
+
self._modules['Desktop'] = get_desktop_module()
|
|
2777
|
+
except ImportError:
|
|
2778
|
+
pass
|
|
2779
|
+
|
|
2780
|
+
# Register APK module for app registration
|
|
2781
|
+
self._modules['APK'] = APKModule()
|
|
2782
|
+
|
|
2783
|
+
def get_module(self, name: str) -> Optional[CSSLModuleBase]:
|
|
2784
|
+
"""Get a module by name"""
|
|
2785
|
+
return self._modules.get(name)
|
|
2786
|
+
|
|
2787
|
+
def list_modules(self) -> List[str]:
|
|
2788
|
+
"""List all available module names"""
|
|
2789
|
+
return sorted(self._modules.keys())
|
|
2790
|
+
|
|
2791
|
+
def register_module(self, name: str, module: CSSLModuleBase):
|
|
2792
|
+
"""Register a custom module"""
|
|
2793
|
+
self._modules[name] = module
|
|
2794
|
+
|
|
2795
|
+
|
|
2796
|
+
def get_module_registry() -> CSSLModuleRegistry:
|
|
2797
|
+
"""Get the global module registry"""
|
|
2798
|
+
return CSSLModuleRegistry()
|
|
2799
|
+
|
|
2800
|
+
|
|
2801
|
+
def get_standard_module(name: str) -> Optional[CSSLModuleBase]:
|
|
2802
|
+
"""Get a standard module by name"""
|
|
2803
|
+
return get_module_registry().get_module(name)
|