moxxyv2_builders/lib/dataclasses.dart

228 lines
5.4 KiB
Dart

import "dart:io";
import "package:build/build.dart";
import "package:yaml/yaml.dart";
String joinString(List<String> 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<String> 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<String> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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();