structui 0.2.0__tar.gz → 0.3.0__tar.gz

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 (37) hide show
  1. {structui-0.2.0 → structui-0.3.0}/PKG-INFO +76 -62
  2. {structui-0.2.0 → structui-0.3.0}/README.md +14 -0
  3. {structui-0.2.0 → structui-0.3.0}/pyproject.toml +1 -1
  4. {structui-0.2.0 → structui-0.3.0}/setup.cfg +4 -4
  5. {structui-0.2.0 → structui-0.3.0}/src/structui/app.py +1 -1
  6. {structui-0.2.0 → structui-0.3.0}/src/structui/cli.py +1 -1
  7. {structui-0.2.0 → structui-0.3.0}/src/structui/file_picker.py +7 -2
  8. {structui-0.2.0 → structui-0.3.0}/src/structui/parser.py +1 -1
  9. {structui-0.2.0 → structui-0.3.0}/src/structui/schema.py +7 -7
  10. {structui-0.2.0 → structui-0.3.0}/src/structui/state.py +1 -1
  11. {structui-0.2.0 → structui-0.3.0}/src/structui/ui.py +69 -18
  12. {structui-0.2.0 → structui-0.3.0}/src/structui/xml_parser.py +1 -1
  13. {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/PKG-INFO +76 -62
  14. {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/SOURCES.txt +4 -1
  15. {structui-0.2.0 → structui-0.3.0}/tests/test_app.py +23 -33
  16. structui-0.3.0/tests/test_cli.py +44 -0
  17. structui-0.3.0/tests/test_coverage_boost.py +292 -0
  18. {structui-0.2.0 → structui-0.3.0}/tests/test_file_picker.py +23 -0
  19. {structui-0.2.0 → structui-0.3.0}/tests/test_schema.py +1 -1
  20. {structui-0.2.0 → structui-0.3.0}/tests/test_ui.py +25 -9
  21. structui-0.3.0/tests/test_ui_blur.py +227 -0
  22. structui-0.3.0/tests/test_ui_coverage.py +160 -0
  23. {structui-0.2.0 → structui-0.3.0}/tests/test_ui_extra.py +57 -0
  24. structui-0.3.0/tests/test_ui_extra2.py +66 -0
  25. structui-0.3.0/tests/test_ui_final.py +506 -0
  26. structui-0.2.0/tests/test_cli.py +0 -22
  27. structui-0.2.0/tests/test_cli_entry.py +0 -18
  28. structui-0.2.0/tests/test_coverage_boost.py +0 -499
  29. {structui-0.2.0 → structui-0.3.0}/src/structui/__init__.py +0 -0
  30. {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/dependency_links.txt +0 -0
  31. {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/entry_points.txt +0 -0
  32. {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/requires.txt +0 -0
  33. {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/top_level.txt +0 -0
  34. {structui-0.2.0 → structui-0.3.0}/tests/test_final_gap.py +0 -0
  35. {structui-0.2.0 → structui-0.3.0}/tests/test_parser.py +0 -0
  36. {structui-0.2.0 → structui-0.3.0}/tests/test_state.py +0 -0
  37. {structui-0.2.0 → structui-0.3.0}/tests/test_xml_parser.py +0 -0
@@ -1,62 +1,76 @@
1
- Metadata-Version: 2.4
2
- Name: structui
3
- Version: 0.2.0
4
- Summary: A format-agnostic, schema-driven, hierarchical configuration UI.
5
- Author: structui contributors
6
- License: MIT
7
- Classifier: Development Status :: 4 - Beta
8
- Classifier: Environment :: Web Environment
9
- Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Requires-Python: >=3.9
17
- Description-Content-Type: text/markdown
18
- Requires-Dist: nicegui>=1.4.0
19
- Requires-Dist: pyyaml>=6.0.1
20
- Requires-Dist: pywin32>=300; sys_platform == "win32"
21
-
22
- # StructUI
23
-
24
- [![PyPI version](https://img.shields.io/pypi/v/structui.svg)](https://pypi.org/project/structui/)
25
- [![CI](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml/badge.svg)](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
26
- [![CD](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml/badge.svg)](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
27
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
-
29
- StructUI is a format-agnostic, schema-driven, hierarchical configuration UI engine built in Python. Designed as a flexible architectural backbone, it parses standard configuration files (YAML, JSON, CSV, XML) and dynamically generates a live web-based property editor based on constraints and metadata defined in a schema file.
30
-
31
- The architecture is explicitly decoupled, making it readily extensible to strict domain-specific specifications (e.g., AUTOSAR configurators) and agent-driven programmatic workflows.
32
-
33
- ## Features
34
-
35
- - **Pillar A: Format-Agnostic Parsers:** cleanly separate UI generation from underlying data formats. Out-of-the-box support for YAML, JSON, and XML (including schema-driven arrays and attributes), with abstract base classes extensible to CSV and others.
36
- - **Pillar B: Hierarchical UI:** Dynamic tree-based rendering with full support for multidimensional containers, dynamic polymorphic list additions, and node mapping. Powered natively by NiceGUI.
37
- - **Pillar C: Data Validity:** Enforces schema metadata strictly at the UI layer. Missing fields gracefully populate via defaults, required flags trigger locking, and nested typings are continuously evaluated.
38
- - **Pillar D: Extensibility & Programmatic Control:** Decomposed core logic (App, Parser, State, Schema, UI) allowing external tools and wrappers (e.g. CLI, Agent Workflows) to invoke the editor or inject properties safely.
39
-
40
- ## Installation
41
-
42
- ### From PyPI (recommended)
43
-
44
- ```bash
45
- pip install structui
46
- ```
47
-
48
- ### From Source (development)
49
-
50
- ```bash
51
- git clone https://github.com/MoSaeedHammad/structui.git
52
- cd structui
53
- pip install -e .
54
- ```
55
-
56
- ## Quick Start
57
-
58
- Launch the editor in the current directory against your local configuration files by simply typing:
59
-
60
- ```bash
61
- structui --dir . --schema .structui_schema.yaml --port 8080
62
- ```
1
+ Metadata-Version: 2.4
2
+ Name: structui
3
+ Version: 0.3.0
4
+ Summary: A format-agnostic, schema-driven, hierarchical configuration UI.
5
+ Author: structui contributors
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Web Environment
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: nicegui>=1.4.0
19
+ Requires-Dist: pyyaml>=6.0.1
20
+ Requires-Dist: pywin32>=300; sys_platform == "win32"
21
+
22
+ # StructUI
23
+
24
+ [![PyPI version](https://img.shields.io/pypi/v/structui.svg)](https://pypi.org/project/structui/)
25
+ [![CI](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml/badge.svg)](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
26
+ [![CD](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml/badge.svg)](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
+
29
+ StructUI is a format-agnostic, schema-driven, hierarchical configuration UI engine built in Python. Designed as a flexible architectural backbone, it parses standard configuration files (YAML, JSON, CSV, XML) and dynamically generates a live web-based property editor based on constraints and metadata defined in a schema file.
30
+
31
+ The architecture is explicitly decoupled, making it readily extensible to strict domain-specific specifications (e.g., AUTOSAR configurators) and agent-driven programmatic workflows.
32
+
33
+ ## Features
34
+
35
+ - **Pillar A: Format-Agnostic Parsers:** cleanly separate UI generation from underlying data formats. Out-of-the-box support for YAML, JSON, and XML (including schema-driven arrays and attributes), with abstract base classes extensible to CSV and others.
36
+ - **Pillar B: Hierarchical UI:** Dynamic tree-based rendering with full support for multidimensional containers, dynamic polymorphic list additions, and node mapping. Powered natively by NiceGUI.
37
+ - **Pillar C: Data Validity:** Enforces schema metadata strictly at the UI layer. Missing fields gracefully populate via defaults, required flags trigger locking, and nested typings are continuously evaluated.
38
+ - **Pillar D: Extensibility & Programmatic Control:** Decomposed core logic (App, Parser, State, Schema, UI) allowing external tools and wrappers (e.g. CLI, Agent Workflows) to invoke the editor or inject properties safely.
39
+
40
+ ## Installation
41
+
42
+ ### From PyPI (recommended)
43
+
44
+ ```bash
45
+ pip install structui
46
+ ```
47
+
48
+ ### From Source (development)
49
+
50
+ ```bash
51
+ git clone https://github.com/MoSaeedHammad/structui.git
52
+ cd structui
53
+ pip install -e .
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ Launch the editor in the current directory against your local configuration files by simply typing:
59
+
60
+ ```bash
61
+ structui --dir . --schema .structui_schema.yaml --port 8080
62
+ ```
63
+
64
+ ## Testing
65
+
66
+ To run the test suite and verify code coverage, first install the necessary development dependencies:
67
+
68
+ ```bash
69
+ pip install -e . pytest pytest-cov pytest-asyncio pyyaml nicegui
70
+ ```
71
+
72
+ Then, run `pytest` with coverage reporting:
73
+
74
+ ```bash
75
+ PYTHONPATH=src python3 -m pytest tests/ --cov=structui --cov-report=term-missing
76
+ ```
@@ -39,3 +39,17 @@ Launch the editor in the current directory against your local configuration file
39
39
  ```bash
40
40
  structui --dir . --schema .structui_schema.yaml --port 8080
41
41
  ```
42
+
43
+ ## Testing
44
+
45
+ To run the test suite and verify code coverage, first install the necessary development dependencies:
46
+
47
+ ```bash
48
+ pip install -e . pytest pytest-cov pytest-asyncio pyyaml nicegui
49
+ ```
50
+
51
+ Then, run `pytest` with coverage reporting:
52
+
53
+ ```bash
54
+ PYTHONPATH=src python3 -m pytest tests/ --cov=structui --cov-report=term-missing
55
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "structui"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "A format-agnostic, schema-driven, hierarchical configuration UI."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -1,4 +1,4 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -4,7 +4,7 @@ from structui.ui import StructUI
4
4
  from structui.state import AppState
5
5
  from structui.schema import SchemaManager
6
6
 
7
- def run_app(data_dir: str = ".", schema_filepath: str = ".structui_schema.yaml", port: int = 8080, dark_mode: Optional[bool] = False):
7
+ def run_app(data_dir: str = ".", schema_filepath: str = ".structui_schema.yaml", port: int = 8080, dark_mode: bool = False):
8
8
  schema_manager = SchemaManager(schema_filepath)
9
9
  try:
10
10
  app_state = AppState(data_dir, schema_manager)
@@ -16,5 +16,5 @@ def main():
16
16
  print(f"Error starting StructUI: {e}", file=sys.stderr)
17
17
  sys.exit(1)
18
18
 
19
- if __name__ in {"__main__", "__mp_main__"}:
19
+ if __name__ in {"__main__", "__mp_main__"}: # pragma: no cover
20
20
  main()
@@ -7,8 +7,8 @@ from nicegui import events, ui
7
7
 
8
8
  class LocalFilePicker(ui.dialog):
9
9
 
10
- def __init__(self, directory: str, *,
11
- upper_limit: Optional[str] = ..., multiple: bool = False, show_hidden_files: bool = False,
10
+ def __init__(self, directory: str, *, allowed_extensions: Optional[list] = None,
11
+ upper_limit: Optional[str] = None, multiple: bool = False, show_hidden_files: bool = False,
12
12
  dirs_only: bool = False) -> None:
13
13
  """Local File Picker
14
14
 
@@ -29,6 +29,7 @@ class LocalFilePicker(ui.dialog):
29
29
  self.upper_limit = Path(directory if upper_limit == ... else upper_limit).expanduser().resolve()
30
30
  self.show_hidden_files = show_hidden_files
31
31
  self.dirs_only = dirs_only
32
+ self.allowed_extensions = allowed_extensions
32
33
 
33
34
  with self, ui.card():
34
35
  self.add_drives_toggle()
@@ -57,7 +58,11 @@ class LocalFilePicker(ui.dialog):
57
58
  paths = [p for p in paths if not p.name.startswith('.')]
58
59
  if self.dirs_only:
59
60
  paths = [p for p in paths if p.is_dir()]
61
+ elif self.allowed_extensions:
62
+ allowed_exts = [ext.lower() if ext.startswith('.') else f'.{ext.lower()}' for ext in self.allowed_extensions]
63
+ paths = [p for p in paths if p.is_dir() or p.suffix.lower() in allowed_exts]
60
64
  paths.sort(key=lambda p: p.name.lower())
65
+
61
66
  paths.sort(key=lambda p: not p.is_dir())
62
67
 
63
68
  self.grid.options['rowData'] = [
@@ -1,5 +1,5 @@
1
1
  import os
2
- import yaml
2
+ import yaml # type: ignore
3
3
  import json
4
4
  import xml.etree.ElementTree as ET
5
5
  from abc import ABC, abstractmethod
@@ -1,5 +1,5 @@
1
1
  import os
2
- from typing import Dict, Any
2
+ from typing import Dict, Any, Optional
3
3
  from .parser import get_parser
4
4
 
5
5
  class SchemaManager:
@@ -22,7 +22,7 @@ class SchemaManager:
22
22
  """Safely fetch metadata for a property key."""
23
23
  return self.schema_meta.get(key, {})
24
24
 
25
- def get_default_val_for_type(self, type_str: str) -> Any:
25
+ def get_default_val_for_type(self, type_str: Optional[str]) -> Any:
26
26
  """Returns a sensible default value based on the given schema type."""
27
27
  if type_str == 'boolean': return False
28
28
  if type_str in ['integer', 'number', 'float']: return 0
@@ -88,9 +88,9 @@ class SchemaManager:
88
88
  current_schema_key = meta.get('list_item_type', current_schema_key)
89
89
  else:
90
90
  current_schema_key = p
91
- return current_schema_key
91
+ return str(current_schema_key)
92
92
 
93
- def get_label_key_for_schema(self, schema_key: str) -> str:
93
+ def get_label_key_for_schema(self, schema_key: str) -> Optional[str]:
94
94
  """Finds which sub-property should be dynamically used as the naming label for UI containers."""
95
95
  if schema_key and schema_key in self.schema_meta:
96
96
  meta = self.get_meta(schema_key)
@@ -99,12 +99,12 @@ class SchemaManager:
99
99
  for child in meta.get('allowed_children', []):
100
100
  if child in self.schema_meta and self.schema_meta[child].get('is_label', False):
101
101
  return child
102
- return None
102
+ return ''
103
103
 
104
104
  def get_item_label(self, item_data: Any, item_path: str, root_data: Any, default_label: str) -> str:
105
105
  """Agnostically determines the display label for an object via schema rules."""
106
106
  if not isinstance(item_data, dict):
107
- return default_label
107
+ return default_label or ''
108
108
 
109
109
  schema_key = self.get_schema_key_for_path(item_path, root_data)
110
110
  label_key = self.get_label_key_for_schema(schema_key)
@@ -120,4 +120,4 @@ class SchemaManager:
120
120
  if isinstance(v, str):
121
121
  return v
122
122
 
123
- return default_label
123
+ return default_label or ''
@@ -88,7 +88,7 @@ class AppState:
88
88
  return self.config_data
89
89
 
90
90
  keys = path.split('/')[1:]
91
- curr = self.config_data
91
+ curr: Any = self.config_data
92
92
 
93
93
  try:
94
94
  for key in keys:
@@ -9,17 +9,17 @@ from .file_picker import LocalFilePicker
9
9
  class StructUI:
10
10
  """The central view abstraction for managing the hierarchical NiceGUI visualization."""
11
11
 
12
- def __init__(self, state: AppState, schema_manager: SchemaManager, dark_mode: bool = None):
12
+ def __init__(self, state: AppState, schema_manager: SchemaManager, dark_mode: bool = False):
13
13
  self.state = state
14
14
  self.schema_manager = schema_manager
15
15
 
16
- self.selected_path = {"value": "root"}
17
- self.tree = None
18
- self.editor_scroll_area = None
19
- self.footer_pane = None
20
- self.dark_mode = None
16
+ self.selected_path: dict[str, Any] = {"value": "root"}
17
+ self.tree: Any = None
18
+ self.editor_scroll_area: Any = None
19
+ self.footer_pane: Any = None
20
+ self.dark_mode: Any = None
21
21
  self.initial_dark_mode = dark_mode
22
- self.save_btn = None
22
+ self.save_btn: Any = None
23
23
 
24
24
  def get_allowed_options(self, path: str, data_node: Any) -> List[Dict[str, str]]:
25
25
  schema_key = self.schema_manager.get_schema_key_for_path(path, self.state.config_data)
@@ -58,8 +58,8 @@ class StructUI:
58
58
  return allowed_options
59
59
 
60
60
  def build_tree_nodes(self, data: Any, path: str = "root", name: str = "Configurations") -> Dict[str, Any]:
61
- node = {'id': path, 'label': name}
62
- children = []
61
+ node: Dict[str, Any] = {'id': path, 'label': name}
62
+ children: list[Dict[str, Any]] = []
63
63
  node['allowed'] = self.get_allowed_options(path, data)
64
64
  has_prims = False
65
65
 
@@ -162,17 +162,17 @@ class StructUI:
162
162
  if opt_type == 'dict_key':
163
163
  if isinstance(data_node, dict):
164
164
  k = option.get('key')
165
- meta_type = self.schema_manager.get_meta(k).get('type')
165
+ meta_type = self.schema_manager.get_meta(str(k)).get('type')
166
166
  if meta_type == 'list':
167
167
  data_node[k] = []
168
168
  elif meta_type in ['container', 'dict']:
169
169
  data_node[k] = {}
170
- elif meta_type == 'bool':
170
+ elif meta_type in ['bool', 'boolean']:
171
171
  data_node[k] = False
172
- elif meta_type in ['int', 'number', 'float']:
172
+ elif meta_type in ['int', 'integer', 'number', 'float']:
173
173
  data_node[k] = 0
174
174
  else:
175
- data_node[k] = ""
175
+ data_node[k] = self.schema_manager.get_default_val_for_type(meta_type)
176
176
  elif opt_type == 'custom_dict':
177
177
  if isinstance(data_node, dict):
178
178
  with ui.dialog() as dialog, ui.card().classes('min-w-[300px]'):
@@ -209,15 +209,22 @@ class StructUI:
209
209
  if not path:
210
210
  path = "root"
211
211
  self.selected_path["value"] = path
212
+ assert self.editor_scroll_area is not None
213
+
212
214
  self.editor_scroll_area.clear()
213
215
  self.update_footer(None)
214
216
 
215
217
  data_node = self.state.get_data_by_path(path)
216
218
  if data_node is None:
219
+ assert self.editor_scroll_area is not None
220
+
217
221
  with self.editor_scroll_area:
218
222
  ui.label("This node no longer exists or was deleted.").classes('text-red-500 mt-10 text-lg font-bold')
219
223
  return
220
224
 
225
+ assert self.editor_scroll_area is not None
226
+
227
+
221
228
  with self.editor_scroll_area:
222
229
  # INTERACTIVE BREADCRUMBS
223
230
  parts = path.split('/')
@@ -275,14 +282,23 @@ class StructUI:
275
282
  props_container = ui.column().classes('w-full gap-4')
276
283
 
277
284
  def render_primitive_input(k, v, parent_node):
278
- def make_on_change(prop_key=k):
285
+ meta = self.schema_manager.get_meta(str(k))
286
+
287
+ def make_on_change(prop_key=k, prop_type=meta.get('type')):
279
288
  def handler(e):
280
- self.state.set_data_by_path(self.selected_path["value"], str(prop_key), e.value)
289
+ val = getattr(e, 'value', getattr(getattr(e, 'sender', None), 'value', None))
290
+ if prop_type == 'integer' and val is not None and val != '':
291
+ try: val = int(float(val))
292
+ except ValueError: pass
293
+ elif prop_type in ['number', 'float'] and val is not None and val != '':
294
+ try: val = float(val)
295
+ except ValueError: pass
296
+
297
+ self.state.set_data_by_path(self.selected_path["value"], str(prop_key), val)
281
298
  self.state.commit()
282
299
  self.update_save_btn_state()
283
300
  return handler
284
301
 
285
- meta = self.schema_manager.get_meta(str(k))
286
302
  is_required = meta.get('required', False)
287
303
  label_text = f"{k} *" if is_required else str(k)
288
304
  options = meta.get('options')
@@ -294,8 +310,43 @@ class StructUI:
294
310
  inp = ui.select(safe_options, value=v, label=label_text).classes('flex-grow').on_value_change(make_on_change())
295
311
  elif isinstance(v, bool):
296
312
  inp = ui.switch(text=label_text, value=v).on_value_change(make_on_change())
297
- elif isinstance(v, (int, float)):
298
- inp = ui.number(label=label_text, value=v).classes('flex-grow').on_value_change(make_on_change())
313
+ elif meta.get('type') == 'file':
314
+ async def pick_file(k=k, val=v):
315
+ result = await LocalFilePicker(directory=self.state.data_dir, multiple=False, show_hidden_files=True, allowed_extensions=meta.get('extensions', []))
316
+ if result:
317
+ self.state.set_data_by_path(self.selected_path["value"], str(k), result[0])
318
+ self.state.commit()
319
+ self.update_save_btn_state()
320
+ self.refresh_tree_and_editor()
321
+
322
+ inp = ui.input(label=label_text, value=str(v)).classes('flex-grow').on_value_change(make_on_change())
323
+ ui.button(icon='folder_open', on_click=pick_file).props('flat round size=sm').tooltip('Select File')
324
+ elif isinstance(v, float) or (isinstance(v, int) and type(v) is not bool and meta.get('type') != 'integer'):
325
+ inp = ui.input(type='number', label=label_text, value=str(v)).classes('flex-grow').on('blur', make_on_change())
326
+ elif isinstance(v, int) and type(v) is not bool:
327
+ # For integers, we add a Dec/Hex toggle switch
328
+ with ui.row().classes('flex-grow items-center gap-2 flex-nowrap'):
329
+ is_hex = getattr(self, f'_is_hex_{k}_{self.selected_path["value"].replace("/", "_")}', False)
330
+
331
+ def toggle_hex(e, key=k, path_val=self.selected_path["value"]):
332
+ setattr(self, f'_is_hex_{key}_{path_val.replace("/", "_")}', e.value)
333
+ self.refresh_tree_and_editor()
334
+
335
+ hex_toggle = ui.switch('Hex', value=is_hex).on_value_change(toggle_hex)
336
+
337
+ if is_hex:
338
+ hex_val = hex(v) if isinstance(v, int) else ""
339
+ def on_hex_change(e, pk=k):
340
+ try:
341
+ new_val = int(e.value, 16) if e.value else 0
342
+ self.state.set_data_by_path(self.selected_path["value"], str(pk), new_val)
343
+ self.state.commit()
344
+ self.update_save_btn_state()
345
+ except ValueError:
346
+ pass
347
+ inp = ui.input(label=label_text, value=hex_val).classes('flex-grow').on_value_change(on_hex_change)
348
+ else:
349
+ inp = ui.input(type='number', label=label_text, value=str(v)).classes('flex-grow').on('blur', make_on_change())
299
350
  else:
300
351
  inp = ui.input(label=label_text, value=str(v)).classes('flex-grow').on_value_change(make_on_change())
301
352
 
@@ -18,7 +18,7 @@ def _parse_element(element: ET.Element, current_schema: Optional[Dict[str, Any]]
18
18
  if text_content:
19
19
  result["#text"] = text_content
20
20
 
21
- child_groups = {}
21
+ child_groups: dict = {}
22
22
  for child in element:
23
23
  child_schema = None
24
24
  if current_schema and "schema" in current_schema and child.tag in current_schema["schema"]:
@@ -1,62 +1,76 @@
1
- Metadata-Version: 2.4
2
- Name: structui
3
- Version: 0.2.0
4
- Summary: A format-agnostic, schema-driven, hierarchical configuration UI.
5
- Author: structui contributors
6
- License: MIT
7
- Classifier: Development Status :: 4 - Beta
8
- Classifier: Environment :: Web Environment
9
- Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Requires-Python: >=3.9
17
- Description-Content-Type: text/markdown
18
- Requires-Dist: nicegui>=1.4.0
19
- Requires-Dist: pyyaml>=6.0.1
20
- Requires-Dist: pywin32>=300; sys_platform == "win32"
21
-
22
- # StructUI
23
-
24
- [![PyPI version](https://img.shields.io/pypi/v/structui.svg)](https://pypi.org/project/structui/)
25
- [![CI](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml/badge.svg)](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
26
- [![CD](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml/badge.svg)](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
27
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
-
29
- StructUI is a format-agnostic, schema-driven, hierarchical configuration UI engine built in Python. Designed as a flexible architectural backbone, it parses standard configuration files (YAML, JSON, CSV, XML) and dynamically generates a live web-based property editor based on constraints and metadata defined in a schema file.
30
-
31
- The architecture is explicitly decoupled, making it readily extensible to strict domain-specific specifications (e.g., AUTOSAR configurators) and agent-driven programmatic workflows.
32
-
33
- ## Features
34
-
35
- - **Pillar A: Format-Agnostic Parsers:** cleanly separate UI generation from underlying data formats. Out-of-the-box support for YAML, JSON, and XML (including schema-driven arrays and attributes), with abstract base classes extensible to CSV and others.
36
- - **Pillar B: Hierarchical UI:** Dynamic tree-based rendering with full support for multidimensional containers, dynamic polymorphic list additions, and node mapping. Powered natively by NiceGUI.
37
- - **Pillar C: Data Validity:** Enforces schema metadata strictly at the UI layer. Missing fields gracefully populate via defaults, required flags trigger locking, and nested typings are continuously evaluated.
38
- - **Pillar D: Extensibility & Programmatic Control:** Decomposed core logic (App, Parser, State, Schema, UI) allowing external tools and wrappers (e.g. CLI, Agent Workflows) to invoke the editor or inject properties safely.
39
-
40
- ## Installation
41
-
42
- ### From PyPI (recommended)
43
-
44
- ```bash
45
- pip install structui
46
- ```
47
-
48
- ### From Source (development)
49
-
50
- ```bash
51
- git clone https://github.com/MoSaeedHammad/structui.git
52
- cd structui
53
- pip install -e .
54
- ```
55
-
56
- ## Quick Start
57
-
58
- Launch the editor in the current directory against your local configuration files by simply typing:
59
-
60
- ```bash
61
- structui --dir . --schema .structui_schema.yaml --port 8080
62
- ```
1
+ Metadata-Version: 2.4
2
+ Name: structui
3
+ Version: 0.3.0
4
+ Summary: A format-agnostic, schema-driven, hierarchical configuration UI.
5
+ Author: structui contributors
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Environment :: Web Environment
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: nicegui>=1.4.0
19
+ Requires-Dist: pyyaml>=6.0.1
20
+ Requires-Dist: pywin32>=300; sys_platform == "win32"
21
+
22
+ # StructUI
23
+
24
+ [![PyPI version](https://img.shields.io/pypi/v/structui.svg)](https://pypi.org/project/structui/)
25
+ [![CI](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml/badge.svg)](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
26
+ [![CD](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml/badge.svg)](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
+
29
+ StructUI is a format-agnostic, schema-driven, hierarchical configuration UI engine built in Python. Designed as a flexible architectural backbone, it parses standard configuration files (YAML, JSON, CSV, XML) and dynamically generates a live web-based property editor based on constraints and metadata defined in a schema file.
30
+
31
+ The architecture is explicitly decoupled, making it readily extensible to strict domain-specific specifications (e.g., AUTOSAR configurators) and agent-driven programmatic workflows.
32
+
33
+ ## Features
34
+
35
+ - **Pillar A: Format-Agnostic Parsers:** cleanly separate UI generation from underlying data formats. Out-of-the-box support for YAML, JSON, and XML (including schema-driven arrays and attributes), with abstract base classes extensible to CSV and others.
36
+ - **Pillar B: Hierarchical UI:** Dynamic tree-based rendering with full support for multidimensional containers, dynamic polymorphic list additions, and node mapping. Powered natively by NiceGUI.
37
+ - **Pillar C: Data Validity:** Enforces schema metadata strictly at the UI layer. Missing fields gracefully populate via defaults, required flags trigger locking, and nested typings are continuously evaluated.
38
+ - **Pillar D: Extensibility & Programmatic Control:** Decomposed core logic (App, Parser, State, Schema, UI) allowing external tools and wrappers (e.g. CLI, Agent Workflows) to invoke the editor or inject properties safely.
39
+
40
+ ## Installation
41
+
42
+ ### From PyPI (recommended)
43
+
44
+ ```bash
45
+ pip install structui
46
+ ```
47
+
48
+ ### From Source (development)
49
+
50
+ ```bash
51
+ git clone https://github.com/MoSaeedHammad/structui.git
52
+ cd structui
53
+ pip install -e .
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ Launch the editor in the current directory against your local configuration files by simply typing:
59
+
60
+ ```bash
61
+ structui --dir . --schema .structui_schema.yaml --port 8080
62
+ ```
63
+
64
+ ## Testing
65
+
66
+ To run the test suite and verify code coverage, first install the necessary development dependencies:
67
+
68
+ ```bash
69
+ pip install -e . pytest pytest-cov pytest-asyncio pyyaml nicegui
70
+ ```
71
+
72
+ Then, run `pytest` with coverage reporting:
73
+
74
+ ```bash
75
+ PYTHONPATH=src python3 -m pytest tests/ --cov=structui --cov-report=term-missing
76
+ ```
@@ -17,7 +17,6 @@ src/structui.egg-info/requires.txt
17
17
  src/structui.egg-info/top_level.txt
18
18
  tests/test_app.py
19
19
  tests/test_cli.py
20
- tests/test_cli_entry.py
21
20
  tests/test_coverage_boost.py
22
21
  tests/test_file_picker.py
23
22
  tests/test_final_gap.py
@@ -25,5 +24,9 @@ tests/test_parser.py
25
24
  tests/test_schema.py
26
25
  tests/test_state.py
27
26
  tests/test_ui.py
27
+ tests/test_ui_blur.py
28
+ tests/test_ui_coverage.py
28
29
  tests/test_ui_extra.py
30
+ tests/test_ui_extra2.py
31
+ tests/test_ui_final.py
29
32
  tests/test_xml_parser.py