Skip to content

Commit

Permalink
Fingerprints for Addons now exist.
Browse files Browse the repository at this point in the history
  • Loading branch information
DrRetro2033 committed Oct 15, 2024
1 parent 2b15766 commit ce0e4fd
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 16 deletions.
52 changes: 52 additions & 0 deletions bin/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "package:args/command_runner.dart";
import 'package:cli_spin/cli_spin.dart';
import 'file_pattern.dart';
import 'extensions.dart';
import 'scripting/addons.dart';
import "version_control/constellation.dart";

/// # `void` main(List<String> arguments)
Expand Down Expand Up @@ -34,6 +35,9 @@ Future<dynamic> main(List<String> arguments) async {
runner.addCommand(ConstellationGrowCommand());
runner.addCommand(ConstellationDeleteCommand());
runner.addCommand(UsersCommands());
runner.addCommand(CompileAddOnCommand());
runner.addCommand(ListAddOnsCommand());
runner.addCommand(ViewAddOnFingerprintCommand());
}
runner.addCommand(ReadPatternCommand());
runner.addCommand(WritePatternCommand());
Expand Down Expand Up @@ -248,3 +252,51 @@ class WritePatternCommand extends Command {
jsonDecode(argResults!.option("data")!));
}
}

class CompileAddOnCommand extends Command {
@override
String get description => "Compile an add-on for Arceus.";

@override
String get name => "compile";

CompileAddOnCommand() {
argParser.addOption("project-path", abbr: "p", mandatory: true);
}

@override
dynamic run() {
final constellation = Constellation(currentPath.fixPath());
AddOn(constellation,
projectPath: argResults!.option("project-path")!.fixPath())
.compile();
}
}

class ListAddOnsCommand extends Command {
@override
String get description => "Lists the add-ons in the constellation.";
@override
String get name => "add-ons";

@override
void run() {
Constellation(currentPath).displayAddOns();
}
}

class ViewAddOnFingerprintCommand extends Command {
@override
String get description => "View the fingerprint of an add-on.";
@override
String get name => "fingerprint";

ViewAddOnFingerprintCommand() {
argParser.addOption("name", abbr: "n", mandatory: true);
}

@override
dynamic run() {
Constellation(currentPath).displayAddOnFingerprint(argResults?["name"]);
}
}
133 changes: 117 additions & 16 deletions bin/scripting/addons.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/stdlib/core.dart';
import 'package:yaml/yaml.dart';
import '../version_control/constellation.dart';

class AddOn {
/// The path to the project folder. Does not need to be defined for already compiled addons.
String? projectPath;

/// The path to the addon file. Is required for both compilation and runtime.
String addonPath;
String? addonName;

final Constellation _constellation;
final Compiler _compiler = Compiler();

File get addonFile => File(addonPath);
File get addonFile =>
File('${_constellation.addonFolderPath}/${addonName!}.evc');

AddOn(this.addonPath, {this.projectPath});
AddOn(this._constellation, {this.addonName, this.projectPath});

void compile() {
if (projectPath == null) {
Expand All @@ -23,19 +30,37 @@ class AddOn {
final dir = Directory(projectPath!);
Map<String, Map<String, String>> packages = {"project": {}};
final files = dir
.listSync()
.listSync(recursive: true)
.where((element) => element.path.endsWith('.dart'))
.map((e) => e.path.split('/').last)
.map((e) => e.path)
.toList(); // Get all the dart files.

if (!files.contains("main.dart")) {
AddOnFingerprint y;
if (!dir
.listSync()
.map((e) => e.path)
.toList()
.any((e) => e.endsWith('addon.yaml') || e.endsWith('addon.yml'))) {
throw Exception(
"Cannot compile without an addon.yaml file in the project folder.");
} else {
File addonYaml = File('$projectPath/addon.yaml');
if (!addonYaml.existsSync()) {
addonYaml = File('$projectPath/addon.yml');
}
final addonYamlMap = loadYaml(addonYaml.readAsStringSync());
addonName = addonYamlMap['name'];
y = AddOnFingerprint.fromYaml(addonYamlMap);
}

if (!files.any((e) => e.endsWith('main.dart'))) {
throw Exception(
"Cannot compile without a main.dart file in the project folder with read and write functions.");
"Cannot compile without a main.dart file in the project folder with read, write, and isCompatible functions.");
} else {
final mainCode = File("$projectPath/main.dart").readAsStringSync();
if (!mainCode.contains("Map<String, dynamic> read(String file)") ||
!mainCode.contains(
"Map<String, dynamic> write(String file, Map<String, dynamic> dataMap)") ||
!mainCode
.contains("void write(String file, Map<String, dynamic> data)") ||
!mainCode.contains("bool isCompatible(String file)")) {
throw Exception(
"Cannot compile without a read, write, and or isCompatible function in the main.dart file.");
Expand All @@ -44,31 +69,107 @@ class AddOn {

for (var file in files) {
final code = File(file).readAsStringSync(); // Read the file.
packages["project"]?[file] = code; // Add the file to the package.
packages["project"]?[file] =
_changesImportsRelative(code); // Add the file to the package.
}

final addon = _compiler.compile(packages); // The compiled code.
addonFile.createSync(
recursive: true); // Create the compiled file for writing to.
addonFile
.writeAsBytesSync(addon.write()); // Write the compiled addon to disk.

addonFile.writeAsBytesSync(y.fingerprint.codeUnits, mode: FileMode.write);

addonFile.writeAsBytesSync(addon.write(),
mode: FileMode.append); // Write the compiled addon to disk.
}

dynamic read(String file) {
final bytecode = addonFile.readAsBytesSync().buffer.asByteData();
final runtime = Runtime(bytecode);
final runtime = Runtime(bytecode.buffer.asByteData());
return runtime.executeLib("project", "read", [$String(file)]);
}

void write(String file, Map<String, dynamic> dataMap) {
final bytecode = addonFile.readAsBytesSync().buffer.asByteData();
final runtime = Runtime(bytecode);
final runtime = Runtime(bytecode.buffer.asByteData());
runtime.executeLib("project", "write", [$String(file), $Map.wrap(dataMap)]);
}

String _changesImportsRelative(String code) {
final regex = RegExp(r"import '(.*)';");
final matches = regex.allMatches(code);
for (var match in matches) {
final path = match.group(1)!;
final newPath = "package:project/$path.dart";
code.replaceRange(match.start, match.end, "import '$newPath';");
}
return code;
}

Uint8List get bytecode {
final data = addonFile.readAsBytesSync();
List<String> lines =
utf8.decode(data.toList(), allowMalformed: true).split("\n");
List<String> fingerprintLines = [];
while (lines[0] != "<END_OF_FINGERPRINT>") {
fingerprintLines.add(lines.removeAt(0));
}
lines.removeAt(0);
return data.sublist(utf8.encode("${fingerprintLines.join("\n")}\n").length +
utf8.encode("<END_OF_FINGERPRINT>\n").length);
}

AddOnFingerprint get fingerprint {
final data = addonFile.readAsBytesSync();
List<String> lines =
utf8.decode(data.toList(), allowMalformed: true).split("\n");
List<String> fingerprintLines = [];
while (lines[0] != "<END_OF_FINGERPRINT>") {
fingerprintLines.add(lines.removeAt(0));
}
return AddOnFingerprint.fromLines(fingerprintLines);
}

bool isCompatible(String file) {
final bytecode = addonFile.readAsBytesSync().buffer.asByteData();
final runtime = Runtime(bytecode);
return runtime.executeLib("project", "isCompatible", [$String(file)]);
}

void printFingerprint() {
print(fingerprint);
}
}

class AddOnFingerprint {
final String name;
final String author;
final String version;
final String description;
final YamlList compatibleFiles;

AddOnFingerprint(this.name, this.author, this.version, this.description,
this.compatibleFiles);

String get fingerprint {
String x = '$name\n$author\n$version\n$description\n';
for (var file in compatibleFiles) {
x += '$file\n';
}
x += "<END_OF_FINGERPRINT>\n";
return x;
}

factory AddOnFingerprint.fromYaml(YamlMap yaml) {
return AddOnFingerprint(yaml['name'], yaml['author'], yaml['version'],
yaml['description'], yaml['compatible-files']);
}

factory AddOnFingerprint.fromLines(List<String> lines) {
return AddOnFingerprint(lines[0], lines[1], lines[2], lines[3],
YamlList.wrap(lines.sublist(4)));
}

@override
String toString() {
return "Name: $name\nAuthor: $author\nVersion: $version\nDescription: $description\nCompatible Files: $compatibleFiles";
}
}
20 changes: 20 additions & 0 deletions bin/version_control/constellation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../extensions.dart';

import 'star.dart';
import 'dossier.dart';
import '../scripting/addons.dart';
import 'users.dart';

/// # `class` Constellation
Expand Down Expand Up @@ -36,8 +37,12 @@ class Constellation {
/// The path to the folder the constellation stores its data in. (The `.constellation` folder)
String get constellationPath => "$path/.constellation";

/// The path to the folder the constellation stores its addons in.
String get addonFolderPath => "$constellationPath/addons";

/// Fetches a directory object that represents the `addonFolderPath` folder.
Directory get addonDirectory => Directory(addonFolderPath);

/// Fetches a directory object that represents the `constellationPath` folder.
Directory get constellationDirectory => Directory(constellationPath);

Expand Down Expand Up @@ -150,6 +155,21 @@ class Constellation {
static bool checkForConstellation(String path) {
return Directory("$path/.constellation").existsSync();
}

void displayAddOns() {
for (var file in addonDirectory.listSync()) {
print('- ${file.path.fixPath().split("/").last.split(".").first}');
}
}

void displayAddOnFingerprint(argResult) {
if (argResult == null) {
print("No add-on specified. Use --help for more information.");
return;
}
AddOn addOn = AddOn(this, addonName: argResult);
print(addOn.fingerprint);
}
}

/// # `class` Starmap
Expand Down

0 comments on commit ce0e4fd

Please sign in to comment.