CREATIVEWITY / SCRIPT SOURCEPython 3.9+ / standard library / MIT

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()