zexus 1.6.2
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.
- package/LICENSE +0 -0
- package/README.md +2513 -0
- package/bin/zexus +2 -0
- package/bin/zpics +2 -0
- package/bin/zpm +2 -0
- package/bin/zx +2 -0
- package/bin/zx-deploy +2 -0
- package/bin/zx-dev +2 -0
- package/bin/zx-run +2 -0
- package/package.json +66 -0
- package/scripts/README.md +24 -0
- package/scripts/postinstall.js +44 -0
- package/shared_config.json +24 -0
- package/src/README.md +1525 -0
- package/src/tests/run_zexus_tests.py +117 -0
- package/src/tests/test_all_phases.zx +346 -0
- package/src/tests/test_blockchain_features.zx +306 -0
- package/src/tests/test_complexity_features.zx +321 -0
- package/src/tests/test_core_integration.py +185 -0
- package/src/tests/test_phase10_ecosystem.zx +177 -0
- package/src/tests/test_phase1_modifiers.zx +87 -0
- package/src/tests/test_phase2_plugins.zx +80 -0
- package/src/tests/test_phase3_security.zx +97 -0
- package/src/tests/test_phase4_vfs.zx +116 -0
- package/src/tests/test_phase5_types.zx +117 -0
- package/src/tests/test_phase6_metaprogramming.zx +125 -0
- package/src/tests/test_phase7_optimization.zx +132 -0
- package/src/tests/test_phase9_advanced_types.zx +157 -0
- package/src/tests/test_security_features.py +419 -0
- package/src/tests/test_security_features.zx +276 -0
- package/src/tests/test_simple_zx.zx +1 -0
- package/src/tests/test_verification_simple.zx +69 -0
- package/src/zexus/__init__.py +28 -0
- package/src/zexus/__main__.py +5 -0
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/advanced_types.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/builtin_modules.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/complexity_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/concurrency_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/config.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/dependency_injection.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/ecosystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/hybrid_orchestrator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/metaprogramming.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/optimization.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/plugin_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/policy_engine.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/stdlib_integration.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/strategy_recovery.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/type_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/virtual_filesystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +401 -0
- package/src/zexus/blockchain/__init__.py +40 -0
- package/src/zexus/blockchain/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/crypto.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/ledger.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/transaction.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/crypto.py +463 -0
- package/src/zexus/blockchain/ledger.py +255 -0
- package/src/zexus/blockchain/transaction.py +267 -0
- package/src/zexus/builtin_modules.py +284 -0
- package/src/zexus/builtin_plugins.py +317 -0
- package/src/zexus/capability_system.py +372 -0
- package/src/zexus/cli/__init__.py +2 -0
- package/src/zexus/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +707 -0
- package/src/zexus/cli/zpm.py +203 -0
- package/src/zexus/compare_interpreter_compiler.py +146 -0
- package/src/zexus/compiler/__init__.py +169 -0
- package/src/zexus/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/bytecode.py +266 -0
- package/src/zexus/compiler/compat_runtime.py +277 -0
- package/src/zexus/compiler/lexer.py +257 -0
- package/src/zexus/compiler/parser.py +779 -0
- package/src/zexus/compiler/semantic.py +118 -0
- package/src/zexus/compiler/zexus_ast.py +454 -0
- package/src/zexus/complexity_system.py +575 -0
- package/src/zexus/concurrency_system.py +493 -0
- package/src/zexus/config.py +201 -0
- package/src/zexus/crypto_bridge.py +19 -0
- package/src/zexus/dependency_injection.py +423 -0
- package/src/zexus/ecosystem.py +434 -0
- package/src/zexus/environment.py +101 -0
- package/src/zexus/environment_manager.py +119 -0
- package/src/zexus/error_reporter.py +314 -0
- package/src/zexus/evaluator/__init__.py +12 -0
- package/src/zexus/evaluator/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/integration.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +700 -0
- package/src/zexus/evaluator/core.py +891 -0
- package/src/zexus/evaluator/expressions.py +827 -0
- package/src/zexus/evaluator/functions.py +3989 -0
- package/src/zexus/evaluator/integration.py +396 -0
- package/src/zexus/evaluator/statements.py +4303 -0
- package/src/zexus/evaluator/utils.py +126 -0
- package/src/zexus/evaluator_original.py +2041 -0
- package/src/zexus/external_bridge.py +16 -0
- package/src/zexus/find_affected_imports.sh +155 -0
- package/src/zexus/hybrid_orchestrator.py +152 -0
- package/src/zexus/input_validation.py +259 -0
- package/src/zexus/lexer.py +571 -0
- package/src/zexus/logging.py +89 -0
- package/src/zexus/lsp/__init__.py +9 -0
- package/src/zexus/lsp/completion_provider.py +207 -0
- package/src/zexus/lsp/definition_provider.py +22 -0
- package/src/zexus/lsp/hover_provider.py +71 -0
- package/src/zexus/lsp/server.py +269 -0
- package/src/zexus/lsp/symbol_provider.py +31 -0
- package/src/zexus/metaprogramming.py +321 -0
- package/src/zexus/module_cache.py +89 -0
- package/src/zexus/module_manager.py +107 -0
- package/src/zexus/object.py +973 -0
- package/src/zexus/optimization.py +424 -0
- package/src/zexus/parser/__init__.py +31 -0
- package/src/zexus/parser/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/integration.py +86 -0
- package/src/zexus/parser/parser.py +3977 -0
- package/src/zexus/parser/strategy_context.py +7254 -0
- package/src/zexus/parser/strategy_structural.py +1033 -0
- package/src/zexus/persistence.py +391 -0
- package/src/zexus/plugin_system.py +290 -0
- package/src/zexus/policy_engine.py +365 -0
- package/src/zexus/profiler/__init__.py +5 -0
- package/src/zexus/profiler/profiler.py +233 -0
- package/src/zexus/purity_system.py +398 -0
- package/src/zexus/runtime/__init__.py +20 -0
- package/src/zexus/runtime/async_runtime.py +324 -0
- package/src/zexus/search_old_imports.sh +65 -0
- package/src/zexus/security.py +1407 -0
- package/src/zexus/stack_trace.py +233 -0
- package/src/zexus/stdlib/__init__.py +27 -0
- package/src/zexus/stdlib/blockchain.py +341 -0
- package/src/zexus/stdlib/compression.py +167 -0
- package/src/zexus/stdlib/crypto.py +124 -0
- package/src/zexus/stdlib/datetime.py +163 -0
- package/src/zexus/stdlib/db_mongo.py +199 -0
- package/src/zexus/stdlib/db_mysql.py +162 -0
- package/src/zexus/stdlib/db_postgres.py +163 -0
- package/src/zexus/stdlib/db_sqlite.py +133 -0
- package/src/zexus/stdlib/encoding.py +230 -0
- package/src/zexus/stdlib/fs.py +195 -0
- package/src/zexus/stdlib/http.py +219 -0
- package/src/zexus/stdlib/http_server.py +248 -0
- package/src/zexus/stdlib/json_module.py +61 -0
- package/src/zexus/stdlib/math.py +360 -0
- package/src/zexus/stdlib/os_module.py +265 -0
- package/src/zexus/stdlib/regex.py +148 -0
- package/src/zexus/stdlib/sockets.py +253 -0
- package/src/zexus/stdlib/test_framework.zx +208 -0
- package/src/zexus/stdlib/test_runner.zx +119 -0
- package/src/zexus/stdlib_integration.py +341 -0
- package/src/zexus/strategy_recovery.py +256 -0
- package/src/zexus/syntax_validator.py +356 -0
- package/src/zexus/testing/zpics.py +407 -0
- package/src/zexus/testing/zpics_runtime.py +369 -0
- package/src/zexus/type_system.py +374 -0
- package/src/zexus/validation_system.py +569 -0
- package/src/zexus/virtual_filesystem.py +355 -0
- package/src/zexus/vm/__init__.py +8 -0
- package/src/zexus/vm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_manager.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_pool.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/peephole_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/profiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_allocator.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/ssa_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +420 -0
- package/src/zexus/vm/bytecode.py +428 -0
- package/src/zexus/vm/bytecode_converter.py +297 -0
- package/src/zexus/vm/cache.py +532 -0
- package/src/zexus/vm/jit.py +720 -0
- package/src/zexus/vm/memory_manager.py +520 -0
- package/src/zexus/vm/memory_pool.py +511 -0
- package/src/zexus/vm/optimizer.py +478 -0
- package/src/zexus/vm/parallel_vm.py +899 -0
- package/src/zexus/vm/peephole_optimizer.py +452 -0
- package/src/zexus/vm/profiler.py +527 -0
- package/src/zexus/vm/register_allocator.py +462 -0
- package/src/zexus/vm/register_vm.py +520 -0
- package/src/zexus/vm/ssa_converter.py +757 -0
- package/src/zexus/vm/vm.py +1392 -0
- package/src/zexus/zexus_ast.py +1782 -0
- package/src/zexus/zexus_token.py +253 -0
- package/src/zexus/zpm/__init__.py +15 -0
- package/src/zexus/zpm/installer.py +116 -0
- package/src/zexus/zpm/package_manager.py +208 -0
- package/src/zexus/zpm/publisher.py +98 -0
- package/src/zexus/zpm/registry.py +110 -0
- package/src/zexus.egg-info/PKG-INFO +2235 -0
- package/src/zexus.egg-info/SOURCES.txt +876 -0
- package/src/zexus.egg-info/dependency_links.txt +1 -0
- package/src/zexus.egg-info/entry_points.txt +3 -0
- package/src/zexus.egg-info/not-zip-safe +1 -0
- package/src/zexus.egg-info/requires.txt +14 -0
- package/src/zexus.egg-info/top_level.txt +2 -0
- package/zexus.json +14 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# src/zexus/persistence.py
|
|
2
|
+
"""
|
|
3
|
+
Persistent Memory Management for Zexus
|
|
4
|
+
Extends persistent storage beyond contracts to all storage keywords (LET, CONST, ENTITY, etc.)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
import sqlite3
|
|
10
|
+
import weakref
|
|
11
|
+
from threading import Lock
|
|
12
|
+
from typing import Dict, Any, Optional, Set
|
|
13
|
+
from .object import (
|
|
14
|
+
Object, Integer, Float, String, Boolean as BooleanObj,
|
|
15
|
+
Null, NULL, List, Map, EntityInstance
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Storage directory for persistent data
|
|
19
|
+
PERSISTENCE_DIR = os.path.expanduser("~/.zexus/persistence")
|
|
20
|
+
os.makedirs(PERSISTENCE_DIR, exist_ok=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ===============================================
|
|
24
|
+
# MEMORY LEAK TRACKING
|
|
25
|
+
# ===============================================
|
|
26
|
+
|
|
27
|
+
class MemoryTracker:
|
|
28
|
+
"""Track object allocations and detect potential memory leaks"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.allocations: Dict[int, Dict[str, Any]] = {}
|
|
32
|
+
self.weak_refs: Dict[int, weakref.ref] = {}
|
|
33
|
+
self.lock = Lock()
|
|
34
|
+
self.enabled = True
|
|
35
|
+
self.allocation_count = 0
|
|
36
|
+
self.max_allocations = 100000 # Alert threshold
|
|
37
|
+
|
|
38
|
+
def track(self, obj: Object, context: str = "unknown"):
|
|
39
|
+
"""Track an object allocation"""
|
|
40
|
+
if not self.enabled:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
with self.lock:
|
|
44
|
+
obj_id = id(obj)
|
|
45
|
+
self.allocation_count += 1
|
|
46
|
+
|
|
47
|
+
# Create weak reference to detect when object is garbage collected
|
|
48
|
+
def cleanup(ref):
|
|
49
|
+
with self.lock:
|
|
50
|
+
if obj_id in self.allocations:
|
|
51
|
+
del self.allocations[obj_id]
|
|
52
|
+
if obj_id in self.weak_refs:
|
|
53
|
+
del self.weak_refs[obj_id]
|
|
54
|
+
|
|
55
|
+
self.weak_refs[obj_id] = weakref.ref(obj, cleanup)
|
|
56
|
+
self.allocations[obj_id] = {
|
|
57
|
+
'type': obj.type() if hasattr(obj, 'type') else type(obj).__name__,
|
|
58
|
+
'context': context,
|
|
59
|
+
'allocation_number': self.allocation_count
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Check for potential memory leak
|
|
63
|
+
if len(self.allocations) > self.max_allocations:
|
|
64
|
+
self._report_potential_leak()
|
|
65
|
+
|
|
66
|
+
def untrack(self, obj: Object):
|
|
67
|
+
"""Manually untrack an object"""
|
|
68
|
+
with self.lock:
|
|
69
|
+
obj_id = id(obj)
|
|
70
|
+
if obj_id in self.allocations:
|
|
71
|
+
del self.allocations[obj_id]
|
|
72
|
+
if obj_id in self.weak_refs:
|
|
73
|
+
del self.weak_refs[obj_id]
|
|
74
|
+
|
|
75
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
76
|
+
"""Get memory usage statistics"""
|
|
77
|
+
with self.lock:
|
|
78
|
+
type_counts = {}
|
|
79
|
+
for alloc in self.allocations.values():
|
|
80
|
+
obj_type = alloc['type']
|
|
81
|
+
type_counts[obj_type] = type_counts.get(obj_type, 0) + 1
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
'total_tracked': len(self.allocations),
|
|
85
|
+
'total_allocated': self.allocation_count,
|
|
86
|
+
'by_type': type_counts
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def _report_potential_leak(self):
|
|
90
|
+
"""Report potential memory leak"""
|
|
91
|
+
stats = self.get_stats()
|
|
92
|
+
print(f"⚠️ MEMORY WARNING: {stats['total_tracked']} objects tracked (threshold: {self.max_allocations})")
|
|
93
|
+
print(f" Breakdown: {stats['by_type']}")
|
|
94
|
+
|
|
95
|
+
def clear(self):
|
|
96
|
+
"""Clear all tracking data"""
|
|
97
|
+
with self.lock:
|
|
98
|
+
self.allocations.clear()
|
|
99
|
+
self.weak_refs.clear()
|
|
100
|
+
self.allocation_count = 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Global memory tracker instance
|
|
104
|
+
_memory_tracker = MemoryTracker()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def track_allocation(obj: Object, context: str = "unknown"):
|
|
108
|
+
"""Track an object allocation"""
|
|
109
|
+
_memory_tracker.track(obj, context)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_memory_stats() -> Dict[str, Any]:
|
|
113
|
+
"""Get current memory statistics"""
|
|
114
|
+
return _memory_tracker.get_stats()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def enable_memory_tracking():
|
|
118
|
+
"""Enable memory tracking"""
|
|
119
|
+
_memory_tracker.enabled = True
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def disable_memory_tracking():
|
|
123
|
+
"""Disable memory tracking"""
|
|
124
|
+
_memory_tracker.enabled = False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ===============================================
|
|
128
|
+
# PERSISTENT STORAGE BACKEND
|
|
129
|
+
# ===============================================
|
|
130
|
+
|
|
131
|
+
class PersistentStorage:
|
|
132
|
+
"""Persistent storage for variables using SQLite"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, scope_id: str, storage_dir: str = PERSISTENCE_DIR):
|
|
135
|
+
self.scope_id = scope_id
|
|
136
|
+
self.db_path = os.path.join(storage_dir, f"{scope_id}.sqlite")
|
|
137
|
+
self.conn = None
|
|
138
|
+
self.lock = Lock()
|
|
139
|
+
self._init_db()
|
|
140
|
+
|
|
141
|
+
def _init_db(self):
|
|
142
|
+
"""Initialize SQLite database"""
|
|
143
|
+
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
144
|
+
cursor = self.conn.cursor()
|
|
145
|
+
cursor.execute('''
|
|
146
|
+
CREATE TABLE IF NOT EXISTS variables (
|
|
147
|
+
name TEXT PRIMARY KEY,
|
|
148
|
+
type TEXT NOT NULL,
|
|
149
|
+
value TEXT NOT NULL,
|
|
150
|
+
is_const INTEGER DEFAULT 0,
|
|
151
|
+
created_at REAL NOT NULL,
|
|
152
|
+
updated_at REAL NOT NULL
|
|
153
|
+
)
|
|
154
|
+
''')
|
|
155
|
+
cursor.execute('''
|
|
156
|
+
CREATE INDEX IF NOT EXISTS idx_name ON variables(name)
|
|
157
|
+
''')
|
|
158
|
+
self.conn.commit()
|
|
159
|
+
|
|
160
|
+
def set(self, name: str, value: Object, is_const: bool = False):
|
|
161
|
+
"""Persist a variable"""
|
|
162
|
+
with self.lock:
|
|
163
|
+
serialized = self._serialize(value)
|
|
164
|
+
cursor = self.conn.cursor()
|
|
165
|
+
|
|
166
|
+
import time
|
|
167
|
+
timestamp = time.time()
|
|
168
|
+
|
|
169
|
+
cursor.execute('''
|
|
170
|
+
INSERT OR REPLACE INTO variables (name, type, value, is_const, created_at, updated_at)
|
|
171
|
+
VALUES (?, ?, ?, ?,
|
|
172
|
+
COALESCE((SELECT created_at FROM variables WHERE name = ?), ?),
|
|
173
|
+
?)
|
|
174
|
+
''', (name, serialized['type'], serialized['value'], 1 if is_const else 0,
|
|
175
|
+
name, timestamp, timestamp))
|
|
176
|
+
|
|
177
|
+
self.conn.commit()
|
|
178
|
+
|
|
179
|
+
def get(self, name: str) -> Optional[Object]:
|
|
180
|
+
"""Retrieve a persisted variable"""
|
|
181
|
+
with self.lock:
|
|
182
|
+
cursor = self.conn.cursor()
|
|
183
|
+
cursor.execute('SELECT type, value FROM variables WHERE name = ?', (name,))
|
|
184
|
+
row = cursor.fetchone()
|
|
185
|
+
|
|
186
|
+
if row is None:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
return self._deserialize({'type': row[0], 'value': row[1]})
|
|
190
|
+
|
|
191
|
+
def delete(self, name: str):
|
|
192
|
+
"""Delete a persisted variable"""
|
|
193
|
+
with self.lock:
|
|
194
|
+
cursor = self.conn.cursor()
|
|
195
|
+
cursor.execute('DELETE FROM variables WHERE name = ?', (name,))
|
|
196
|
+
self.conn.commit()
|
|
197
|
+
|
|
198
|
+
def is_const(self, name: str) -> bool:
|
|
199
|
+
"""Check if a variable is const"""
|
|
200
|
+
with self.lock:
|
|
201
|
+
cursor = self.conn.cursor()
|
|
202
|
+
cursor.execute('SELECT is_const FROM variables WHERE name = ?', (name,))
|
|
203
|
+
row = cursor.fetchone()
|
|
204
|
+
return bool(row[0]) if row else False
|
|
205
|
+
|
|
206
|
+
def list_variables(self) -> list:
|
|
207
|
+
"""List all persisted variables"""
|
|
208
|
+
with self.lock:
|
|
209
|
+
cursor = self.conn.cursor()
|
|
210
|
+
cursor.execute('SELECT name, type, is_const FROM variables')
|
|
211
|
+
return [{'name': row[0], 'type': row[1], 'const': bool(row[2])} for row in cursor.fetchall()]
|
|
212
|
+
|
|
213
|
+
def clear(self):
|
|
214
|
+
"""Clear all persisted variables"""
|
|
215
|
+
with self.lock:
|
|
216
|
+
cursor = self.conn.cursor()
|
|
217
|
+
cursor.execute('DELETE FROM variables')
|
|
218
|
+
self.conn.commit()
|
|
219
|
+
|
|
220
|
+
def _serialize(self, obj: Object) -> Dict[str, str]:
|
|
221
|
+
"""Serialize Zexus object to JSON"""
|
|
222
|
+
if isinstance(obj, String):
|
|
223
|
+
return {'type': 'string', 'value': json.dumps(obj.value)}
|
|
224
|
+
elif isinstance(obj, Integer):
|
|
225
|
+
return {'type': 'integer', 'value': json.dumps(obj.value)}
|
|
226
|
+
elif isinstance(obj, Float):
|
|
227
|
+
return {'type': 'float', 'value': json.dumps(obj.value)}
|
|
228
|
+
elif isinstance(obj, BooleanObj):
|
|
229
|
+
return {'type': 'boolean', 'value': json.dumps(obj.value)}
|
|
230
|
+
elif isinstance(obj, List):
|
|
231
|
+
serialized = [self._serialize(e) for e in obj.elements]
|
|
232
|
+
return {'type': 'list', 'value': json.dumps(serialized)}
|
|
233
|
+
elif isinstance(obj, Map):
|
|
234
|
+
serialized = {k: self._serialize(v) for k, v in obj.pairs.items()}
|
|
235
|
+
return {'type': 'map', 'value': json.dumps(serialized)}
|
|
236
|
+
elif obj is Null or obj is NULL:
|
|
237
|
+
return {'type': 'null', 'value': json.dumps(None)}
|
|
238
|
+
elif isinstance(obj, EntityInstance):
|
|
239
|
+
serialized_values = {k: self._serialize(v) for k, v in obj.values.items()}
|
|
240
|
+
return {
|
|
241
|
+
'type': 'entity_instance',
|
|
242
|
+
'value': json.dumps({
|
|
243
|
+
'entity_name': obj.entity_def.name,
|
|
244
|
+
'values': serialized_values
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
else:
|
|
248
|
+
# Fallback: convert to string
|
|
249
|
+
return {'type': 'string', 'value': json.dumps(str(obj.inspect() if hasattr(obj, 'inspect') else obj))}
|
|
250
|
+
|
|
251
|
+
def _deserialize(self, data: Dict[str, str]) -> Object:
|
|
252
|
+
"""Deserialize JSON to Zexus object"""
|
|
253
|
+
obj_type = data['type']
|
|
254
|
+
value = json.loads(data['value'])
|
|
255
|
+
|
|
256
|
+
if obj_type == 'string':
|
|
257
|
+
return String(value)
|
|
258
|
+
elif obj_type == 'integer':
|
|
259
|
+
return Integer(value)
|
|
260
|
+
elif obj_type == 'float':
|
|
261
|
+
return Float(value)
|
|
262
|
+
elif obj_type == 'boolean':
|
|
263
|
+
return BooleanObj(value)
|
|
264
|
+
elif obj_type == 'null':
|
|
265
|
+
return NULL
|
|
266
|
+
elif obj_type == 'list':
|
|
267
|
+
elements = [self._deserialize(e) for e in value]
|
|
268
|
+
return List(elements)
|
|
269
|
+
elif obj_type == 'map':
|
|
270
|
+
pairs = {k: self._deserialize(v) for k, v in value.items()}
|
|
271
|
+
return Map(pairs)
|
|
272
|
+
elif obj_type == 'entity_instance':
|
|
273
|
+
# Note: This creates a basic EntityInstance without full EntityDefinition
|
|
274
|
+
# For production, you'd need to store/restore the entity definition
|
|
275
|
+
from .object import EntityDefinition
|
|
276
|
+
entity_name = value['entity_name']
|
|
277
|
+
serialized_values = value['values']
|
|
278
|
+
deserialized_values = {k: self._deserialize(v) for k, v in serialized_values.items()}
|
|
279
|
+
|
|
280
|
+
# Create minimal entity definition
|
|
281
|
+
entity_def = EntityDefinition(entity_name, [])
|
|
282
|
+
return EntityInstance(entity_def, deserialized_values)
|
|
283
|
+
else:
|
|
284
|
+
return String(str(value))
|
|
285
|
+
|
|
286
|
+
def close(self):
|
|
287
|
+
"""Close database connection"""
|
|
288
|
+
if self.conn:
|
|
289
|
+
self.conn.close()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# ===============================================
|
|
293
|
+
# PERSISTENT ENVIRONMENT MIXIN
|
|
294
|
+
# ===============================================
|
|
295
|
+
|
|
296
|
+
class PersistentEnvironmentMixin:
|
|
297
|
+
"""Mixin to add persistence to Environment class"""
|
|
298
|
+
|
|
299
|
+
def __init__(self, *args, persistence_scope: Optional[str] = None, enable_persistence: bool = False, **kwargs):
|
|
300
|
+
super().__init__(*args, **kwargs)
|
|
301
|
+
self.persistence_enabled = enable_persistence
|
|
302
|
+
self.persistence_scope = persistence_scope
|
|
303
|
+
self.persistent_storage = None
|
|
304
|
+
self.persisted_vars: Set[str] = set() # Track which vars are persisted
|
|
305
|
+
|
|
306
|
+
if enable_persistence and persistence_scope:
|
|
307
|
+
self.persistent_storage = PersistentStorage(persistence_scope)
|
|
308
|
+
self._load_persisted_vars()
|
|
309
|
+
|
|
310
|
+
def _load_persisted_vars(self):
|
|
311
|
+
"""Load persisted variables into environment"""
|
|
312
|
+
if not self.persistent_storage:
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
for var_info in self.persistent_storage.list_variables():
|
|
316
|
+
name = var_info['name']
|
|
317
|
+
value = self.persistent_storage.get(name)
|
|
318
|
+
if value is not None:
|
|
319
|
+
self.store[name] = value
|
|
320
|
+
self.persisted_vars.add(name)
|
|
321
|
+
if var_info['const']:
|
|
322
|
+
self.const_vars.add(name)
|
|
323
|
+
|
|
324
|
+
def set_persistent(self, name: str, val: Object, is_const: bool = False):
|
|
325
|
+
"""Set a variable with persistence"""
|
|
326
|
+
# Set in memory
|
|
327
|
+
if is_const:
|
|
328
|
+
self.set_const(name, val)
|
|
329
|
+
else:
|
|
330
|
+
self.set(name, val)
|
|
331
|
+
|
|
332
|
+
# Persist to storage
|
|
333
|
+
if self.persistence_enabled and self.persistent_storage:
|
|
334
|
+
self.persistent_storage.set(name, val, is_const)
|
|
335
|
+
self.persisted_vars.add(name)
|
|
336
|
+
track_allocation(val, f"persistent:{name}")
|
|
337
|
+
|
|
338
|
+
return val
|
|
339
|
+
|
|
340
|
+
def get_persistent(self, name: str) -> Optional[Object]:
|
|
341
|
+
"""Get a variable, checking persistence if not in memory"""
|
|
342
|
+
# Check memory first
|
|
343
|
+
val = self.get(name)
|
|
344
|
+
if val is not None:
|
|
345
|
+
return val
|
|
346
|
+
|
|
347
|
+
# Check persistent storage
|
|
348
|
+
if self.persistence_enabled and self.persistent_storage:
|
|
349
|
+
val = self.persistent_storage.get(name)
|
|
350
|
+
if val is not None:
|
|
351
|
+
self.store[name] = val
|
|
352
|
+
self.persisted_vars.add(name)
|
|
353
|
+
return val
|
|
354
|
+
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
def clear_persistence(self):
|
|
358
|
+
"""Clear all persisted variables"""
|
|
359
|
+
if self.persistent_storage:
|
|
360
|
+
self.persistent_storage.clear()
|
|
361
|
+
self.persisted_vars.clear()
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
# ===============================================
|
|
365
|
+
# UTILITY FUNCTIONS
|
|
366
|
+
# ===============================================
|
|
367
|
+
|
|
368
|
+
def create_persistent_scope(scope_name: str) -> PersistentStorage:
|
|
369
|
+
"""Create a new persistent storage scope"""
|
|
370
|
+
return PersistentStorage(scope_name)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def list_persistent_scopes() -> list:
|
|
374
|
+
"""List all persistent storage scopes"""
|
|
375
|
+
if not os.path.exists(PERSISTENCE_DIR):
|
|
376
|
+
return []
|
|
377
|
+
|
|
378
|
+
scopes = []
|
|
379
|
+
for filename in os.listdir(PERSISTENCE_DIR):
|
|
380
|
+
if filename.endswith('.sqlite'):
|
|
381
|
+
scope_name = filename[:-7] # Remove .sqlite extension
|
|
382
|
+
scopes.append(scope_name)
|
|
383
|
+
|
|
384
|
+
return scopes
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def delete_persistent_scope(scope_name: str):
|
|
388
|
+
"""Delete a persistent storage scope"""
|
|
389
|
+
db_path = os.path.join(PERSISTENCE_DIR, f"{scope_name}.sqlite")
|
|
390
|
+
if os.path.exists(db_path):
|
|
391
|
+
os.remove(db_path)
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin system for Zexus interpreter.
|
|
3
|
+
|
|
4
|
+
Enables third-party extensions through hooks and capability declarations.
|
|
5
|
+
Plugins are self-contained modules that extend the language without
|
|
6
|
+
modifying core functionality.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict, List, Callable, Any, Optional, Set
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class PluginMetadata:
|
|
18
|
+
"""Metadata for a plugin."""
|
|
19
|
+
name: str
|
|
20
|
+
version: str
|
|
21
|
+
author: str = ""
|
|
22
|
+
description: str = ""
|
|
23
|
+
requires: List[str] = field(default_factory=list) # Capabilities required
|
|
24
|
+
provides: List[str] = field(default_factory=list) # Capabilities provided
|
|
25
|
+
hooks: List[str] = field(default_factory=list) # Hook names registered
|
|
26
|
+
config: Dict[str, Any] = field(default_factory=dict) # Configuration schema
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_dict(cls, data: dict) -> 'PluginMetadata':
|
|
30
|
+
"""Create metadata from dictionary."""
|
|
31
|
+
return cls(
|
|
32
|
+
name=data.get('name', ''),
|
|
33
|
+
version=data.get('version', '1.0.0'),
|
|
34
|
+
author=data.get('author', ''),
|
|
35
|
+
description=data.get('description', ''),
|
|
36
|
+
requires=data.get('requires', []),
|
|
37
|
+
provides=data.get('provides', []),
|
|
38
|
+
hooks=data.get('hooks', []),
|
|
39
|
+
config=data.get('config', {})
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Hook:
|
|
45
|
+
"""A registered hook handler."""
|
|
46
|
+
name: str
|
|
47
|
+
handler: Callable
|
|
48
|
+
plugin_name: str
|
|
49
|
+
priority: int = 0 # Higher priority executes first
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PluginManager:
|
|
53
|
+
"""
|
|
54
|
+
Manages plugin loading, registration, and execution.
|
|
55
|
+
|
|
56
|
+
Handles:
|
|
57
|
+
- Plugin discovery and loading
|
|
58
|
+
- Hook registration and execution
|
|
59
|
+
- Capability tracking and validation
|
|
60
|
+
- Dependency resolution
|
|
61
|
+
- Sandbox enforcement
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self):
|
|
65
|
+
"""Initialize the plugin manager."""
|
|
66
|
+
self.loaded_plugins: Dict[str, PluginMetadata] = {}
|
|
67
|
+
self.hooks: Dict[str, List[Hook]] = defaultdict(list)
|
|
68
|
+
self.capabilities: Set[str] = set()
|
|
69
|
+
self.plugin_modules: Dict[str, Any] = {} # Loaded plugin modules
|
|
70
|
+
self.config: Dict[str, Dict[str, Any]] = {} # Per-plugin config
|
|
71
|
+
|
|
72
|
+
# Builtin capabilities always available
|
|
73
|
+
self.capabilities.add("core")
|
|
74
|
+
|
|
75
|
+
def load_plugin(self, module_path: str, config: Optional[Dict[str, Any]] = None) -> PluginMetadata:
|
|
76
|
+
"""
|
|
77
|
+
Load a plugin from a module path.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
module_path: Path to plugin module (.zx file or directory)
|
|
81
|
+
config: Configuration dictionary for the plugin
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
PluginMetadata object
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
FileNotFoundError: If plugin module not found
|
|
88
|
+
ValueError: If plugin metadata invalid or dependencies unmet
|
|
89
|
+
"""
|
|
90
|
+
if not os.path.exists(module_path):
|
|
91
|
+
raise FileNotFoundError(f"Plugin module not found: {module_path}")
|
|
92
|
+
|
|
93
|
+
# Note: Actual implementation will parse .zx module
|
|
94
|
+
# For now, this is the interface
|
|
95
|
+
metadata = PluginMetadata(
|
|
96
|
+
name="placeholder",
|
|
97
|
+
version="1.0.0"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Validate dependencies
|
|
101
|
+
for required_cap in metadata.requires:
|
|
102
|
+
if required_cap not in self.capabilities:
|
|
103
|
+
raise ValueError(f"Plugin {metadata.name} requires unavailable capability: {required_cap}")
|
|
104
|
+
|
|
105
|
+
# Store metadata and config
|
|
106
|
+
self.loaded_plugins[metadata.name] = metadata
|
|
107
|
+
if config:
|
|
108
|
+
self.config[metadata.name] = config
|
|
109
|
+
|
|
110
|
+
# Add provided capabilities
|
|
111
|
+
for cap in metadata.provides:
|
|
112
|
+
self.capabilities.add(cap)
|
|
113
|
+
|
|
114
|
+
return metadata
|
|
115
|
+
|
|
116
|
+
def register_hook(self, hook_name: str, handler: Callable,
|
|
117
|
+
plugin_name: str, priority: int = 0) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Register a hook handler.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
hook_name: Name of the hook (e.g., "pre_eval", "import_resolver")
|
|
123
|
+
handler: Callable that handles the hook
|
|
124
|
+
plugin_name: Name of plugin registering the hook
|
|
125
|
+
priority: Execution priority (higher = earlier)
|
|
126
|
+
"""
|
|
127
|
+
hook = Hook(name=hook_name, handler=handler, plugin_name=plugin_name, priority=priority)
|
|
128
|
+
self.hooks[hook_name].append(hook)
|
|
129
|
+
|
|
130
|
+
# Sort by priority (descending)
|
|
131
|
+
self.hooks[hook_name].sort(key=lambda h: h.priority, reverse=True)
|
|
132
|
+
|
|
133
|
+
def call_hooks(self, hook_name: str, *args, **kwargs) -> Any:
|
|
134
|
+
"""
|
|
135
|
+
Call all registered handlers for a hook.
|
|
136
|
+
|
|
137
|
+
Handlers are called in priority order (highest first).
|
|
138
|
+
If a handler returns non-None, stop and return that value.
|
|
139
|
+
Otherwise, return the first argument (usually the transformed input).
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
hook_name: Name of the hook to call
|
|
143
|
+
*args: Arguments to pass to handlers
|
|
144
|
+
**kwargs: Keyword arguments to pass to handlers
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Result from first non-None handler return, or first argument
|
|
148
|
+
"""
|
|
149
|
+
if hook_name not in self.hooks:
|
|
150
|
+
# No handlers registered, return first arg unchanged
|
|
151
|
+
return args[0] if args else None
|
|
152
|
+
|
|
153
|
+
result = args[0] if args else None
|
|
154
|
+
|
|
155
|
+
for hook in self.hooks[hook_name]:
|
|
156
|
+
try:
|
|
157
|
+
# Call handler with arguments
|
|
158
|
+
handler_result = hook.handler(*args, **kwargs)
|
|
159
|
+
if handler_result is not None:
|
|
160
|
+
# Handler returned a value, update result
|
|
161
|
+
result = handler_result
|
|
162
|
+
# Update args[0] for next handler
|
|
163
|
+
if args:
|
|
164
|
+
args = (result,) + args[1:]
|
|
165
|
+
except Exception as e:
|
|
166
|
+
# Log hook error but continue
|
|
167
|
+
print(f"Error in hook {hook_name} from {hook.plugin_name}: {e}",
|
|
168
|
+
file=sys.stderr)
|
|
169
|
+
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
def check_capability(self, capability: str) -> bool:
|
|
173
|
+
"""
|
|
174
|
+
Check if a capability is available.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
capability: Capability name to check
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
True if capability is available, False otherwise
|
|
181
|
+
"""
|
|
182
|
+
return capability in self.capabilities
|
|
183
|
+
|
|
184
|
+
def grant_capability(self, capability: str) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Manually grant a capability.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
capability: Capability name to grant
|
|
190
|
+
"""
|
|
191
|
+
self.capabilities.add(capability)
|
|
192
|
+
|
|
193
|
+
def has_plugin(self, plugin_name: str) -> bool:
|
|
194
|
+
"""Check if a plugin is loaded."""
|
|
195
|
+
return plugin_name in self.loaded_plugins
|
|
196
|
+
|
|
197
|
+
def get_plugin_metadata(self, plugin_name: str) -> Optional[PluginMetadata]:
|
|
198
|
+
"""Get metadata for a loaded plugin."""
|
|
199
|
+
return self.loaded_plugins.get(plugin_name)
|
|
200
|
+
|
|
201
|
+
def get_capabilities(self) -> List[str]:
|
|
202
|
+
"""Get list of available capabilities."""
|
|
203
|
+
return sorted(list(self.capabilities))
|
|
204
|
+
|
|
205
|
+
def get_loaded_plugins(self) -> List[str]:
|
|
206
|
+
"""Get list of loaded plugin names."""
|
|
207
|
+
return sorted(list(self.loaded_plugins.keys()))
|
|
208
|
+
|
|
209
|
+
def get_hooks(self, hook_name: Optional[str] = None) -> Dict[str, List[str]]:
|
|
210
|
+
"""
|
|
211
|
+
Get registered hooks.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
hook_name: If specified, return handlers for specific hook
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Dict of hook_name -> [plugin_names] or filtered results
|
|
218
|
+
"""
|
|
219
|
+
if hook_name:
|
|
220
|
+
return {hook_name: [h.plugin_name for h in self.hooks.get(hook_name, [])]}
|
|
221
|
+
|
|
222
|
+
result = {}
|
|
223
|
+
for name, hooks_list in self.hooks.items():
|
|
224
|
+
result[name] = [h.plugin_name for h in hooks_list]
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class PluginGlobalObject:
|
|
229
|
+
"""
|
|
230
|
+
The `plugin` global object exposed to plugin code.
|
|
231
|
+
|
|
232
|
+
Provides interface for:
|
|
233
|
+
- Registering hooks
|
|
234
|
+
- Declaring capabilities
|
|
235
|
+
- Accessing metadata
|
|
236
|
+
- Introspection
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
def __init__(self, manager: PluginManager, current_plugin: str):
|
|
240
|
+
"""
|
|
241
|
+
Initialize the plugin global.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
manager: PluginManager instance
|
|
245
|
+
current_plugin: Name of the currently-executing plugin
|
|
246
|
+
"""
|
|
247
|
+
self.manager = manager
|
|
248
|
+
self.current_plugin = current_plugin
|
|
249
|
+
|
|
250
|
+
def register_hook(self, hook_name: str, handler: Callable) -> None:
|
|
251
|
+
"""Register a hook handler for the current plugin."""
|
|
252
|
+
self.manager.register_hook(hook_name, handler, self.current_plugin)
|
|
253
|
+
|
|
254
|
+
def grant_capability(self, capability: str) -> None:
|
|
255
|
+
"""Declare that this plugin grants a capability."""
|
|
256
|
+
self.manager.grant_capability(capability)
|
|
257
|
+
|
|
258
|
+
def has_capability(self, capability: str) -> bool:
|
|
259
|
+
"""Check if a capability is available."""
|
|
260
|
+
return self.manager.check_capability(capability)
|
|
261
|
+
|
|
262
|
+
def get_hooks(self) -> Dict[str, List[str]]:
|
|
263
|
+
"""Get all registered hooks."""
|
|
264
|
+
return self.manager.get_hooks()
|
|
265
|
+
|
|
266
|
+
def get_capabilities(self) -> List[str]:
|
|
267
|
+
"""Get all available capabilities."""
|
|
268
|
+
return self.manager.get_capabilities()
|
|
269
|
+
|
|
270
|
+
def get_loaded_plugins(self) -> List[str]:
|
|
271
|
+
"""Get list of loaded plugins."""
|
|
272
|
+
return self.manager.get_loaded_plugins()
|
|
273
|
+
|
|
274
|
+
def metadata(self) -> PluginMetadata:
|
|
275
|
+
"""Get metadata for current plugin."""
|
|
276
|
+
return self.manager.get_plugin_metadata(self.current_plugin)
|
|
277
|
+
|
|
278
|
+
def load(self, plugin_name: str, config: Optional[Dict[str, Any]] = None) -> bool:
|
|
279
|
+
"""
|
|
280
|
+
Load another plugin by name.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
True if loaded, False if already loaded
|
|
284
|
+
"""
|
|
285
|
+
if self.manager.has_plugin(plugin_name):
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
# Would resolve plugin path and load
|
|
289
|
+
# Simplified for now
|
|
290
|
+
return True
|