zexus 1.6.2 → 1.6.4
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/README.md +165 -5
- package/package.json +1 -1
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/access_control_system/__init__.py +38 -0
- package/src/zexus/access_control_system/access_control.py +237 -0
- package/src/zexus/cli/main.py +1 -1
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/debug_sanitizer.py +250 -0
- package/src/zexus/error_reporter.py +22 -2
- package/src/zexus/evaluator/core.py +17 -21
- package/src/zexus/evaluator/expressions.py +116 -57
- package/src/zexus/evaluator/functions.py +613 -170
- package/src/zexus/evaluator/resource_limiter.py +291 -0
- package/src/zexus/evaluator/statements.py +47 -12
- package/src/zexus/evaluator/utils.py +12 -6
- package/src/zexus/lsp/server.py +1 -1
- package/src/zexus/object.py +21 -2
- package/src/zexus/parser/parser.py +56 -4
- package/src/zexus/parser/strategy_context.py +83 -7
- package/src/zexus/parser/strategy_structural.py +12 -4
- package/src/zexus/persistence.py +105 -6
- package/src/zexus/security.py +43 -25
- package/src/zexus/security_enforcement.py +237 -0
- package/src/zexus/stdlib/fs.py +120 -22
- package/src/zexus/zexus_ast.py +3 -2
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +499 -13
- package/src/zexus.egg-info/SOURCES.txt +258 -152
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# src/zexus/security_enforcement.py
|
|
2
|
+
"""
|
|
3
|
+
Security enforcement for Zexus language.
|
|
4
|
+
|
|
5
|
+
This module enforces mandatory sanitization in sensitive contexts.
|
|
6
|
+
It's NOT optional - security is built into the language.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .object import String, EvaluationError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SecurityEnforcementError(Exception):
|
|
13
|
+
"""Raised when unsanitized input is used in sensitive context"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SensitiveContext:
|
|
18
|
+
"""Defines sensitive contexts that require sanitization"""
|
|
19
|
+
|
|
20
|
+
SQL = 'sql'
|
|
21
|
+
HTML = 'html'
|
|
22
|
+
URL = 'url'
|
|
23
|
+
SHELL = 'shell'
|
|
24
|
+
|
|
25
|
+
# Patterns that indicate SQL context
|
|
26
|
+
SQL_PATTERNS = [
|
|
27
|
+
'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE',
|
|
28
|
+
'ALTER', 'FROM', 'WHERE', 'JOIN', 'UNION'
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# Patterns that indicate HTML context
|
|
32
|
+
HTML_PATTERNS = [
|
|
33
|
+
'<html', '<div', '<span', '<script', '<body', '<head',
|
|
34
|
+
'innerHTML', 'outerHTML'
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
# Patterns that indicate URL context
|
|
38
|
+
URL_PATTERNS = [
|
|
39
|
+
'http://', 'https://', 'ftp://', '?', '&', 'url=', 'redirect='
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# Patterns that indicate shell context
|
|
43
|
+
SHELL_PATTERNS = [
|
|
44
|
+
'exec', 'system', 'shell', 'bash', 'sh', 'cmd', 'powershell'
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def detect_sensitive_context(string_value):
|
|
49
|
+
"""
|
|
50
|
+
Detect if a string is being used in a sensitive context.
|
|
51
|
+
|
|
52
|
+
Returns the context type (sql, html, url, shell) or None.
|
|
53
|
+
|
|
54
|
+
IMPORTANT: This now uses more sophisticated pattern matching to reduce
|
|
55
|
+
false positives. We look for actual dangerous patterns, not just keywords.
|
|
56
|
+
"""
|
|
57
|
+
if not isinstance(string_value, str):
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
upper_value = string_value.upper()
|
|
61
|
+
|
|
62
|
+
# Check for SQL context - require actual SQL query patterns, not just keywords
|
|
63
|
+
# Look for patterns like "SELECT ... FROM", "WHERE ... =", etc.
|
|
64
|
+
sql_query_indicators = [
|
|
65
|
+
('SELECT', 'FROM'), # SELECT must be followed by FROM
|
|
66
|
+
('INSERT', 'INTO'), # INSERT must be followed by INTO
|
|
67
|
+
('UPDATE', 'SET'), # UPDATE must be followed by SET
|
|
68
|
+
('DELETE', 'FROM'), # DELETE must be followed by FROM
|
|
69
|
+
('DROP', 'TABLE'), # DROP must be followed by TABLE
|
|
70
|
+
('CREATE', 'TABLE'), # CREATE must be followed by TABLE
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
for keyword1, keyword2 in sql_query_indicators:
|
|
74
|
+
if keyword1 in upper_value and keyword2 in upper_value:
|
|
75
|
+
# Found a real SQL query pattern
|
|
76
|
+
return SensitiveContext.SQL
|
|
77
|
+
|
|
78
|
+
# Single keywords alone are not enough - they could be normal text
|
|
79
|
+
# Only trigger if we see SQL-like syntax patterns
|
|
80
|
+
if ' WHERE ' in upper_value and ('=' in string_value or 'LIKE' in upper_value):
|
|
81
|
+
return SensitiveContext.SQL
|
|
82
|
+
|
|
83
|
+
# Check for HTML context - require actual HTML tags, not just keywords
|
|
84
|
+
for pattern in SensitiveContext.HTML_PATTERNS:
|
|
85
|
+
if pattern.lower() in string_value.lower():
|
|
86
|
+
# Check if it's actually a tag (starts with <)
|
|
87
|
+
if pattern.startswith('<') or 'innerHTML' in string_value or 'outerHTML' in string_value:
|
|
88
|
+
return SensitiveContext.HTML
|
|
89
|
+
|
|
90
|
+
# Check for URL context - require actual URL schemes or injection patterns
|
|
91
|
+
url_indicators = ['http://', 'https://', 'ftp://']
|
|
92
|
+
injection_indicators = ['url=', 'redirect=', 'goto=', 'next=']
|
|
93
|
+
|
|
94
|
+
has_url_scheme = any(indicator in string_value.lower() for indicator in url_indicators)
|
|
95
|
+
has_injection_param = any(indicator in string_value.lower() for indicator in injection_indicators)
|
|
96
|
+
|
|
97
|
+
if has_url_scheme or (has_injection_param and ('?' in string_value or '&' in string_value)):
|
|
98
|
+
return SensitiveContext.URL
|
|
99
|
+
|
|
100
|
+
# Check for shell context - require actual command execution patterns
|
|
101
|
+
shell_execution_funcs = ['exec(', 'system(', 'shell(', 'bash ', 'sh ', 'cmd ', 'powershell ']
|
|
102
|
+
if any(pattern in string_value.lower() for pattern in shell_execution_funcs):
|
|
103
|
+
return SensitiveContext.SHELL
|
|
104
|
+
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def enforce_sanitization(string_obj, operation_context=None):
|
|
109
|
+
"""
|
|
110
|
+
Enforce sanitization requirement for String objects in sensitive contexts.
|
|
111
|
+
|
|
112
|
+
This is ALWAYS enforced - not optional. Security is built into the language.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
string_obj: The String object to check
|
|
116
|
+
operation_context: Optional explicit context (sql, html, url, shell)
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
EvaluationError: If unsanitized input is used in sensitive context
|
|
120
|
+
"""
|
|
121
|
+
if not isinstance(string_obj, String):
|
|
122
|
+
return # Not a string, nothing to enforce
|
|
123
|
+
|
|
124
|
+
# If string is trusted (literal), no enforcement needed
|
|
125
|
+
if string_obj.is_trusted:
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
# Detect context if not explicitly provided
|
|
129
|
+
if operation_context is None:
|
|
130
|
+
operation_context = detect_sensitive_context(string_obj.value)
|
|
131
|
+
|
|
132
|
+
# If no sensitive context detected, allow
|
|
133
|
+
if operation_context is None:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
# Check if string is sanitized for this context
|
|
137
|
+
if not string_obj.is_safe_for(operation_context):
|
|
138
|
+
raise_sanitization_error(string_obj, operation_context)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def raise_sanitization_error(string_obj, context):
|
|
142
|
+
"""
|
|
143
|
+
Raise a clear, helpful error message for unsanitized input.
|
|
144
|
+
|
|
145
|
+
The error message guides developers to use the sanitize keyword.
|
|
146
|
+
"""
|
|
147
|
+
context_name = context.upper()
|
|
148
|
+
|
|
149
|
+
# Create helpful error message
|
|
150
|
+
error_msg = f"""
|
|
151
|
+
🔒 SECURITY ERROR: Unsanitized input used in {context_name} context
|
|
152
|
+
|
|
153
|
+
The string value appears to be used in a {context_name} operation, but it has not been sanitized.
|
|
154
|
+
This could lead to {get_vulnerability_name(context)} vulnerabilities.
|
|
155
|
+
|
|
156
|
+
To fix this, sanitize the input before use:
|
|
157
|
+
|
|
158
|
+
sanitize your_variable as {context}
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
|
|
162
|
+
❌ UNSAFE:
|
|
163
|
+
query = "SELECT * FROM users WHERE name = '" + user_input + "'"
|
|
164
|
+
|
|
165
|
+
✅ SAFE:
|
|
166
|
+
sanitize user_input as {context}
|
|
167
|
+
query = "SELECT * FROM users WHERE name = '" + user_input + "'"
|
|
168
|
+
|
|
169
|
+
Security is mandatory in Zexus - this protection cannot be disabled.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
raise SecurityEnforcementError(error_msg.strip())
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_vulnerability_name(context):
|
|
176
|
+
"""Get the vulnerability name for a given context"""
|
|
177
|
+
vuln_map = {
|
|
178
|
+
SensitiveContext.SQL: "SQL Injection",
|
|
179
|
+
SensitiveContext.HTML: "Cross-Site Scripting (XSS)",
|
|
180
|
+
SensitiveContext.URL: "URL Injection / Open Redirect",
|
|
181
|
+
SensitiveContext.SHELL: "Command Injection"
|
|
182
|
+
}
|
|
183
|
+
return vuln_map.get(context, "Injection")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def check_string_concatenation(left, right):
|
|
187
|
+
"""
|
|
188
|
+
Check string concatenation for security issues.
|
|
189
|
+
|
|
190
|
+
When concatenating strings, if the result would be used in a sensitive
|
|
191
|
+
context, both operands must be sanitized or trusted.
|
|
192
|
+
|
|
193
|
+
Improvements:
|
|
194
|
+
- If BOTH operands are trusted (literals), the result is safe
|
|
195
|
+
- Only check context on the final combined result
|
|
196
|
+
- Reduce false positives from normal text operations
|
|
197
|
+
"""
|
|
198
|
+
# If either operand is a String object, check sanitization
|
|
199
|
+
left_is_string = isinstance(left, String)
|
|
200
|
+
right_is_string = isinstance(right, String)
|
|
201
|
+
|
|
202
|
+
if not (left_is_string or right_is_string):
|
|
203
|
+
return # Not string concatenation
|
|
204
|
+
|
|
205
|
+
# OPTIMIZATION: If both are trusted literals, the concatenation is safe
|
|
206
|
+
if (left_is_string and left.is_trusted) and (right_is_string and right.is_trusted):
|
|
207
|
+
return # Both sides are literals - safe!
|
|
208
|
+
|
|
209
|
+
# Get the concatenated value for context detection
|
|
210
|
+
left_val = left.value if left_is_string else str(left.inspect() if hasattr(left, 'inspect') else left)
|
|
211
|
+
right_val = right.value if right_is_string else str(right.inspect() if hasattr(right, 'inspect') else right)
|
|
212
|
+
combined = left_val + right_val
|
|
213
|
+
|
|
214
|
+
# Detect if the combined string is in a sensitive context
|
|
215
|
+
context = detect_sensitive_context(combined)
|
|
216
|
+
|
|
217
|
+
if context is None:
|
|
218
|
+
return # No sensitive context detected
|
|
219
|
+
|
|
220
|
+
# Check if both operands are safe for this context
|
|
221
|
+
# NOTE: We only enforce if the string is NOT trusted AND NOT sanitized
|
|
222
|
+
if left_is_string and not left.is_trusted and not left.is_safe_for(context):
|
|
223
|
+
enforce_sanitization(left, context)
|
|
224
|
+
|
|
225
|
+
if right_is_string and not right.is_trusted and not right.is_safe_for(context):
|
|
226
|
+
enforce_sanitization(right, context)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def mark_as_trusted(string_obj):
|
|
230
|
+
"""
|
|
231
|
+
Mark a string as trusted (from literal, not external input).
|
|
232
|
+
|
|
233
|
+
This should be called when creating String objects from literals.
|
|
234
|
+
"""
|
|
235
|
+
if isinstance(string_obj, String):
|
|
236
|
+
string_obj.is_trusted = True
|
|
237
|
+
return string_obj
|
package/src/zexus/stdlib/fs.py
CHANGED
|
@@ -4,76 +4,173 @@ import os
|
|
|
4
4
|
import shutil
|
|
5
5
|
import glob as glob_module
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import List, Dict, Any
|
|
7
|
+
from typing import List, Dict, Any, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PathTraversalError(Exception):
|
|
11
|
+
"""Raised when path traversal attack is detected."""
|
|
12
|
+
pass
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
class FileSystemModule:
|
|
11
|
-
"""Provides file system operations."""
|
|
16
|
+
"""Provides file system operations with path traversal protection."""
|
|
17
|
+
|
|
18
|
+
# Allowed base directories for file operations
|
|
19
|
+
# If None, uses current working directory
|
|
20
|
+
_allowed_base_dirs: Optional[List[str]] = None
|
|
21
|
+
_strict_mode: bool = True # Enable path validation by default
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def configure_security(cls, allowed_dirs: Optional[List[str]] = None, strict: bool = True):
|
|
25
|
+
"""
|
|
26
|
+
Configure file system security settings.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
allowed_dirs: List of allowed base directories. None = use CWD only.
|
|
30
|
+
strict: Enable strict path validation
|
|
31
|
+
"""
|
|
32
|
+
cls._allowed_base_dirs = allowed_dirs
|
|
33
|
+
cls._strict_mode = strict
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def _validate_path(cls, path: str, operation: str = "access") -> str:
|
|
37
|
+
"""
|
|
38
|
+
Validate path to prevent traversal attacks.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
path: User-provided path
|
|
42
|
+
operation: Type of operation (for error messages)
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Validated absolute path
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
PathTraversalError: If path traversal detected
|
|
49
|
+
"""
|
|
50
|
+
if not cls._strict_mode:
|
|
51
|
+
return path
|
|
52
|
+
|
|
53
|
+
# Convert to absolute path
|
|
54
|
+
abs_path = Path(path).resolve()
|
|
55
|
+
|
|
56
|
+
# Check for common traversal patterns
|
|
57
|
+
path_str = str(path)
|
|
58
|
+
if '..' in path_str:
|
|
59
|
+
# Allow .. only if it doesn't escape allowed directories
|
|
60
|
+
pass # Will be checked below
|
|
61
|
+
|
|
62
|
+
# Determine allowed base directories
|
|
63
|
+
if cls._allowed_base_dirs is None:
|
|
64
|
+
# Default: only allow access within CWD
|
|
65
|
+
allowed_bases = [Path.cwd().resolve()]
|
|
66
|
+
else:
|
|
67
|
+
allowed_bases = [Path(d).resolve() for d in cls._allowed_base_dirs]
|
|
68
|
+
|
|
69
|
+
# Check if resolved path is within allowed directories
|
|
70
|
+
is_allowed = False
|
|
71
|
+
for base in allowed_bases:
|
|
72
|
+
try:
|
|
73
|
+
abs_path.relative_to(base)
|
|
74
|
+
is_allowed = True
|
|
75
|
+
break
|
|
76
|
+
except ValueError:
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
if not is_allowed:
|
|
80
|
+
raise PathTraversalError(
|
|
81
|
+
f"Path traversal detected: '{path}' resolves to '{abs_path}' "
|
|
82
|
+
f"which is outside allowed directories. "
|
|
83
|
+
f"Allowed: {[str(b) for b in allowed_bases]}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return str(abs_path)
|
|
12
87
|
|
|
13
88
|
@staticmethod
|
|
14
89
|
def read_file(path: str, encoding: str = 'utf-8') -> str:
|
|
15
90
|
"""Read entire file as text."""
|
|
16
|
-
|
|
91
|
+
validated_path = FileSystemModule._validate_path(path, "read")
|
|
92
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
17
93
|
return f.read()
|
|
18
94
|
|
|
19
95
|
@staticmethod
|
|
20
96
|
def write_file(path: str, content: str, encoding: str = 'utf-8') -> None:
|
|
21
97
|
"""Write text to file."""
|
|
98
|
+
validated_path = FileSystemModule._validate_path(path, "write")
|
|
22
99
|
# Create parent directory if it doesn't exist
|
|
23
|
-
Path(
|
|
24
|
-
with open(
|
|
100
|
+
Path(validated_path).parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
with open(validated_path, 'w', encoding=encoding) as f:
|
|
25
102
|
f.write(content)
|
|
26
103
|
|
|
27
104
|
@staticmethod
|
|
28
105
|
def append_file(path: str, content: str, encoding: str = 'utf-8') -> None:
|
|
29
106
|
"""Append text to file."""
|
|
107
|
+
validated_path = FileSystemModule._validate_path(path, "append")
|
|
30
108
|
# Create parent directory if it doesn't exist (for consistency with write_file)
|
|
31
|
-
Path(
|
|
32
|
-
with open(
|
|
109
|
+
Path(validated_path).parent.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
with open(validated_path, 'a', encoding=encoding) as f:
|
|
33
111
|
f.write(content)
|
|
34
112
|
|
|
35
113
|
@staticmethod
|
|
36
114
|
def read_binary(path: str) -> bytes:
|
|
37
115
|
"""Read file as binary."""
|
|
38
|
-
|
|
116
|
+
validated_path = FileSystemModule._validate_path(path, "read_binary")
|
|
117
|
+
with open(validated_path, 'rb') as f:
|
|
39
118
|
return f.read()
|
|
40
119
|
|
|
41
120
|
@staticmethod
|
|
42
121
|
def write_binary(path: str, data: bytes) -> None:
|
|
43
122
|
"""Write binary data to file."""
|
|
44
|
-
|
|
45
|
-
|
|
123
|
+
validated_path = FileSystemModule._validate_path(path, "write_binary")
|
|
124
|
+
Path(validated_path).parent.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
with open(validated_path, 'wb') as f:
|
|
46
126
|
f.write(data)
|
|
47
127
|
|
|
48
128
|
@staticmethod
|
|
49
129
|
def exists(path: str) -> bool:
|
|
50
130
|
"""Check if file or directory exists."""
|
|
51
|
-
|
|
131
|
+
try:
|
|
132
|
+
validated_path = FileSystemModule._validate_path(path, "exists")
|
|
133
|
+
return os.path.exists(validated_path)
|
|
134
|
+
except PathTraversalError:
|
|
135
|
+
return False # Return False for invalid paths instead of error
|
|
52
136
|
|
|
53
137
|
@staticmethod
|
|
54
138
|
def is_file(path: str) -> bool:
|
|
55
139
|
"""Check if path is a file."""
|
|
56
|
-
|
|
140
|
+
try:
|
|
141
|
+
validated_path = FileSystemModule._validate_path(path, "is_file")
|
|
142
|
+
return os.path.isfile(validated_path)
|
|
143
|
+
except PathTraversalError:
|
|
144
|
+
return False
|
|
57
145
|
|
|
58
146
|
@staticmethod
|
|
59
147
|
def is_dir(path: str) -> bool:
|
|
60
148
|
"""Check if path is a directory."""
|
|
61
|
-
|
|
149
|
+
try:
|
|
150
|
+
validated_path = FileSystemModule._validate_path(path, "is_dir")
|
|
151
|
+
return os.path.isdir(validated_path)
|
|
152
|
+
except PathTraversalError:
|
|
153
|
+
return False
|
|
62
154
|
|
|
63
155
|
@staticmethod
|
|
64
156
|
def mkdir(path: str, parents: bool = True) -> None:
|
|
65
|
-
|
|
66
|
-
|
|
157
|
+
validated_path = FileSystemModule._validate_path(path, "remove")
|
|
158
|
+
os.remove(validated_path)
|
|
67
159
|
|
|
68
160
|
@staticmethod
|
|
69
|
-
def
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
os.rmdir(path)
|
|
161
|
+
def rename(old_path: str, new_path: str) -> None:
|
|
162
|
+
"""Rename/move file or directory."""
|
|
163
|
+
validated_old = FileSystemModule._validate_path(old_path, "rename_source")
|
|
164
|
+
validated_new = FileSystemModule._validate_path(new_path, "rename_dest")
|
|
165
|
+
os.rename(validated_old, validated_new)
|
|
75
166
|
|
|
76
167
|
@staticmethod
|
|
168
|
+
def copy_file(src: str, dst: str) -> None:
|
|
169
|
+
"""Copy file."""
|
|
170
|
+
validated_src = FileSystemModule._validate_path(src, "copy_source")
|
|
171
|
+
validated_dst = FileSystemModule._validate_path(dst, "copy_dest")
|
|
172
|
+
shutil.copy2(validated_src, validated_
|
|
173
|
+
@staticmethod
|
|
77
174
|
def remove(path: str) -> None:
|
|
78
175
|
"""Remove file."""
|
|
79
176
|
os.remove(path)
|
|
@@ -91,7 +188,8 @@ class FileSystemModule:
|
|
|
91
188
|
@staticmethod
|
|
92
189
|
def copy_dir(src: str, dst: str) -> None:
|
|
93
190
|
"""Copy directory recursively."""
|
|
94
|
-
|
|
191
|
+
validated_path = FileSystemModule._validate_path(path, "list_dir")
|
|
192
|
+
return os.listdir(validated_c, dst)
|
|
95
193
|
|
|
96
194
|
@staticmethod
|
|
97
195
|
def list_dir(path: str = '.') -> List[str]:
|
package/src/zexus/zexus_ast.py
CHANGED
|
@@ -592,12 +592,13 @@ class LiteralPattern:
|
|
|
592
592
|
return f"LiteralPattern({self.value})"
|
|
593
593
|
|
|
594
594
|
class PropertyAccessExpression(Expression):
|
|
595
|
-
def __init__(self, object, property):
|
|
595
|
+
def __init__(self, object, property, computed=False):
|
|
596
596
|
self.object = object
|
|
597
597
|
self.property = property
|
|
598
|
+
self.computed = computed # True for obj[expr], False for obj.prop
|
|
598
599
|
|
|
599
600
|
def __repr__(self):
|
|
600
|
-
return f"PropertyAccessExpression(object={self.object}, property={self.property})"
|
|
601
|
+
return f"PropertyAccessExpression(object={self.object}, property={self.property}, computed={self.computed})"
|
|
601
602
|
|
|
602
603
|
class AssignmentExpression(Expression):
|
|
603
604
|
def __init__(self, name, value):
|
|
@@ -23,7 +23,7 @@ class PackageManager:
|
|
|
23
23
|
self.installer = PackageInstaller(self.zpm_dir)
|
|
24
24
|
self.publisher = PackagePublisher(self.registry)
|
|
25
25
|
|
|
26
|
-
def init(self, name: str = None, version: str = "1.6.
|
|
26
|
+
def init(self, name: str = None, version: str = "1.6.4") -> Dict:
|
|
27
27
|
"""Initialize a new Zexus project with package.json"""
|
|
28
28
|
if self.config_file.exists():
|
|
29
29
|
print(f"⚠️ {self.config_file} already exists")
|