dars-framework 1.2.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.
Files changed (118) hide show
  1. dars/__init__.py +0 -0
  2. dars/all.py +69 -0
  3. dars/cli/__init__.py +0 -0
  4. dars/cli/doctor/__init__.py +1 -0
  5. dars/cli/doctor/detect.py +154 -0
  6. dars/cli/doctor/doctor.py +176 -0
  7. dars/cli/doctor/installers.py +100 -0
  8. dars/cli/doctor/persist.py +62 -0
  9. dars/cli/doctor/preflight.py +33 -0
  10. dars/cli/doctor/ui.py +54 -0
  11. dars/cli/hot_reload.py +33 -0
  12. dars/cli/main.py +1107 -0
  13. dars/cli/preview.py +448 -0
  14. dars/cli/translations.py +531 -0
  15. dars/components/__init__.py +0 -0
  16. dars/components/advanced/__init__.py +8 -0
  17. dars/components/advanced/accordion.py +26 -0
  18. dars/components/advanced/card.py +33 -0
  19. dars/components/advanced/modal.py +45 -0
  20. dars/components/advanced/navbar.py +44 -0
  21. dars/components/advanced/table.py +25 -0
  22. dars/components/advanced/tabs.py +31 -0
  23. dars/components/basic/__init__.py +34 -0
  24. dars/components/basic/button.py +55 -0
  25. dars/components/basic/checkbox.py +35 -0
  26. dars/components/basic/container.py +29 -0
  27. dars/components/basic/datepicker.py +139 -0
  28. dars/components/basic/image.py +36 -0
  29. dars/components/basic/input.py +57 -0
  30. dars/components/basic/link.py +31 -0
  31. dars/components/basic/markdown.py +86 -0
  32. dars/components/basic/page.py +20 -0
  33. dars/components/basic/progressbar.py +18 -0
  34. dars/components/basic/radiobutton.py +35 -0
  35. dars/components/basic/select.py +82 -0
  36. dars/components/basic/slider.py +63 -0
  37. dars/components/basic/spinner.py +12 -0
  38. dars/components/basic/text.py +23 -0
  39. dars/components/basic/textarea.py +46 -0
  40. dars/components/basic/tooltip.py +19 -0
  41. dars/components/layout/__init__.py +0 -0
  42. dars/components/layout/anchor.py +13 -0
  43. dars/components/layout/flex.py +26 -0
  44. dars/components/layout/grid.py +45 -0
  45. dars/config.py +134 -0
  46. dars/core/__init__.py +0 -0
  47. dars/core/app.py +957 -0
  48. dars/core/component.py +284 -0
  49. dars/core/events.py +102 -0
  50. dars/core/js_bridge.py +99 -0
  51. dars/core/properties.py +127 -0
  52. dars/core/state.py +309 -0
  53. dars/dars_tests/apps_test/health_check.py +56 -0
  54. dars/dars_tests/run_tests.py +275 -0
  55. dars/dars_tests/tests/test_advanced_components.py +69 -0
  56. dars/dars_tests/tests/test_basic_components.py +88 -0
  57. dars/dars_tests/tests/test_core_and_cli.py +17 -0
  58. dars/dars_tests/tests/test_layout_components.py +58 -0
  59. dars/dars_tests/tests/test_version_check.py +21 -0
  60. dars/docs/__init__.py +0 -0
  61. dars/docs/app.md +290 -0
  62. dars/docs/cli.md +80 -0
  63. dars/docs/components.md +1679 -0
  64. dars/docs/custom_components.md +30 -0
  65. dars/docs/events.md +45 -0
  66. dars/docs/exporters.md +162 -0
  67. dars/docs/getting_started.md +79 -0
  68. dars/docs/index.md +18 -0
  69. dars/docs/scripts.md +593 -0
  70. dars/docs/state_management.md +57 -0
  71. dars/exporters/__init__.py +0 -0
  72. dars/exporters/base.py +96 -0
  73. dars/exporters/web/OLD/html_css_js_OLD4.py +1538 -0
  74. dars/exporters/web/OLD/html_css_js_old.py +1406 -0
  75. dars/exporters/web/OLD/html_css_js_old2.py +1406 -0
  76. dars/exporters/web/__init__.py +0 -0
  77. dars/exporters/web/html_css_js.py +2675 -0
  78. dars/exporters/web/vdom.py +251 -0
  79. dars/js_lib.py +206 -0
  80. dars/scripts/__init__.py +0 -0
  81. dars/scripts/dscript.py +26 -0
  82. dars/scripts/script.py +39 -0
  83. dars/security.py +195 -0
  84. dars/templates/__init__.py +0 -0
  85. dars/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  86. dars/templates/examples/README.md +4 -0
  87. dars/templates/examples/__pycache__/dynamic_event_demo.cpython-311.pyc +0 -0
  88. dars/templates/examples/advanced/Modal_Demo/advanced_modal_demo.py +275 -0
  89. dars/templates/examples/advanced/SimpleDashboard/dashboard.py +437 -0
  90. dars/templates/examples/advanced/SimpleModermWeb/modern_web_app.py +452 -0
  91. dars/templates/examples/advanced/VariousComponents/all_components_demo.py +87 -0
  92. dars/templates/examples/advanced/__init__.py +0 -0
  93. dars/templates/examples/advanced/dState/state_mods_demo.py +68 -0
  94. dars/templates/examples/basic/Forms/form_components.py +516 -0
  95. dars/templates/examples/basic/Forms/simple_form.py +379 -0
  96. dars/templates/examples/basic/HelloWorld/hello_world.py +56 -0
  97. dars/templates/examples/basic/Layouts/flex_layout_responsive.py +13 -0
  98. dars/templates/examples/basic/Layouts/grid_layout_responsive.py +12 -0
  99. dars/templates/examples/basic/Layouts/layout_multipage_demo.py +23 -0
  100. dars/templates/examples/basic/Multipage/multipage_example.py +67 -0
  101. dars/templates/examples/basic/PWA/icon-192x192.png +0 -0
  102. dars/templates/examples/basic/PWA/icon-512x512.png +0 -0
  103. dars/templates/examples/basic/PWA/pwa_custom_icons.py +33 -0
  104. dars/templates/examples/basic/__init__.py +0 -0
  105. dars/templates/examples/demo/__pycache__/complete_app.cpython-311.pyc +0 -0
  106. dars/templates/examples/demo/complete_app.py +21 -0
  107. dars/templates/examples/markdown/MarkdownTemplate/README.md +159 -0
  108. dars/templates/examples/markdown/MarkdownTemplate/markdown_template.py +21 -0
  109. dars/templates/examples/markdown/MarkdownTemplate/other_docs.md +1 -0
  110. dars/templates/examples/markdown/__init__.py +0 -0
  111. dars/templates/html/__init__.py +0 -0
  112. dars/version.py +2 -0
  113. dars_framework-1.2.3.dist-info/METADATA +15 -0
  114. dars_framework-1.2.3.dist-info/RECORD +118 -0
  115. dars_framework-1.2.3.dist-info/WHEEL +5 -0
  116. dars_framework-1.2.3.dist-info/entry_points.txt +2 -0
  117. dars_framework-1.2.3.dist-info/licenses/LICENSE +21 -0
  118. dars_framework-1.2.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,251 @@
1
+ """
2
+ VDOM utilities for Dars HTML exporter.
3
+
4
+ This module defines a minimal, modular Virtual DOM representation and a builder
5
+ that converts Dars Components (built-in or user-defined) to a serializable
6
+ VNode tree. It is intentionally patch-agnostic; it only focuses on building
7
+ an accurate snapshot that future patch systems can consume.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Callable, Dict, List, Optional
12
+
13
+ # We only use loose imports to avoid heavy coupling
14
+ try:
15
+ from dars.core.component import Component
16
+ except Exception: # pragma: no cover - defensive import
17
+ Component = Any # type: ignore
18
+
19
+
20
+ class VNode:
21
+ """A minimal serializable Virtual Node for Dars components."""
22
+
23
+ def __init__(
24
+ self,
25
+ type_name: str,
26
+ id: Optional[str],
27
+ key: Optional[str],
28
+ class_name: Optional[str],
29
+ style: Dict[str, Any],
30
+ props: Dict[str, Any],
31
+ events: Optional[Dict[str, Any]],
32
+ children: Optional[List["VNode"]] = None,
33
+ text: Optional[str] = None,
34
+ is_island: bool = False,
35
+ ) -> None:
36
+ self.type = type_name
37
+ self.id = id
38
+ self.key = key
39
+ self.class_name = class_name
40
+ self.style = style or {}
41
+ self.props = props or {}
42
+ self.events = events or None
43
+ self.children = children or []
44
+ self.text = text
45
+ self.isIsland = is_island
46
+
47
+ def to_dict(self) -> Dict[str, Any]:
48
+ d: Dict[str, Any] = {
49
+ "type": self.type,
50
+ "id": self.id,
51
+ "key": self.key,
52
+ "class": self.class_name,
53
+ "style": self.style or {},
54
+ "props": self.props or {},
55
+ "events": self.events or None,
56
+ "children": [c.to_dict() for c in (self.children or [])],
57
+ }
58
+ if self.text is not None:
59
+ d["text"] = self.text
60
+ # Siempre incluimos isIsland para que el runtime pueda tomar decisiones
61
+ d["isIsland"] = bool(self.isIsland)
62
+ return d
63
+
64
+
65
+ class VDomBuilder:
66
+ """Builds a VNode tree from a Dars Component tree.
67
+
68
+ Notes:
69
+ - Works with built-in and user-defined components alike.
70
+ - For user components that render HTML via `render(exporter)`, we still
71
+ build a structural node capturing props/events. The future patch system
72
+ may treat these as opaque islands unless more granular hooks are added.
73
+ """
74
+
75
+ def __init__(self, id_provider: Optional[Callable[[Component, str], str]] = None) -> None:
76
+ # id_provider(component, prefix) -> id string
77
+ self.id_provider = id_provider
78
+
79
+ def build(self, component: Component) -> Dict[str, Any]:
80
+ vnode = self._build_vnode(component, path=["0"]) # raíz con path estable
81
+ return vnode.to_dict()
82
+
83
+ # --- internals ---
84
+ def _safe_props(self, component: Component) -> Dict[str, Any]:
85
+ """Extract props + public attributes into a single serializable mapping.
86
+
87
+ Rules:
88
+ - Start with component.props (if present), but filter out framework-managed fields
89
+ like id, class_name, style, children, events to avoid duplication.
90
+ - Augment with other public attributes from the component instance (e.g.,
91
+ Markdown.dark_theme, Markdown.file_path, CustomComponent.title), skipping
92
+ callables, private names (prefixed with '_'), Component instances and lists of Components.
93
+ - Only include values that are JSON-serializable; otherwise, stringify as fallback.
94
+ """
95
+ import json
96
+
97
+ result: Dict[str, Any] = {}
98
+ EXCLUDE_KEYS = {
99
+ 'id', 'class_name', 'style', 'children', 'events', 'scripts', 'key',
100
+ 'props', # avoid nesting component.props inside props
101
+ 'rendered_html', # avoid transporting heavy derived HTML payloads
102
+ }
103
+
104
+ # 1) Base props from component.props
105
+ try:
106
+ base_props = getattr(component, 'props', {}) or {}
107
+ for k, v in base_props.items():
108
+ if k in EXCLUDE_KEYS:
109
+ continue
110
+ if callable(v):
111
+ continue
112
+ try:
113
+ json.dumps(v)
114
+ result[k] = v
115
+ except Exception:
116
+ result[k] = str(v)
117
+ except Exception:
118
+ pass
119
+
120
+ # 2) Additional public attributes from the instance
121
+ try:
122
+ for k, v in vars(component).items():
123
+ if k in EXCLUDE_KEYS:
124
+ continue
125
+ if k in result:
126
+ continue
127
+ if k.startswith('_'):
128
+ continue
129
+ # Skip methods/callables
130
+ if callable(v):
131
+ continue
132
+ # Skip Component instances or lists/tuples of Components
133
+ try:
134
+ if isinstance(v, Component):
135
+ continue
136
+ if isinstance(v, (list, tuple)) and any(isinstance(it, Component) for it in v):
137
+ continue
138
+ except Exception:
139
+ pass
140
+
141
+ try:
142
+ json.dumps(v)
143
+ result[k] = v
144
+ except Exception:
145
+ result[k] = str(v)
146
+ except Exception:
147
+ pass
148
+
149
+ return result
150
+
151
+ def _serialize_events(self, component: Component) -> Optional[Dict[str, Any]]:
152
+ events_payload: Dict[str, Any] = {}
153
+ try:
154
+ events = getattr(component, 'events', {}) or {}
155
+ for ev_name, handler in events.items():
156
+ code = None
157
+ try:
158
+ if hasattr(handler, 'get_code'):
159
+ code = handler.get_code()
160
+ elif isinstance(handler, dict):
161
+ code = handler.get('code') or handler.get('value')
162
+ elif isinstance(handler, str):
163
+ code = handler
164
+ else:
165
+ # fallback best-effort
166
+ code = str(handler)
167
+ except Exception:
168
+ code = None
169
+ if code:
170
+ events_payload[ev_name] = {"type": "inline", "code": code}
171
+ except Exception:
172
+ pass
173
+ return events_payload or None
174
+
175
+ def _text_value(self, component: Component) -> Optional[str]:
176
+ # Try extracting a textual value if the component has a primary text prop
177
+ try:
178
+ for cand in ('text', 'content', 'value', 'label'):
179
+ if hasattr(component, cand):
180
+ v = getattr(component, cand)
181
+ if isinstance(v, (str, int, float)):
182
+ return str(v)
183
+ except Exception:
184
+ pass
185
+ return None
186
+
187
+ def _build_vnode(self, component: Component, path: list) -> VNode:
188
+ try:
189
+ comp_type = component.__class__.__name__
190
+ except Exception:
191
+ comp_type = 'Component'
192
+
193
+ # Prefer an injected id provider to keep IDs consistent with the HTML output
194
+ comp_id = getattr(component, 'id', None)
195
+ if self.id_provider is not None:
196
+ try:
197
+ # Choose a sensible prefix based on type name (lowercase)
198
+ prefix = (component.__class__.__name__ or 'comp').lower()
199
+ comp_id = self.id_provider(component, prefix=prefix)
200
+ except Exception:
201
+ # fallback to existing id attribute (may be None)
202
+ comp_id = getattr(component, 'id', None)
203
+
204
+ # Props
205
+ safe_props = self._safe_props(component)
206
+
207
+ # Events
208
+ events_payload = self._serialize_events(component)
209
+
210
+ # Children
211
+ children_nodes: List[VNode] = []
212
+ try:
213
+ for idx, child in enumerate(getattr(component, 'children', []) or []):
214
+ if child is None:
215
+ continue
216
+ child_path = path + [str(idx)]
217
+ children_nodes.append(self._build_vnode(child, child_path))
218
+ except Exception:
219
+ children_nodes = []
220
+
221
+ # Text (optional)
222
+ text_value = self._text_value(component)
223
+
224
+ # Heurística para saber si es componente "isla" (custom)
225
+ is_island = False
226
+ try:
227
+ mod = getattr(component.__class__, '__module__', '') or ''
228
+ # Si no pertenece al paquete de componentes built-in, lo tratamos como isla
229
+ if not mod.startswith('dars.components.'):
230
+ is_island = True
231
+ except Exception:
232
+ is_island = False
233
+
234
+ # Clave estable: si no hay id ni key definidos, usamos el path del árbol
235
+ stable_key = getattr(component, 'key', None)
236
+ if not stable_key and not comp_id:
237
+ stable_key = "/".join(path)
238
+
239
+ vnode = VNode(
240
+ type_name=comp_type,
241
+ id=comp_id,
242
+ key=stable_key,
243
+ class_name=getattr(component, 'class_name', None),
244
+ style=getattr(component, 'style', {}) or {},
245
+ props=safe_props,
246
+ events=events_payload,
247
+ children=children_nodes,
248
+ text=text_value,
249
+ is_island=is_island,
250
+ )
251
+ return vnode
dars/js_lib.py ADDED
@@ -0,0 +1,206 @@
1
+ # Embedded dars.min.js content (ESM + global)
2
+ DARS_MIN_JS = """/* Dars minimal runtime: state + DOM updates (ESM + global) */
3
+ const __registry = new Map();
4
+
5
+ function $(id){ return document.getElementById(id) || document.querySelector(`[data-id="${id}"]`) || null; }
6
+
7
+ function registerState(name, cfg){
8
+ if(!name || !cfg || !cfg.id) return;
9
+ const entry = {
10
+ id: cfg.id,
11
+ states: Array.isArray(cfg.states) ? cfg.states.slice() : [],
12
+ current: 0,
13
+ isCustom: !!cfg.isCustom,
14
+ rules: (cfg.rules && typeof cfg.rules === 'object') ? cfg.rules : {},
15
+ defaultIndex: (typeof cfg.defaultIndex === 'number') ? cfg.defaultIndex : 0,
16
+ defaultValue: (cfg.hasOwnProperty('defaultValue') ? cfg.defaultValue : null),
17
+ __defaultSnapshot: null
18
+ };
19
+ __registry.set(name, entry);
20
+ try{
21
+ const el = $(entry.id);
22
+ if(el){
23
+ const attrs = {};
24
+ try{
25
+ for(const a of el.getAttributeNames()) attrs[a] = el.getAttribute(a);
26
+ }catch(_){ }
27
+ entry.__defaultSnapshot = { attrs, html: String(el.innerHTML||'') };
28
+ }
29
+ }catch(_){ }
30
+ }
31
+
32
+ function getState(name){ return __registry.get(name); }
33
+
34
+ function _restoreDefault(id, snap){
35
+ try{
36
+ const el = $(id);
37
+ if(!el || !snap) return;
38
+ // Remove any dynamic event listeners we attached previously
39
+ try{
40
+ if(el.__darsEv){
41
+ for(const t in el.__darsEv){
42
+ const fn = el.__darsEv[t];
43
+ try{ el.removeEventListener(t, fn, true); }catch(_){ }
44
+ try{ el.removeEventListener(t, fn, false); }catch(_){ }
45
+ }
46
+ el.__darsEv = {};
47
+ }
48
+ }catch(_){ }
49
+ try{
50
+ const current = el.getAttributeNames ? el.getAttributeNames() : [];
51
+ for(const n of current){ if(n !== 'id') el.removeAttribute(n); }
52
+ for(const k in snap.attrs){ if(k !== 'id') el.setAttribute(k, snap.attrs[k]); }
53
+ }catch(_){ }
54
+ try{ el.innerHTML = snap.html || ''; }catch(_){ }
55
+ }catch(_){ }
56
+ }
57
+
58
+ function _applyMods(defaultId, mods){
59
+ if(!Array.isArray(mods) || !mods.length) return;
60
+ for(const m of mods){
61
+ try{
62
+ const op = m && m.op;
63
+ if(!op) continue;
64
+ const tid = (m && m.target) ? m.target : defaultId;
65
+ const el = $(tid);
66
+ if(!el) continue;
67
+ if(op === 'inc' || op === 'dec'){
68
+ const prop = m.prop || 'text';
69
+ const by = Number(m.by || (op==='dec'?-1:1));
70
+ if(prop === 'text'){
71
+ const cur = parseFloat(el.textContent||'0') || 0;
72
+ el.textContent = String(cur + by);
73
+ } else {
74
+ const cur = parseFloat(el.getAttribute(prop)||'0') || 0;
75
+ el.setAttribute(prop, String(cur + by));
76
+ }
77
+ } else if(op === 'set'){
78
+ const attrs = m.attrs || {};
79
+ for(const k in attrs){
80
+ try{
81
+ if(k === 'text') { el.textContent = String(attrs[k]); continue; }
82
+ if(k === 'html') { el.innerHTML = String(attrs[k]); continue; }
83
+ if(k.startsWith('on_')){
84
+ const type = k.slice(3);
85
+ const v = attrs[k];
86
+ const codes = [];
87
+ const pushCode = (item)=>{
88
+ if(typeof item === 'string') codes.push(item);
89
+ else if(item && typeof item.code === 'string') codes.push(item.code);
90
+ };
91
+ if(Array.isArray(v)) v.forEach(pushCode);
92
+ else pushCode(v);
93
+ if(codes.length){
94
+ el.__darsEv = el.__darsEv || {};
95
+ if(el.__darsEv[type]){
96
+ try{ el.removeEventListener(type, el.__darsEv[type], true); }catch(_){ }
97
+ try{ el.removeEventListener(type, el.__darsEv[type], false); }catch(_){ }
98
+ }
99
+ const handler = function(ev){
100
+ try{ ev.stopImmediatePropagation(); }catch(_){ }
101
+ try{ ev.stopPropagation(); }catch(_){ }
102
+ try{ ev.preventDefault(); }catch(_){ }
103
+ try{ ev.cancelBubble = true; }catch(_){ }
104
+ let propName = 'on'+type;
105
+ let prevOn = null;
106
+ try{ prevOn = el[propName]; el[propName] = null; }catch(_){ }
107
+ try{ for(const c of codes){ try{ (0,eval)(c); }catch(_){ } } }finally{
108
+ try{ setTimeout(()=>{ try{ el[propName] = prevOn; }catch(_){ } }, 0); }catch(_){ }
109
+ }
110
+ };
111
+ try{ el.addEventListener(type, handler, { capture: true }); }catch(_){ }
112
+ el.__darsEv[type] = handler;
113
+ continue;
114
+ }
115
+ }
116
+ el.setAttribute(k, String(attrs[k]));
117
+ }catch(_){ }
118
+ }
119
+ } else if(op === 'toggleClass'){
120
+ const name = m.name || '';
121
+ const on = m.hasOwnProperty('on') ? !!m.on : null;
122
+ if(!name) continue;
123
+ if(on === null){ el.classList.toggle(name); }
124
+ else if(on){ el.classList.add(name); }
125
+ else { el.classList.remove(name); }
126
+ } else if(op === 'appendText'){
127
+ el.textContent = String(el.textContent||'') + String(m.value||'');
128
+ } else if(op === 'prependText'){
129
+ el.textContent = String(m.value||'') + String(el.textContent||'');
130
+ } else if(op === 'call'){
131
+ try{
132
+ const payload = {};
133
+ if (m.name) payload.name = String(m.name);
134
+ if (m.id) payload.id = String(m.id);
135
+ if (m.hasOwnProperty('state')) payload.state = m.state;
136
+ if (m.hasOwnProperty('goto')) payload.goto = m.goto;
137
+ if (!payload.name && !payload.id && defaultId) payload.id = String(defaultId);
138
+ setTimeout(()=>{ try{ change(payload); }catch(_){ } }, 0);
139
+ }catch(_){ }
140
+ }
141
+ }catch(_){ }
142
+ }
143
+ }
144
+
145
+ function _resolveGoto(cur, goto, statesLen){
146
+ if(goto == null) return cur;
147
+ if(typeof goto === 'number') return goto;
148
+ if(typeof goto === 'string'){
149
+ if(/^[-+]\d+$/.test(goto)){
150
+ const delta = parseInt(goto, 10);
151
+ const next = cur + delta;
152
+ if(statesLen && statesLen > 0){ return Math.max(0, Math.min(statesLen-1, next)); }
153
+ return next;
154
+ }
155
+ const n = parseInt(goto, 10);
156
+ if(!isNaN(n)) return n;
157
+ }
158
+ return cur;
159
+ }
160
+
161
+ function change(opt){
162
+ if(!opt||!opt.id) return;
163
+ if(opt.useCustomRender && typeof opt.html === 'string'){
164
+ const el = $(opt.id);
165
+ if(!el) return;
166
+ el.innerHTML = opt.html;
167
+ if(typeof window.DarsHydrate === 'function'){ try{ window.DarsHydrate(el); }catch(e){} }
168
+ return;
169
+ }
170
+
171
+ const name = opt.name || null;
172
+ const st = name ? __registry.get(name) : null;
173
+ let targetState = (typeof opt.state === 'number') ? opt.state : null;
174
+ let goto = (opt.hasOwnProperty('goto') ? opt.goto : null);
175
+ if(st){
176
+ const cur = st.current || 0;
177
+ const len = Array.isArray(st.states) ? st.states.length : 0;
178
+ if(goto !== null){ targetState = _resolveGoto(cur, goto, len); }
179
+ if(targetState === null){ targetState = cur; }
180
+ st.current = targetState;
181
+ const rules = st.rules && st.rules[String(targetState)];
182
+ if(targetState === 0){
183
+ _restoreDefault(st.id, st.__defaultSnapshot);
184
+ if(rules){ try{ console.error('[Dars] Default state (index 0) is immutable. Rules for state 0 are ignored.'); }catch(_){ } }
185
+ } else if(rules){
186
+ if(Array.isArray(rules.mods)){ _applyMods(st.id, rules.mods); }
187
+ if(rules.hasOwnProperty('goto')){
188
+ const nxt = _resolveGoto(st.current, rules.goto, len);
189
+ if(nxt !== st.current){ st.current = nxt; }
190
+ }
191
+ }
192
+ }
193
+
194
+ try{
195
+ const el = $(opt.id);
196
+ if(!el) return;
197
+ const ev = new CustomEvent('dars:state', { detail: { id: opt.id, state: targetState } });
198
+ el.dispatchEvent(ev);
199
+ }catch(e){ }
200
+ }
201
+
202
+ const Dars = { registerState, getState, change, $ };
203
+ try { window.Dars = window.Dars || Dars; } catch(_) {}
204
+ export { registerState, getState, change, $ };
205
+ export default Dars;
206
+ """
File without changes
@@ -0,0 +1,26 @@
1
+ from typing import Optional
2
+ from .script import Script
3
+
4
+ class dScript(Script):
5
+ """
6
+ Script that can be defined as inline (JS code in string) or as a reference to an external file.
7
+ Only one of the two must be present.
8
+ """
9
+ def __init__(self, code: Optional[str] = None, file_path: Optional[str] = None, target_language: str = "javascript", module: bool = False):
10
+ super().__init__(target_language, module=module)
11
+ if (code is None and file_path is None) or (code is not None and file_path is not None):
12
+ raise ValueError("You have to specify only one: 'code' (inline) or 'file_path' (external), but not both.")
13
+ self.code = code
14
+ self.file_path = file_path
15
+
16
+ def get_code(self) -> str:
17
+ if self.code is not None:
18
+ return self.code
19
+ elif self.file_path is not None:
20
+ try:
21
+ with open(self.file_path, 'r') as f:
22
+ return f.read()
23
+ except FileNotFoundError:
24
+ raise FileNotFoundError(f"The script file was not found: {self.file_path}")
25
+ else:
26
+ raise ValueError("No code or file path defined for this dScript.")
dars/scripts/script.py ADDED
@@ -0,0 +1,39 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+
4
+ class Script(ABC):
5
+ """Base class for script definitions"""
6
+ def __init__(self, target_language: str = "javascript", module: bool = False):
7
+ if target_language not in ["javascript", "typescript"]:
8
+ raise ValueError("The target language must be 'javascript' or 'typescript'")
9
+ self.target_language = target_language
10
+ self.module = module
11
+
12
+ @abstractmethod
13
+ def get_code(self) -> str:
14
+ """Returns the script code in the target language"""
15
+ pass
16
+
17
+ class InlineScript(Script):
18
+ """Script defined directly in Python code"""
19
+ def __init__(self, code: str, target_language: str = "javascript", module: bool = False):
20
+ super().__init__(target_language, module=module)
21
+ self.code = code
22
+
23
+ def get_code(self) -> str:
24
+ return self.code
25
+
26
+ class FileScript(Script):
27
+ """Script loaded from an external file"""
28
+ def __init__(self, file_path: str, target_language: str = "javascript", module: bool = False):
29
+ super().__init__(target_language, module=module)
30
+ self.file_path = file_path
31
+
32
+ def get_code(self) -> str:
33
+ try:
34
+ with open(self.file_path, 'r') as f:
35
+ return f.read()
36
+ except FileNotFoundError:
37
+ raise FileNotFoundError(f"The script file was not found: {self.file_path}")
38
+
39
+