// Extracts TMemo Lines.Strings from example/pq/Config.dfm into a Dart const map. // Usage: // dart run tool/dfm_extract.dart [input_dfm] [output_dart] // Defaults: // input_dfm: example/pq/Config.dfm // output_dart: lib/data/pq_config_data.dart import 'dart:convert'; import 'dart:io'; void main(List args) { final inputPath = args.isNotEmpty ? args[0] : 'example/pq/Config.dfm'; final outputPath = args.length > 1 ? args[1] : 'lib/data/pq_config_data.dart'; final file = File(inputPath); if (!file.existsSync()) { stderr.writeln('Input file not found: $inputPath'); exitCode = 1; return; } final lines = file.readAsLinesSync(); final data = _parseMemos(lines); _writeDart(outputPath, data); _printSummary(outputPath, data); } Map> _parseMemos(List lines) { final result = >{}; String? currentMemo; bool inLines = false; for (final rawLine in lines) { final line = rawLine.trimRight(); final trimmed = line.trimLeft(); // Detect TMemo object names. if (trimmed.startsWith('object ') && trimmed.contains(': TMemo')) { final token = trimmed.split(' ')[1]; currentMemo = token.contains(':') ? token.split(':').first : token; inLines = false; continue; } // Detect start of Lines.Strings block. if (trimmed.contains('Lines.Strings')) { if (currentMemo == null) continue; inLines = true; result[currentMemo] = []; continue; } if (!inLines || currentMemo == null) { continue; } // End of block. if (trimmed == ')') { inLines = false; continue; } // Extract string literal line (may end with "')" on last item). final hasClosing = trimmed.endsWith(')'); final value = _decodeDelphiLiteral(line, stripTrailingParen: hasClosing); if (value.isNotEmpty) { result[currentMemo]!.add(value); } // Handle closing parenthesis on same line. if (hasClosing) { inLines = false; } } return result; } String _decodeDelphiLiteral(String line, {required bool stripTrailingParen}) { // Remove trailing ')' only when it denotes end-of-block. var work = line; if (stripTrailingParen && work.trimRight().endsWith(')')) { work = work.substring(0, work.lastIndexOf(')')); } final buffer = StringBuffer(); var i = 0; while (i < work.length) { final ch = work[i]; if (ch == '\'') { // Quoted segment. i++; final start = i; while (i < work.length && work[i] != '\'') { i++; } buffer.write(work.substring(start, i)); i++; // Skip closing quote. } else if (ch == '#') { // Numeric char code segment. i++; final start = i; while (i < work.length && _isDigit(work.codeUnitAt(i))) { i++; } final code = int.parse(work.substring(start, i)); buffer.write(String.fromCharCode(code)); } else { i++; // Ignore other characters between tokens. } } return buffer.toString(); } bool _isDigit(int charCode) => charCode >= 48 && charCode <= 57; void _writeDart(String outputPath, Map> data) { final buffer = StringBuffer() ..writeln('// GENERATED CODE - DO NOT EDIT BY HAND.') ..writeln( '// Generated by tool/dfm_extract.dart from example/pq/Config.dfm', ) ..writeln('') ..writeln('const Map> pqConfigData = {'); final sortedKeys = data.keys.toList()..sort(); for (final key in sortedKeys) { final encodedKey = jsonEncode(key); final encodedList = jsonEncode(data[key]); buffer.writeln(' $encodedKey: $encodedList,'); } buffer.writeln('};'); File(outputPath) ..createSync(recursive: true) ..writeAsStringSync(buffer.toString()); } void _printSummary(String outputPath, Map> data) { stdout.writeln('Wrote $outputPath'); for (final entry in data.entries) { stdout.writeln(' - ${entry.key}: ${entry.value.length} items'); } }