Files
ObsidianDragon/scripts/json2toml.py
dan_s c809666624 ObsidianDragon - DragonX ImGui Wallet
Full-node GUI wallet for DragonX cryptocurrency.
Built with Dear ImGui, SDL3, and OpenGL3/DX11.

Features:
- Send/receive shielded and transparent transactions
- Autoshield with merged transaction display
- Built-in CPU mining (xmrig)
- Peer management and network monitoring
- Wallet encryption with PIN lock
- QR code generation for receive addresses
- Transaction history with pagination
- Console for direct RPC commands
- Cross-platform (Linux, Windows)
2026-02-27 00:26:01 -06:00

179 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""Convert DragonX ui.json / ui-dark.json / ui-light.json to TOML format.
Usage:
python3 scripts/json2toml.py res/themes/ui.json res/themes/ui.toml
python3 scripts/json2toml.py res/themes/ui-dark.json res/themes/ui-dark.toml
python3 scripts/json2toml.py res/themes/ui-light.json res/themes/ui-light.toml
python3 scripts/json2toml.py --all # converts all three
"""
import json
import sys
import os
import re
from collections import OrderedDict
# Keys that need quoting in TOML because they contain special chars
def needs_quoting(key):
# TOML bare keys: [A-Za-z0-9_-]+
return not re.match(r'^[A-Za-z0-9_-]+$', key)
def quote_key(key):
if needs_quoting(key):
return f'"{key}"'
return key
def format_value(val):
"""Format a Python value as a TOML value string."""
if isinstance(val, bool):
return "true" if val else "false"
elif isinstance(val, int):
return str(val)
elif isinstance(val, float):
# Ensure floats always have a decimal point
s = repr(val)
if '.' not in s and 'e' not in s and 'E' not in s:
s += '.0'
return s
elif isinstance(val, str):
# Escape backslashes and quotes
escaped = val.replace('\\', '\\\\').replace('"', '\\"')
return f'"{escaped}"'
elif isinstance(val, list):
parts = [format_value(v) for v in val]
return f'[{", ".join(parts)}]'
else:
return repr(val)
def is_simple_leaf(obj):
"""Check if an object is a simple leaf that can be an inline table.
Simple leafs: {"size": X}, {"color": "..."}, {"height": X}, or small
objects with only primitive values and no nested objects."""
if not isinstance(obj, dict):
return False
for v in obj.values():
if isinstance(v, (dict, list)):
return False
# Keep objects with many keys as sections (threshold: 6 keys)
return len(obj) <= 6
def is_array_of_objects(val):
"""Check if val is an array of objects (needs [[array.of.tables]])."""
return isinstance(val, list) and all(isinstance(v, dict) for v in val) and len(val) > 0
def write_inline_table(obj):
"""Write a dict as a TOML inline table."""
parts = []
for k, v in obj.items():
parts.append(f'{quote_key(k)} = {format_value(v)}')
return '{ ' + ', '.join(parts) + ' }'
def emit_toml(data, lines, prefix='', depth=0):
"""Recursively emit TOML from a parsed JSON dict."""
# Separate keys into: scalars/arrays, simple-leaf objects, complex objects, array-of-tables
scalars = []
inline_leaves = []
complex_tables = []
array_tables = []
for key, val in data.items():
# Skip _comment keys (we'll handle them differently)
if key.startswith('_comment'):
continue
if isinstance(val, dict):
if is_simple_leaf(val):
inline_leaves.append((key, val))
else:
complex_tables.append((key, val))
elif is_array_of_objects(val):
array_tables.append((key, val))
else:
scalars.append((key, val))
# Emit scalars first
for key, val in scalars:
lines.append(f'{quote_key(key)} = {format_value(val)}')
# Emit inline leaf objects (like { size = 42.0 })
for key, val in inline_leaves:
lines.append(f'{quote_key(key)} = {write_inline_table(val)}')
# Emit complex sub-tables with [section] headers
for key, val in complex_tables:
subprefix = f'{prefix}.{key}' if prefix else key
lines.append('')
lines.append(f'[{subprefix}]')
emit_toml(val, lines, subprefix, depth + 1)
# Emit array-of-tables with [[section]] headers
for key, val in array_tables:
subprefix = f'{prefix}.{key}' if prefix else key
for item in val:
lines.append('')
lines.append(f'[[{subprefix}]]')
if isinstance(item, dict):
for ik, iv in item.items():
if isinstance(iv, dict):
# Nested object inside array item — inline table
lines.append(f'{quote_key(ik)} = {write_inline_table(iv)}')
else:
lines.append(f'{quote_key(ik)} = {format_value(iv)}')
def convert_file(input_path, output_path):
"""Convert a JSON theme file to TOML."""
with open(input_path) as f:
data = json.load(f, object_pairs_hook=OrderedDict)
lines = []
# Add header comments (converted from _comment_* keys)
for key, val in data.items():
if key.startswith('_comment') and isinstance(val, str):
if val:
lines.append(f'# {val}')
else:
lines.append('')
if lines:
lines.append('')
# Emit all non-comment content
emit_toml(data, lines)
# Clean up extra blank lines
output = '\n'.join(lines).strip() + '\n'
# Collapse 3+ consecutive newlines to 2
while '\n\n\n' in output:
output = output.replace('\n\n\n', '\n\n')
with open(output_path, 'w') as f:
f.write(output)
print(f'Converted: {input_path} -> {output_path}')
print(f' JSON: {os.path.getsize(input_path):,} bytes')
print(f' TOML: {os.path.getsize(output_path):,} bytes')
print(f' Reduction: {100 - 100*os.path.getsize(output_path)/os.path.getsize(input_path):.0f}%')
def main():
if len(sys.argv) == 2 and sys.argv[1] == '--all':
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
themes = os.path.join(base, 'res', 'themes')
for name in ['ui', 'ui-dark', 'ui-light']:
inp = os.path.join(themes, f'{name}.json')
out = os.path.join(themes, f'{name}.toml')
if os.path.exists(inp):
convert_file(inp, out)
else:
print(f'Skipping (not found): {inp}')
elif len(sys.argv) == 3:
convert_file(sys.argv[1], sys.argv[2])
else:
print(__doc__)
sys.exit(1)
if __name__ == '__main__':
main()