kryptorious-dataforge 1.0.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.
- kryptorious_dataforge-1.0.0/PKG-INFO +16 -0
- kryptorious_dataforge-1.0.0/pyproject.toml +25 -0
- kryptorious_dataforge-1.0.0/setup.cfg +4 -0
- kryptorious_dataforge-1.0.0/src/dataforge/__init__.py +2 -0
- kryptorious_dataforge-1.0.0/src/dataforge/cli.py +256 -0
- kryptorious_dataforge-1.0.0/src/kryptorious_dataforge.egg-info/PKG-INFO +16 -0
- kryptorious_dataforge-1.0.0/src/kryptorious_dataforge.egg-info/SOURCES.txt +9 -0
- kryptorious_dataforge-1.0.0/src/kryptorious_dataforge.egg-info/dependency_links.txt +1 -0
- kryptorious_dataforge-1.0.0/src/kryptorious_dataforge.egg-info/entry_points.txt +2 -0
- kryptorious_dataforge-1.0.0/src/kryptorious_dataforge.egg-info/requires.txt +10 -0
- kryptorious_dataforge-1.0.0/src/kryptorious_dataforge.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kryptorious-dataforge
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Universal data format converter — JSON, YAML, TOML, CSV, XML. Convert, validate, merge.
|
|
5
|
+
Author: Kryptorious Quantum Biosciences, Inc.
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://devflow.sh
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: click>=8.0
|
|
11
|
+
Requires-Dist: rich>=13.0
|
|
12
|
+
Requires-Dist: pyyaml>=6.0
|
|
13
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kryptorious-dataforge"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Universal data format converter — JSON, YAML, TOML, CSV, XML. Convert, validate, merge."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{name = "Kryptorious Quantum Biosciences, Inc."}]
|
|
13
|
+
dependencies = ["click>=8.0", "rich>=13.0", "pyyaml>=6.0", "tomli>=2.0; python_version < '3.11'"]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = ["pytest>=7.0", "black>=23.0"]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
dataforge = "dataforge.cli:main"
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://devflow.sh"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
where = ["src"]
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""DataForge CLI — Convert between any data format."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
FORMATS = ["json", "yaml", "yml", "toml", "csv", "xml"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _detect_format(path: str) -> str:
|
|
19
|
+
ext = Path(path).suffix.lower().lstrip(".")
|
|
20
|
+
if ext in FORMATS:
|
|
21
|
+
return "yaml" if ext == "yml" else ext
|
|
22
|
+
return ext
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _read_file(path: str, fmt: str = None):
|
|
26
|
+
fmt = fmt or _detect_format(path)
|
|
27
|
+
content = Path(path).read_text(encoding="utf-8")
|
|
28
|
+
|
|
29
|
+
if fmt == "json":
|
|
30
|
+
return json.loads(content)
|
|
31
|
+
elif fmt in ("yaml", "yml"):
|
|
32
|
+
import yaml
|
|
33
|
+
return yaml.safe_load(content)
|
|
34
|
+
elif fmt == "toml":
|
|
35
|
+
try:
|
|
36
|
+
import tomllib
|
|
37
|
+
except ImportError:
|
|
38
|
+
import tomli as tomllib
|
|
39
|
+
return tomllib.loads(content)
|
|
40
|
+
elif fmt == "csv":
|
|
41
|
+
import csv, io
|
|
42
|
+
reader = csv.DictReader(io.StringIO(content))
|
|
43
|
+
return [row for row in reader]
|
|
44
|
+
elif fmt == "xml":
|
|
45
|
+
import xml.etree.ElementTree as ET
|
|
46
|
+
return _xml_to_dict(ET.fromstring(content))
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError(f"Unsupported format: {fmt}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _write_file(data, path: str, fmt: str = None):
|
|
52
|
+
fmt = fmt or _detect_format(path)
|
|
53
|
+
|
|
54
|
+
if fmt == "json":
|
|
55
|
+
content = json.dumps(data, indent=2, ensure_ascii=False)
|
|
56
|
+
elif fmt in ("yaml", "yml"):
|
|
57
|
+
import yaml
|
|
58
|
+
content = yaml.dump(data, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
59
|
+
elif fmt == "toml":
|
|
60
|
+
raise click.ClickException("TOML output requires premium version. Upgrade at https://kryptorious.gumroad.com/l/jbvet")
|
|
61
|
+
elif fmt == "csv":
|
|
62
|
+
if not isinstance(data, list):
|
|
63
|
+
raise ValueError("CSV output requires list data")
|
|
64
|
+
import csv, io
|
|
65
|
+
output = io.StringIO()
|
|
66
|
+
if data:
|
|
67
|
+
writer = csv.DictWriter(output, fieldnames=data[0].keys())
|
|
68
|
+
writer.writeheader()
|
|
69
|
+
writer.writerows(data)
|
|
70
|
+
content = output.getvalue()
|
|
71
|
+
elif fmt == "xml":
|
|
72
|
+
content = _dict_to_xml(data)
|
|
73
|
+
else:
|
|
74
|
+
raise ValueError(f"Unsupported format: {fmt}")
|
|
75
|
+
|
|
76
|
+
Path(path).write_text(content, encoding="utf-8")
|
|
77
|
+
return content
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _xml_to_dict(element):
|
|
81
|
+
result = {}
|
|
82
|
+
if element.attrib:
|
|
83
|
+
result["@attributes"] = element.attrib
|
|
84
|
+
for child in element:
|
|
85
|
+
child_dict = _xml_to_dict(child)
|
|
86
|
+
tag = child.tag
|
|
87
|
+
if tag in result:
|
|
88
|
+
if not isinstance(result[tag], list):
|
|
89
|
+
result[tag] = [result[tag]]
|
|
90
|
+
result[tag].append(child_dict)
|
|
91
|
+
else:
|
|
92
|
+
result[tag] = child_dict
|
|
93
|
+
text = element.text.strip() if element.text else ""
|
|
94
|
+
if not result and text:
|
|
95
|
+
return text
|
|
96
|
+
if text:
|
|
97
|
+
result["#text"] = text
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _dict_to_xml(data, root_tag="root"):
|
|
102
|
+
import xml.etree.ElementTree as ET
|
|
103
|
+
import xml.dom.minidom as minidom
|
|
104
|
+
|
|
105
|
+
def _build(element, data):
|
|
106
|
+
if isinstance(data, dict):
|
|
107
|
+
for key, value in data.items():
|
|
108
|
+
if key.startswith("@"):
|
|
109
|
+
element.set(key[1:], str(value))
|
|
110
|
+
elif key == "#text":
|
|
111
|
+
element.text = str(value)
|
|
112
|
+
else:
|
|
113
|
+
child = ET.SubElement(element, key)
|
|
114
|
+
_build(child, value)
|
|
115
|
+
elif isinstance(data, list):
|
|
116
|
+
for item in data:
|
|
117
|
+
child = ET.SubElement(element, "item")
|
|
118
|
+
_build(child, item)
|
|
119
|
+
else:
|
|
120
|
+
element.text = str(data)
|
|
121
|
+
|
|
122
|
+
root = ET.Element(root_tag)
|
|
123
|
+
if isinstance(data, dict) and len(data) == 1:
|
|
124
|
+
tag, content = next(iter(data.items()))
|
|
125
|
+
root = ET.Element(tag)
|
|
126
|
+
_build(root, content)
|
|
127
|
+
else:
|
|
128
|
+
_build(root, data)
|
|
129
|
+
|
|
130
|
+
return minidom.parseString(ET.tostring(root, encoding="unicode")).toprettyxml(indent=" ")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@click.group()
|
|
134
|
+
@click.version_option(version="1.0.0", prog_name="dataforge")
|
|
135
|
+
def main():
|
|
136
|
+
"""DataForge — Convert between JSON, YAML, TOML, CSV, XML.
|
|
137
|
+
|
|
138
|
+
One command to convert, validate, and merge any data format.
|
|
139
|
+
"""
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@main.command()
|
|
144
|
+
@click.argument("input_path")
|
|
145
|
+
@click.argument("output_path")
|
|
146
|
+
@click.option("--from", "-f", "from_fmt", help="Input format (auto-detect if omitted)")
|
|
147
|
+
@click.option("--to", "-t", "to_fmt", help="Output format (auto-detect if omitted)")
|
|
148
|
+
@click.option("--pretty/--compact", default=True, help="Pretty-print output")
|
|
149
|
+
def convert(input_path, output_path, from_fmt, to_fmt, pretty):
|
|
150
|
+
"""Convert between data formats.
|
|
151
|
+
|
|
152
|
+
\b
|
|
153
|
+
Examples:
|
|
154
|
+
dataforge convert data.json output.yaml
|
|
155
|
+
dataforge convert data.csv output.json
|
|
156
|
+
dataforge convert config.toml config.json
|
|
157
|
+
dataforge convert data.json output.xml
|
|
158
|
+
"""
|
|
159
|
+
in_fmt = from_fmt or _detect_format(input_path)
|
|
160
|
+
out_fmt = to_fmt or _detect_format(output_path)
|
|
161
|
+
|
|
162
|
+
console.print()
|
|
163
|
+
console.print(Panel(f"[bold]DataForge Convert[/bold] — [cyan]{in_fmt}[/cyan] → [green]{out_fmt}[/green]",
|
|
164
|
+
border_style="blue"))
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
console.print(f"Reading [cyan]{input_path}[/cyan]...")
|
|
168
|
+
data = _read_file(input_path, in_fmt)
|
|
169
|
+
console.print(f"Writing [green]{output_path}[/green]...")
|
|
170
|
+
_write_file(data, output_path, out_fmt)
|
|
171
|
+
size = os.path.getsize(output_path)
|
|
172
|
+
console.print(f"[green]✓[/green] Converted {_format_size(size)} — {output_path}")
|
|
173
|
+
except Exception as e:
|
|
174
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
175
|
+
raise SystemExit(1)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@main.command()
|
|
179
|
+
@click.argument("path")
|
|
180
|
+
@click.option("--format", "-f", "fmt", help="Format (auto-detect if omitted)")
|
|
181
|
+
def validate(path, fmt):
|
|
182
|
+
"""Validate a data file's syntax.
|
|
183
|
+
|
|
184
|
+
\b
|
|
185
|
+
Examples:
|
|
186
|
+
dataforge validate config.yaml
|
|
187
|
+
dataforge validate data.json
|
|
188
|
+
"""
|
|
189
|
+
fmt = fmt or _detect_format(path)
|
|
190
|
+
console.print()
|
|
191
|
+
console.print(Panel(f"[bold]DataForge Validate[/bold] — [cyan]{path}[/cyan] ({fmt})",
|
|
192
|
+
border_style="blue"))
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
data = _read_file(path, fmt)
|
|
196
|
+
console.print(f"[green]✓[/green] Valid {fmt.upper()} — {_summarize(data)}")
|
|
197
|
+
except Exception as e:
|
|
198
|
+
console.print(f"[red]✗[/red] Invalid {fmt.upper()}: {e}")
|
|
199
|
+
raise SystemExit(1)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@main.command()
|
|
203
|
+
@click.argument("files", nargs=-1)
|
|
204
|
+
@click.argument("output")
|
|
205
|
+
@click.option("--format", "-f", "fmt", default="json", help="Output format")
|
|
206
|
+
def merge(files, output, fmt):
|
|
207
|
+
"""Merge multiple data files into one (premium feature).
|
|
208
|
+
|
|
209
|
+
\b
|
|
210
|
+
Example:
|
|
211
|
+
dataforge merge a.json b.json merged.json
|
|
212
|
+
"""
|
|
213
|
+
console.print()
|
|
214
|
+
console.print("[yellow]DataForge merge is a premium feature.[/yellow]")
|
|
215
|
+
console.print("Upgrade at https://kryptorious.gumroad.com/l/jbvet")
|
|
216
|
+
console.print()
|
|
217
|
+
console.print(f"Would merge {len(files)} files → {output} ({fmt})")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@main.command()
|
|
221
|
+
@click.argument("path")
|
|
222
|
+
def view(path):
|
|
223
|
+
"""Pretty-print any data file to terminal.
|
|
224
|
+
|
|
225
|
+
\b
|
|
226
|
+
Examples:
|
|
227
|
+
dataforge view config.yaml
|
|
228
|
+
dataforge view data.json
|
|
229
|
+
"""
|
|
230
|
+
fmt = _detect_format(path)
|
|
231
|
+
data = _read_file(path, fmt)
|
|
232
|
+
console.print()
|
|
233
|
+
console.print(Panel(f"[bold]{path}[/bold] ({fmt})", border_style="blue"))
|
|
234
|
+
console.print_json(json.dumps(data)) if isinstance(data, (dict, list)) else console.print(data)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _format_size(size: int) -> str:
|
|
238
|
+
if size < 1024:
|
|
239
|
+
return f"{size} B"
|
|
240
|
+
elif size < 1024 * 1024:
|
|
241
|
+
return f"{size / 1024:.1f} KB"
|
|
242
|
+
else:
|
|
243
|
+
return f"{size / (1024 * 1024):.1f} MB"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _summarize(data) -> str:
|
|
247
|
+
if isinstance(data, dict):
|
|
248
|
+
return f"{len(data)} keys"
|
|
249
|
+
elif isinstance(data, list):
|
|
250
|
+
return f"{len(data)} items"
|
|
251
|
+
else:
|
|
252
|
+
return str(type(data).__name__)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
if __name__ == "__main__":
|
|
256
|
+
main()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kryptorious-dataforge
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Universal data format converter — JSON, YAML, TOML, CSV, XML. Convert, validate, merge.
|
|
5
|
+
Author: Kryptorious Quantum Biosciences, Inc.
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://devflow.sh
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: click>=8.0
|
|
11
|
+
Requires-Dist: rich>=13.0
|
|
12
|
+
Requires-Dist: pyyaml>=6.0
|
|
13
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/dataforge/__init__.py
|
|
3
|
+
src/dataforge/cli.py
|
|
4
|
+
src/kryptorious_dataforge.egg-info/PKG-INFO
|
|
5
|
+
src/kryptorious_dataforge.egg-info/SOURCES.txt
|
|
6
|
+
src/kryptorious_dataforge.egg-info/dependency_links.txt
|
|
7
|
+
src/kryptorious_dataforge.egg-info/entry_points.txt
|
|
8
|
+
src/kryptorious_dataforge.egg-info/requires.txt
|
|
9
|
+
src/kryptorious_dataforge.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dataforge
|