Readable source
#!/usr/bin/env python3
"""Convert nested value/$value tokens to CSS variables. Python 3.9+, standard library."""
import argparse,json,re
from pathlib import Path
def flatten(node,prefix=()):
if isinstance(node,dict) and ('$value' in node or 'value' in node):
value=node.get('$value',node.get('value'))
if isinstance(value,dict) and set(value)=={'value','unit'}:value=str(value['value'])+str(value['unit'])
yield prefix,value
elif isinstance(node,dict):
for k,v in node.items():
if not k.startswith('$'):yield from flatten(v,prefix+(k,))
else:yield prefix,node
def export(data):
lines=[];names=set()
for path,value in flatten(data):
name=re.sub('[^a-z0-9-]+','-', '-'.join(path).lower()).strip('-')
if not name or name in names:raise ValueError('Empty or duplicate normalised token name: '+name)
if not isinstance(value,(str,int,float)) or isinstance(value,bool):raise ValueError('Unsupported value for '+name)
value=str(value)
if any(c in value for c in ';{}<>\r\n') or not value.strip():raise ValueError('Unsafe or empty CSS value for '+name)
names.add(name);lines.append(' --'+name+': '+value+';')
return ':root {\n'+'\n'.join(lines)+'\n}\n'
def main():
p=argparse.ArgumentParser(description=__doc__);p.add_argument('input',type=Path);p.add_argument('--output',type=Path);p.add_argument('--force',action='store_true');a=p.parse_args()
try:
data=json.loads(a.input.read_text(encoding='utf-8'))
if not isinstance(data,dict):raise ValueError('Top-level tokens must be an object.')
css=export(data)
if a.output:
if a.output.resolve()==a.input.resolve():raise ValueError('Output must differ from input.')
if a.output.exists() and not a.force:raise ValueError('Output exists. Choose another file or use --force.')
a.output.write_text(css,encoding='utf-8')
else:print(css,end='')
except (ValueError,OSError) as e:p.error(str(e))
if __name__=='__main__':main()