// Reads shared/fabledsword.tokens.json and emits lib/theme/tokens.dart. // Run via `dart run tool/gen_tokens.dart`. The generated file is // committed (CI validates it matches the JSON to catch drift). // // Emits three flat classes mirroring the source JSON: // - FabledSwordDarkTokens (surface colors for dark mode) // - FabledSwordLightTokens (surface colors for light mode) // - FabledSwordFlatTokens (colors that don't change between modes, // plus radii + font families) // // Plus a back-compat FabledSwordTokens alias that re-exports the dark // surface colors + flat — kept so any code that still reads // FabledSwordTokens.obsidian (etc.) continues to work during the // migration. Safe to delete once theme_extension.dart switches over. import 'dart:convert'; import 'dart:io'; void main() { final jsonFile = File('shared/fabledsword.tokens.json'); final tokens = jsonDecode(jsonFile.readAsStringSync()) as Map; final colorsRoot = (tokens['colors'] as Map).cast(); final dark = (colorsRoot['dark'] as Map).cast(); final light = (colorsRoot['light'] as Map).cast(); final flat = (colorsRoot['flat'] as Map).cast(); final radii = (tokens['radii'] as Map).cast(); final fonts = (tokens['fonts'] as Map).cast(); final out = StringBuffer() ..writeln('// GENERATED — do not edit. Source: shared/fabledsword.tokens.json') ..writeln('// Run `dart run tool/gen_tokens.dart` to regenerate.') ..writeln("import 'package:flutter/material.dart';") ..writeln(); void writeColorClass(String className, Map colors) { out.writeln('class $className {'); colors.forEach((name, hex) { final value = hex.replaceFirst('#', '0xFF'); out.writeln(' static const Color ${_camel(name)} = Color($value);'); }); out.writeln('}'); out.writeln(); } writeColorClass('FabledSwordDarkTokens', dark); writeColorClass('FabledSwordLightTokens', light); out.writeln('class FabledSwordFlatTokens {'); flat.forEach((name, hex) { final value = hex.replaceFirst('#', '0xFF'); out.writeln(' static const Color ${_camel(name)} = Color($value);'); }); radii.forEach((name, px) { final v = px.replaceAll('px', ''); out.writeln(' static const double radius${_pascal(name)} = $v;'); }); fonts.forEach((name, family) { out.writeln(' static const String font${_pascal(name)} = ${jsonEncode(family)};'); }); out.writeln('}'); out.writeln(); // Back-compat alias: dark surface colors + flat. Lets existing // call sites that read FabledSwordTokens.obsidian etc. keep working // until they're migrated to the explicit dark/light/flat classes. out.writeln('/// Back-compat alias — dark surface tokens + flat. Prefer the explicit'); out.writeln('/// FabledSwordDarkTokens / FabledSwordLightTokens / FabledSwordFlatTokens.'); out.writeln('class FabledSwordTokens {'); dark.forEach((name, hex) { final value = hex.replaceFirst('#', '0xFF'); out.writeln(' static const Color ${_camel(name)} = Color($value);'); }); flat.forEach((name, hex) { final value = hex.replaceFirst('#', '0xFF'); out.writeln(' static const Color ${_camel(name)} = Color($value);'); }); radii.forEach((name, px) { final v = px.replaceAll('px', ''); out.writeln(' static const double radius${_pascal(name)} = $v;'); }); fonts.forEach((name, family) { out.writeln(' static const String font${_pascal(name)} = ${jsonEncode(family)};'); }); out.writeln('}'); File('lib/theme/tokens.dart').writeAsStringSync(out.toString()); stdout.writeln('wrote lib/theme/tokens.dart'); } // Hyphens become camelCase boundaries: on-action → onAction. String _camel(String s) { final parts = s.split('-'); return parts.first + parts.skip(1).map((p) => p[0].toUpperCase() + p.substring(1)).join(); } String _pascal(String s) => s[0].toUpperCase() + s.substring(1);