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.
- {structui-0.2.0 → structui-0.3.0}/PKG-INFO +76 -62
- {structui-0.2.0 → structui-0.3.0}/README.md +14 -0
- {structui-0.2.0 → structui-0.3.0}/pyproject.toml +1 -1
- {structui-0.2.0 → structui-0.3.0}/setup.cfg +4 -4
- {structui-0.2.0 → structui-0.3.0}/src/structui/app.py +1 -1
- {structui-0.2.0 → structui-0.3.0}/src/structui/cli.py +1 -1
- {structui-0.2.0 → structui-0.3.0}/src/structui/file_picker.py +7 -2
- {structui-0.2.0 → structui-0.3.0}/src/structui/parser.py +1 -1
- {structui-0.2.0 → structui-0.3.0}/src/structui/schema.py +7 -7
- {structui-0.2.0 → structui-0.3.0}/src/structui/state.py +1 -1
- {structui-0.2.0 → structui-0.3.0}/src/structui/ui.py +69 -18
- {structui-0.2.0 → structui-0.3.0}/src/structui/xml_parser.py +1 -1
- {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/PKG-INFO +76 -62
- {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/SOURCES.txt +4 -1
- {structui-0.2.0 → structui-0.3.0}/tests/test_app.py +23 -33
- structui-0.3.0/tests/test_cli.py +44 -0
- structui-0.3.0/tests/test_coverage_boost.py +292 -0
- {structui-0.2.0 → structui-0.3.0}/tests/test_file_picker.py +23 -0
- {structui-0.2.0 → structui-0.3.0}/tests/test_schema.py +1 -1
- {structui-0.2.0 → structui-0.3.0}/tests/test_ui.py +25 -9
- structui-0.3.0/tests/test_ui_blur.py +227 -0
- structui-0.3.0/tests/test_ui_coverage.py +160 -0
- {structui-0.2.0 → structui-0.3.0}/tests/test_ui_extra.py +57 -0
- structui-0.3.0/tests/test_ui_extra2.py +66 -0
- structui-0.3.0/tests/test_ui_final.py +506 -0
- structui-0.2.0/tests/test_cli.py +0 -22
- structui-0.2.0/tests/test_cli_entry.py +0 -18
- structui-0.2.0/tests/test_coverage_boost.py +0 -499
- {structui-0.2.0 → structui-0.3.0}/src/structui/__init__.py +0 -0
- {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/dependency_links.txt +0 -0
- {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/entry_points.txt +0 -0
- {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/requires.txt +0 -0
- {structui-0.2.0 → structui-0.3.0}/src/structui.egg-info/top_level.txt +0 -0
- {structui-0.2.0 → structui-0.3.0}/tests/test_final_gap.py +0 -0
- {structui-0.2.0 → structui-0.3.0}/tests/test_parser.py +0 -0
- {structui-0.2.0 → structui-0.3.0}/tests/test_state.py +0 -0
- {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.
|
|
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
|
-
[](https://pypi.org/project/structui/)
|
|
25
|
-
[](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
|
|
26
|
-
[](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
|
|
27
|
-
[](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
|
+
[](https://pypi.org/project/structui/)
|
|
25
|
+
[](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
|
|
26
|
+
[](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
|
|
27
|
+
[](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
|
+
```
|
|
@@ -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:
|
|
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)
|
|
@@ -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] =
|
|
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
|
-
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
|
|
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 ''
|
|
@@ -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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
298
|
-
|
|
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.
|
|
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
|
-
[](https://pypi.org/project/structui/)
|
|
25
|
-
[](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
|
|
26
|
-
[](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
|
|
27
|
-
[](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
|
+
[](https://pypi.org/project/structui/)
|
|
25
|
+
[](https://github.com/MoSaeedHammad/structui/actions/workflows/ci.yml)
|
|
26
|
+
[](https://github.com/MoSaeedHammad/structui/actions/workflows/publish.yml)
|
|
27
|
+
[](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
|