dan-annotation 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 DAN Python Library Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: dan-annotation
3
+ Version: 0.1.0
4
+ Summary: DAN (Data Advanced Notation) Parser and Encoder for Python
5
+ Author: DAN Python Library
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/marcuwynu23/danpy#readme
8
+ Project-URL: Issue Tracker, https://github.com/marcuwynu23/danpy/issues
9
+ Project-URL: Source Code, https://github.com/marcuwynu23/danpy
10
+ Keywords: dan,parser,encoder,data-format,configuration,config
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: Text Processing :: Markup
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.7
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # DAN (Data Advanced Notation) Python Library
31
+
32
+ [![Python Version](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/)
33
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
34
+
35
+ A Python implementation of the DAN (Data Advanced Notation) parser and encoder. DAN is a human-readable data format that combines the simplicity of key-value pairs with the power of nested structures and tables.
36
+
37
+ ## Features
38
+
39
+ - **Decode**: Parse DAN text into Python dictionaries
40
+ - **Encode**: Convert Python dictionaries into DAN text
41
+ - Support for nested blocks, tables, arrays, and various data types
42
+ - Comment support (# and //)
43
+ - Type inference (strings, numbers, booleans, arrays)
44
+ - Comprehensive test suite with 40+ test cases
45
+ - Multiple real-world examples
46
+
47
+ See [FEATURES.md](FEATURES.md) for a complete feature list.
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install -e .
53
+ ```
54
+
55
+ Or install from source:
56
+
57
+ ```bash
58
+ git clone https://github.com/yourusername/dan-py.git
59
+ cd dan-py
60
+ pip install -e .
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ### Decoding DAN
66
+
67
+ ```python
68
+ from dan import decode
69
+
70
+ text = """
71
+ app {
72
+ name: "MyApp"
73
+ version: 1.0
74
+ server {
75
+ host: localhost
76
+ port: 3000
77
+ }
78
+ }
79
+ """
80
+
81
+ data = decode(text)
82
+ print(data)
83
+ # {'app': {'name': 'MyApp', 'version': 1.0, 'server': {'host': 'localhost', 'port': 3000}}}
84
+ ```
85
+
86
+ ### Encoding to DAN
87
+
88
+ ```python
89
+ from dan import encode
90
+
91
+ data = {
92
+ "app": {
93
+ "name": "MyApp",
94
+ "version": 1.0,
95
+ "server": {
96
+ "host": "localhost",
97
+ "port": 3000
98
+ }
99
+ }
100
+ }
101
+
102
+ text = encode(data)
103
+ print(text)
104
+ ```
105
+
106
+ ### Working with Tables
107
+
108
+ ```python
109
+ text = """
110
+ users: table(id, username, email) [
111
+ 1, alice, "alice@example.com"
112
+ 2, bob, "bob@example.com"
113
+ ]
114
+ """
115
+
116
+ data = decode(text)
117
+ # {'users': [{'id': 1, 'username': 'alice', 'email': 'alice@example.com'}, ...]}
118
+ ```
119
+
120
+ ### Reading from Files
121
+
122
+ ```python
123
+ from dan import decode
124
+
125
+ with open('config.dan', 'r') as f:
126
+ config = decode(f.read())
127
+ ```
128
+
129
+ ## Examples
130
+
131
+ Check out the [examples/](examples/) directory for real-world usage scenarios:
132
+
133
+ - Application configuration (`config.dan`)
134
+ - Database schemas (`database_schema.dan`)
135
+ - API routes (`api_routes.dan`)
136
+ - Microservices configuration (`microservices.dan`)
137
+ - Docker Compose (`docker_compose.dan`)
138
+ - Kubernetes resources (`kubernetes.dan`)
139
+ - And many more!
140
+
141
+ ## Running Tests
142
+
143
+ ```bash
144
+ # Run all tests
145
+ python -m unittest discover tests -v
146
+
147
+ # Run specific test file
148
+ python -m unittest tests.test_dan -v
149
+
150
+ # Using pytest (if installed)
151
+ pytest tests/
152
+ ```
153
+
154
+ ## Documentation
155
+
156
+ - [FEATURES.md](FEATURES.md) - Complete feature documentation
157
+ - [CONTRIBUTING.md](CONTRIBUTING.md) - Guidelines for contributing
158
+ - [CHANGELOG.md](CHANGELOG.md) - Version history and changes
159
+ - [examples/README.md](examples/README.md) - Example files documentation
160
+
161
+ ## Contributing
162
+
163
+ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
164
+
165
+ ## License
166
+
167
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
168
+
169
+ ## Security
170
+
171
+ Please report security vulnerabilities to the maintainers. See [SECURITY.md](SECURITY.md) for more information.
172
+
173
+ ## Support
174
+
175
+ - 📖 [Documentation](https://github.com/yourusername/dan-py#readme)
176
+ - 🐛 [Issue Tracker](https://github.com/yourusername/dan-py/issues)
177
+ - 💬 [Discussions](https://github.com/yourusername/dan-py/discussions)
178
+
@@ -0,0 +1,149 @@
1
+ # DAN (Data Advanced Notation) Python Library
2
+
3
+ [![Python Version](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/)
4
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
5
+
6
+ A Python implementation of the DAN (Data Advanced Notation) parser and encoder. DAN is a human-readable data format that combines the simplicity of key-value pairs with the power of nested structures and tables.
7
+
8
+ ## Features
9
+
10
+ - **Decode**: Parse DAN text into Python dictionaries
11
+ - **Encode**: Convert Python dictionaries into DAN text
12
+ - Support for nested blocks, tables, arrays, and various data types
13
+ - Comment support (# and //)
14
+ - Type inference (strings, numbers, booleans, arrays)
15
+ - Comprehensive test suite with 40+ test cases
16
+ - Multiple real-world examples
17
+
18
+ See [FEATURES.md](FEATURES.md) for a complete feature list.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install -e .
24
+ ```
25
+
26
+ Or install from source:
27
+
28
+ ```bash
29
+ git clone https://github.com/yourusername/dan-py.git
30
+ cd dan-py
31
+ pip install -e .
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### Decoding DAN
37
+
38
+ ```python
39
+ from dan import decode
40
+
41
+ text = """
42
+ app {
43
+ name: "MyApp"
44
+ version: 1.0
45
+ server {
46
+ host: localhost
47
+ port: 3000
48
+ }
49
+ }
50
+ """
51
+
52
+ data = decode(text)
53
+ print(data)
54
+ # {'app': {'name': 'MyApp', 'version': 1.0, 'server': {'host': 'localhost', 'port': 3000}}}
55
+ ```
56
+
57
+ ### Encoding to DAN
58
+
59
+ ```python
60
+ from dan import encode
61
+
62
+ data = {
63
+ "app": {
64
+ "name": "MyApp",
65
+ "version": 1.0,
66
+ "server": {
67
+ "host": "localhost",
68
+ "port": 3000
69
+ }
70
+ }
71
+ }
72
+
73
+ text = encode(data)
74
+ print(text)
75
+ ```
76
+
77
+ ### Working with Tables
78
+
79
+ ```python
80
+ text = """
81
+ users: table(id, username, email) [
82
+ 1, alice, "alice@example.com"
83
+ 2, bob, "bob@example.com"
84
+ ]
85
+ """
86
+
87
+ data = decode(text)
88
+ # {'users': [{'id': 1, 'username': 'alice', 'email': 'alice@example.com'}, ...]}
89
+ ```
90
+
91
+ ### Reading from Files
92
+
93
+ ```python
94
+ from dan import decode
95
+
96
+ with open('config.dan', 'r') as f:
97
+ config = decode(f.read())
98
+ ```
99
+
100
+ ## Examples
101
+
102
+ Check out the [examples/](examples/) directory for real-world usage scenarios:
103
+
104
+ - Application configuration (`config.dan`)
105
+ - Database schemas (`database_schema.dan`)
106
+ - API routes (`api_routes.dan`)
107
+ - Microservices configuration (`microservices.dan`)
108
+ - Docker Compose (`docker_compose.dan`)
109
+ - Kubernetes resources (`kubernetes.dan`)
110
+ - And many more!
111
+
112
+ ## Running Tests
113
+
114
+ ```bash
115
+ # Run all tests
116
+ python -m unittest discover tests -v
117
+
118
+ # Run specific test file
119
+ python -m unittest tests.test_dan -v
120
+
121
+ # Using pytest (if installed)
122
+ pytest tests/
123
+ ```
124
+
125
+ ## Documentation
126
+
127
+ - [FEATURES.md](FEATURES.md) - Complete feature documentation
128
+ - [CONTRIBUTING.md](CONTRIBUTING.md) - Guidelines for contributing
129
+ - [CHANGELOG.md](CHANGELOG.md) - Version history and changes
130
+ - [examples/README.md](examples/README.md) - Example files documentation
131
+
132
+ ## Contributing
133
+
134
+ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
135
+
136
+ ## License
137
+
138
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
139
+
140
+ ## Security
141
+
142
+ Please report security vulnerabilities to the maintainers. See [SECURITY.md](SECURITY.md) for more information.
143
+
144
+ ## Support
145
+
146
+ - 📖 [Documentation](https://github.com/yourusername/dan-py#readme)
147
+ - 🐛 [Issue Tracker](https://github.com/yourusername/dan-py/issues)
148
+ - 💬 [Discussions](https://github.com/yourusername/dan-py/discussions)
149
+
@@ -0,0 +1,221 @@
1
+ """
2
+ DAN (Data Advanced Notation) Parser and Encoder for Python
3
+ """
4
+
5
+ import re
6
+ from typing import Any, Dict, List, Union
7
+
8
+
9
+ def decode(text: Union[str, bytes]) -> Dict[str, Any]:
10
+ """
11
+ Decode DAN text into a Python dictionary.
12
+
13
+ Args:
14
+ text: DAN text as string or bytes
15
+
16
+ Returns:
17
+ Dictionary representation of the DAN data
18
+
19
+ Raises:
20
+ TypeError: If input is not string or bytes
21
+ """
22
+ # Handle bytes inputs
23
+ if isinstance(text, bytes):
24
+ text = text.decode('utf-8')
25
+
26
+ # Ensure text is a string
27
+ if not isinstance(text, str):
28
+ raise TypeError(f"Expected string or bytes, got {type(text).__name__}")
29
+
30
+ # Handle empty input - return empty object
31
+ if not text or not text.strip():
32
+ return {}
33
+
34
+ lines = text.splitlines()
35
+ stack = [{"obj": {}, "type": "root"}]
36
+ current_table = None
37
+
38
+ table_re = re.compile(r'^(\w+):\s*table\(([^)]+)\)\s*\[$')
39
+ kv_re = re.compile(r'^(\w+):\s*(.+)$')
40
+
41
+ for i, line in enumerate(lines):
42
+ # Remove comments and trim
43
+ comment_index1 = line.find("#")
44
+ comment_index2 = line.find("//")
45
+ cut_index = -1
46
+ if comment_index1 >= 0 and comment_index2 >= 0:
47
+ cut_index = min(comment_index1, comment_index2)
48
+ else:
49
+ cut_index = max(comment_index1, comment_index2)
50
+
51
+ if cut_index >= 0:
52
+ line = line[:cut_index]
53
+ line = line.strip()
54
+ if not line:
55
+ continue
56
+
57
+ top = stack[-1]
58
+
59
+ # Block start
60
+ if line.endswith("{"):
61
+ key = line[:-1].strip()
62
+ new_obj = {}
63
+ top["obj"][key] = new_obj
64
+ stack.append({"obj": new_obj, "type": "block"})
65
+ continue
66
+
67
+ # Block end
68
+ if line == "}":
69
+ stack.pop()
70
+ continue
71
+
72
+ # Table start
73
+ table_match = table_re.match(line)
74
+ if table_match:
75
+ key = table_match.group(1)
76
+ columns = [c.strip() for c in table_match.group(2).split(",")]
77
+ table = []
78
+ top["obj"][key] = table
79
+ current_table = {"obj": table, "columns": columns}
80
+ continue
81
+
82
+ # Table end
83
+ if line == "]":
84
+ current_table = None
85
+ continue
86
+
87
+ # Table row
88
+ if current_table:
89
+ row = {}
90
+ # Split by comma and trim each value
91
+ values = [v.strip() for v in line.split(",")]
92
+ # Only process up to the number of columns defined
93
+ for col_idx, val in enumerate(values):
94
+ if col_idx < len(current_table["columns"]):
95
+ row[current_table["columns"][col_idx]] = parse_value(val)
96
+ # Ensure all columns are present (fill missing with empty string)
97
+ for col_idx, col_name in enumerate(current_table["columns"]):
98
+ if col_name not in row:
99
+ row[col_name] = ""
100
+ current_table["obj"].append(row)
101
+ continue
102
+
103
+ # Key-value
104
+ kv_match = kv_re.match(line)
105
+ if kv_match:
106
+ key = kv_match.group(1)
107
+ val = kv_match.group(2)
108
+ top["obj"][key] = parse_value(val)
109
+
110
+ return stack[0]["obj"]
111
+
112
+
113
+ def encode(obj: Dict[str, Any], indent: int = 0) -> str:
114
+ """
115
+ Encode a Python dictionary into DAN text.
116
+
117
+ Args:
118
+ obj: Dictionary to encode
119
+ indent: Current indentation level (for recursion)
120
+
121
+ Returns:
122
+ DAN text representation
123
+ """
124
+ lines = []
125
+ pad = " " * indent
126
+
127
+ for key, val in obj.items():
128
+ if isinstance(val, list):
129
+ if len(val) > 0 and isinstance(val[0], dict) and not isinstance(val[0], list):
130
+ # Table
131
+ columns = list(val[0].keys())
132
+ lines.append(f"{pad}{key}: table({', '.join(columns)}) [")
133
+ for row in val:
134
+ row_values = [serialize_value(row.get(c)) for c in columns]
135
+ lines.append(f"{pad} {', '.join(row_values)}")
136
+ lines.append(f"{pad}]")
137
+ else:
138
+ lines.append(f"{pad}{key}: {serialize_value(val)}")
139
+ elif isinstance(val, dict):
140
+ lines.append(f"{pad}{key} {{")
141
+ nested_lines = encode(val, indent + 1)
142
+ if nested_lines:
143
+ # Split nested lines and add them individually for proper formatting
144
+ nested_lines_array = nested_lines.split("\n")
145
+ lines.extend(nested_lines_array)
146
+ lines.append(f"{pad}}}")
147
+ else:
148
+ lines.append(f"{pad}{key}: {serialize_value(val)}")
149
+
150
+ return "\n".join(lines)
151
+
152
+
153
+ # --- Internal helpers ---
154
+
155
+ def parse_value(val: str) -> Any:
156
+ """
157
+ Parse a string value into appropriate Python type.
158
+
159
+ Args:
160
+ val: String value to parse
161
+
162
+ Returns:
163
+ Parsed value (bool, int, float, str, list, or original string)
164
+ """
165
+ if not isinstance(val, str):
166
+ return val
167
+
168
+ val = val.strip()
169
+
170
+ if val == "true":
171
+ return True
172
+ if val == "false":
173
+ return False
174
+
175
+ # String (quoted)
176
+ if len(val) >= 2 and val[0] == '"' and val[-1] == '"':
177
+ return val[1:-1]
178
+
179
+ # Number
180
+ if val and not val.startswith("["):
181
+ try:
182
+ # Try integer first
183
+ if '.' not in val:
184
+ return int(val)
185
+ else:
186
+ return float(val)
187
+ except ValueError:
188
+ pass
189
+
190
+ # Array
191
+ if len(val) >= 2 and val[0] == "[" and val[-1] == "]":
192
+ content = val[1:-1].strip()
193
+ if content == "":
194
+ return []
195
+ # Split by comma, but preserve empty strings for explicit empty values
196
+ parts = [v.strip() for v in content.split(",")]
197
+ return [parse_value(v) for v in parts]
198
+
199
+ return val
200
+
201
+
202
+ def serialize_value(val: Any) -> str:
203
+ """
204
+ Serialize a Python value to DAN string representation.
205
+
206
+ Args:
207
+ val: Value to serialize
208
+
209
+ Returns:
210
+ String representation
211
+ """
212
+ if isinstance(val, bool):
213
+ return "true" if val else "false"
214
+ if isinstance(val, str):
215
+ return f'"{val}"'
216
+ if isinstance(val, (int, float)):
217
+ return str(val)
218
+ if isinstance(val, list):
219
+ return f"[{', '.join(serialize_value(v) for v in val)}]"
220
+ return str(val)
221
+