import "dart:io"; import "package:build/build.dart"; import "package:yaml/yaml.dart"; String joinString(List strings, String sep) { String joined = ""; for (final str in strings) { joined += "$str$sep"; } return joined; } String _getAttributeType(dynamic data) { if (data is String) { return data; } return (data as YamlMap)["type"]!; } bool _getAttributeDeserialise(dynamic data) { if (data is YamlMap) { return data["deserialise"] ?? false; } return false; } String _generateAttributes(YamlMap data) { if (data["attributes"] == null) { return ""; } List attrs = []; data["attributes"].forEach((key, value) { final type_ = _getAttributeType(value); assert(!type_.endsWith(" ")); attrs.add("\tfinal $type_ $key;"); }); final joined = joinString(attrs, "\n"); return joined.substring(0, joined.length - 1); } String _generateInitializers(YamlMap data) { if (data["attributes"] == null) { return ""; } List attrs = []; data["attributes"].forEach((key, value) { final type_ = _getAttributeType(value); final defaultValue = value is YamlMap ? value["default"] : null; final defaultString = defaultValue != null ? " = const $defaultValue" : ""; final isRequired = !type_.endsWith("?") && defaultValue == null; final requiredString = isRequired ? "required " : ""; attrs.add("\t\t${requiredString}this.$key$defaultString,"); }); final joined = joinString(attrs, "\n"); return joined.substring(0, joined.length - 1); } String _generateToJson(YamlMap data) { String json = ''' \t@override \tMap toJson() => {\n '''; if (data["attributes"] != null) { data["attributes"].forEach((key, _) { json += "\t\t\"$key\": $key,\n"; }); } json += ''' \t\t"type": "${data["name"]!}" \t};\n '''; return json; } String _generateFromJson(YamlMap data) { final name = data["name"]!; String json = "\tstatic $name fromJson(Map json) => $name("; if (data["attributes"] != null) { json += "\n"; data["attributes"].forEach((key, value) { final type_ = _getAttributeType(value); if (type_.startsWith("List<")) { final exp = RegExp(r"List\<(.*?)\>"); final listType = exp.firstMatch(type_)!.group(1)!; final suffix = type_.endsWith("?") ? " ?? []" : "!"; String attr = '(json["$key"]$suffix)'; if (_getAttributeDeserialise(value)) { attr += ".map((item) => $listType.fromJson(item))"; } json += "\t\t$key: List<$listType>.from($attr),\n"; } else { if (_getAttributeDeserialise(value)) { if (type_.endsWith("?")) { final attrTypeNoNull = type_.substring(0, type_.length - 1); json += '\t\t$key: json["$key"] != null ? $attrTypeNoNull.fromJson(json["$key"]) : null,\n'; } else { json += '\t\t$key: $type_.fromJson(json["$key"]!),\n'; } } else { final nullString = type_.endsWith("?") ? "" : "!"; json += '\t\t$key: json["$key"]$nullString,\n'; } } }); json += "\t);"; } else { json += ");"; } return json; } String generateClassFromData(YamlMap data) { String impls = ""; for (final impl in data["implements"]) { impls += "$impl, "; } final constructor = data["attributes"] != null ? ''' \t${data["name"]!}({ ${_generateInitializers(data)} \t}); ''' : '\t${data["name"]!}();'; return ''' class ${data["name"]!} extends ${data["extends"]} implements ${impls.substring(0, impls.length - 2)} { ${_generateAttributes(data)} $constructor \t// JSON Stuff ${_generateToJson(data)} ${_generateFromJson(data)} } '''; } String _generateBuilder(String name, String baseClass, YamlList classes) { String func = ''' $baseClass? get${name}FromJson(Map json) { \tswitch(json["type"]!) { '''; for (final class_ in classes) { final name = class_["name"]!; func += '\t\tcase "$name": return $name.fromJson(json);\n'; } func += "\t\tdefault: return null;\n"; func += "\t}\n}"; return func; } String generateFileFromData(YamlMap data) { String file = ''' //// AUTO-GENERATED by build_runner //// /// DO NOT EDIT BY HAND part of "${data["partof"]!}"; '''; data["classes"].forEach((attributes) { file += generateClassFromData(attributes); }); if (data["generate_builder"]) { file += "\n\n"; file += _generateBuilder( data["builder_name"], data["builder_baseclass"], data["classes"] ); } return file; } class DataClassBuilder implements Builder { @override Future build(BuildStep step) async { final file = File(step.inputId.path); final data = loadYaml(await file.readAsString()); for (final asset in step.allowedOutputs) { if (asset.path.endsWith("commands.moxxy.dart")) { await step.writeAsString( asset, generateFileFromData(data["files"]["commands"]!) ); } else if (asset.path.endsWith("events.moxxy.dart")) { await step.writeAsString( asset, generateFileFromData(data["files"]["events"]!) ); } } } @override final buildExtensions = const { "data_classes.yaml": [ "shared/commands.moxxy.dart", "shared/events.moxxy.dart" ] }; } Builder dataClassBuilder(BuilderOptions options) => DataClassBuilder();