From 2fc40453def0a838c8fa6225ff2d737e83616a42 Mon Sep 17 00:00:00 2001
From: Kir_Antipov <kp.antipov@gmail.com>
Date: Fri, 25 Nov 2022 01:35:27 +0300
Subject: [PATCH] `ForgeModMetadata` refactoring

---
 README.md                                     |  34 +-----
 src/metadata/forge/forge-mod-config.ts        |  44 ++++++++
 .../forge/forge-mod-metadata-reader.ts        |  15 ++-
 src/metadata/forge/forge-mod-metadata.ts      | 104 +++++++++++++-----
 test/content/forge/mods.toml                  |   7 +-
 5 files changed, 137 insertions(+), 67 deletions(-)
 create mode 100644 src/metadata/forge/forge-mod-config.ts

diff --git a/README.md b/README.md
index 084bdf0..ec3061b 100644
--- a/README.md
+++ b/README.md
@@ -193,21 +193,9 @@ Can be automatically retrieved from the config file of your mod:
 
 - `mods.toml` (Forge)
 
-  - Custom `mc-publish` field *(recommended)*:
+  - Custom `mc-publish` field:
       ```toml
-      [custom.mc-publish]
-          modrinth="AANobbMI"
-      ```
-
-  - Custom `projects` field:
-      ```toml
-      [custom.projects]
-          modrinth="AANobbMI"
-      ```
-
-  - `projects` field:
-      ```toml
-      [projects]
+      [mc-publish]
           modrinth="AANobbMI"
       ```
 
@@ -311,21 +299,9 @@ Can be automatically retrieved from the config file of your mod:
 
 - `mods.toml` (Forge)
 
-  - Custom `mc-publish` field *(recommended)*:
+  - Custom `mc-publish` field:
       ```toml
-      [custom.mc-publish]
-          curseforge=394468
-      ```
-
-  - Custom `projects` field:
-      ```toml
-      [custom.projects]
-          curseforge=394468
-      ```
-
-  - `projects` field:
-      ```toml
-      [projects]
+      [mc-publish]
           curseforge=394468
       ```
 
@@ -617,7 +593,7 @@ Can be automatically retrieved from the config file of your mod:
     versionRange="0.3.0"
     ordering="NONE"
     side="BOTH"
-    [dependencies.mod-id.custom.mc-publish]
+    [dependencies.mod-id.mc-publish]
         ignore=false # `mc-publish` will ignore this dependency, if `ignore` is set to true
         modrinth="included-dependency-forge" # Modrinth's project slug
         curseforge="included-dependency-forge" # CurseForge's project slug
diff --git a/src/metadata/forge/forge-mod-config.ts b/src/metadata/forge/forge-mod-config.ts
new file mode 100644
index 0000000..3608d46
--- /dev/null
+++ b/src/metadata/forge/forge-mod-config.ts
@@ -0,0 +1,44 @@
+// https://forge.gemwire.uk/wiki/Mods.toml
+type ForgeModConfig = {
+    // Non-Mod-Specific Properties
+    modLoader: string;
+    loaderVersion: string;
+    license: string;
+    showAsResourcePack?: boolean;
+    properties?: Record<string, unknown>;
+    issueTrackerURL?: string;
+
+    // Mod Properties
+    mods: {
+        modId: string;
+        namespace?: string;
+        version?: string;
+        displayName?: string;
+        description?: string;
+        logoFile?: string;
+        logoBlur?: boolean;
+        updateJSONURL?: string;
+        modproperties?: Record<string, unknown>;
+        credits?: string;
+        authors?: string;
+        displayURL?: string;
+        displayTest?: "MATCH_VERSION" | "IGNORE_SERVER_VERSION" | "IGNORE_ALL_VERSION" | "NONE";
+    }[];
+
+    // Dependency Configurations
+    dependencies?: Record<string, ({
+        modId: string;
+        mandatory: boolean;
+        incompatible?: boolean;
+        embedded?: boolean;
+        versionRange?: string;
+        ordering?: "BEFORE" | "AFTER" | "NONE";
+        side?: "CLIENT" | "SERVER" | "BOTH";
+    } & Record<string, any>)[]>;
+} & Record<string, any>;
+
+namespace ForgeModConfig {
+    export const FILENAME = "META-INF/mods.toml";
+}
+
+export default ForgeModConfig;
diff --git a/src/metadata/forge/forge-mod-metadata-reader.ts b/src/metadata/forge/forge-mod-metadata-reader.ts
index d4af1c7..cf3f751 100644
--- a/src/metadata/forge/forge-mod-metadata-reader.ts
+++ b/src/metadata/forge/forge-mod-metadata-reader.ts
@@ -1,18 +1,21 @@
-import ModMetadata from "../../metadata/mod-metadata";
+import ModMetadata from "../mod-metadata";
 import toml from "toml";
-import ZippedModMetadataReader from "../../metadata/zipped-mod-metadata-reader";
+import ZippedModMetadataReader from "../zipped-mod-metadata-reader";
 import ForgeModMetadata from "./forge-mod-metadata";
+import ForgeModConfig from "./forge-mod-config";
 
-export default class ForgeModMetadataReader extends ZippedModMetadataReader {
+class ForgeModMetadataReader extends ZippedModMetadataReader<ForgeModConfig> {
     constructor() {
-        super("META-INF/mods.toml");
+        super(ForgeModConfig.FILENAME);
     }
 
-    protected loadConfig(buffer: Buffer): Record<string, unknown> {
+    protected loadConfig(buffer: Buffer): ForgeModConfig {
         return toml.parse(buffer.toString("utf8"));
     }
 
-    protected createMetadataFromConfig(config: Record<string, unknown>): ModMetadata {
+    protected createMetadataFromConfig(config: ForgeModConfig): ModMetadata {
         return new ForgeModMetadata(config);
     }
 }
+
+export default ForgeModMetadataReader;
diff --git a/src/metadata/forge/forge-mod-metadata.ts b/src/metadata/forge/forge-mod-metadata.ts
index 23ed705..3791b62 100644
--- a/src/metadata/forge/forge-mod-metadata.ts
+++ b/src/metadata/forge/forge-mod-metadata.ts
@@ -1,29 +1,74 @@
-import ModConfig from "../../metadata/mod-config";
-import ModConfigDependency from "../../metadata/mod-config-dependency";
-import Dependency from "../../metadata/dependency";
-import DependencyKind from "../../metadata/dependency-kind";
+import action from "../../../package.json";
+import Dependency from "../dependency";
+import DependencyKind from "../dependency-kind";
+import ForgeModConfig from "./forge-mod-config";
+import ModMetadata from "../mod-metadata";
+import PublisherTarget from "../../publishing/publisher-target";
 
-const ignoredByDefault = ["minecraft", "java", "forge"];
-function createDependency(body: any): Dependency {
-    return new ModConfigDependency({
-        ignore: ignoredByDefault.includes(body.modId),
-        ...body,
-        id: body.modId,
-        version: body.versionRange,
-        kind: body.incompatible && DependencyKind.Breaks || body.embedded && DependencyKind.Includes || body.mandatory && DependencyKind.Depends || DependencyKind.Recommends
-    });
+type ForgeDependency = ForgeModConfig["dependencies"][string][number];
+
+function getDependencies(config: ForgeModConfig): Dependency[] {
+    return Object
+        .values(config.dependencies || {})
+        .filter(x => Array.isArray(x))
+        .flatMap(x => x)
+        .map(parseDependency)
+        .filter((x, i, self) => self.findIndex(y => x.id === y.id && x.kind === y.kind) === i);
 }
 
-export default class ForgeModMetadata extends ModConfig {
-    public readonly id: string;
-    public readonly name: string;
-    public readonly version: string;
-    public readonly loaders: string[];
-    public readonly dependencies: Dependency[];
+function parseDependency(body: ForgeDependency): Dependency {
+    const id = body.modId;
+    const kind = body.incompatible && DependencyKind.Breaks || body.embedded && DependencyKind.Includes || body.mandatory && DependencyKind.Depends || DependencyKind.Recommends;
+    const version = body.versionRange;
+    const ignore = body[action.name]?.ignore ?? body.custom?.[action.name]?.ignore ?? body.ignore ?? isDependencyIgnoredByDefault(id);
+    const aliases = new Map<PublisherTarget, string>();
+    for (const target of PublisherTarget.getValues()) {
+        const targetName = PublisherTarget.toString(target).toLowerCase();
+        const alias = body[action.name]?.[targetName] ?? body.custom?.[action.name]?.[targetName];
+        if (alias) {
+            aliases.set(target, String(alias));
+        }
+    }
 
-    constructor(config: Record<string, unknown>) {
-        super(config);
-        const mods = Array.isArray(this.config.mods) && <any[]>this.config.mods || [];
+    return Dependency.create({ id, kind, version, ignore, aliases });
+}
+
+const ignoredByDefault = [
+    "minecraft",
+    "java",
+    "forge",
+];
+function isDependencyIgnoredByDefault(id: string): boolean {
+    return ignoredByDefault.includes(id);
+}
+
+function getProjects(config: ForgeModConfig): Map<PublisherTarget, string> {
+    const projects = new Map();
+    for (const target of PublisherTarget.getValues()) {
+        const targetName = PublisherTarget.toString(target).toLowerCase();
+        const projectId = config[action.name]?.[targetName]
+            ?? config.custom?.[action.name]?.[targetName]
+            ?? config.projects?.[targetName]
+            ?? config.custom?.projects?.[targetName];
+
+        if (projectId) {
+            projects.set(target, String(projectId));
+        }
+    }
+    return projects;
+}
+
+class ForgeModMetadata implements ModMetadata {
+    readonly id: string;
+    readonly name: string;
+    readonly version: string;
+    readonly loaders: string[];
+    readonly dependencies: Dependency[];
+
+    private readonly _projects: Map<PublisherTarget, string>;
+
+    constructor(config: ForgeModConfig) {
+        const mods = Array.isArray(config.mods) && config.mods || [];
         const mod = mods[0];
         if (!mod) {
             throw new Error("At least one mod should be specified");
@@ -33,11 +78,14 @@ export default class ForgeModMetadata extends ModConfig {
         this.name = mod.displayName || this.id;
         this.version = mod.version || "*";
         this.loaders = ["forge"];
-        this.dependencies = Object
-            .values(this.config.dependencies || {})
-            .filter(Array.isArray)
-            .flatMap(x => x)
-            .map(createDependency)
-            .filter((x, i, self) => self.findIndex(y => x.id === y.id && x.kind === y.kind) === i);
+        this.dependencies = getDependencies(config);
+
+        this._projects = getProjects(config);
+    }
+
+    getProjectId(project: PublisherTarget): string | undefined {
+        return this._projects.get(project);
     }
 }
+
+export default ForgeModMetadata;
diff --git a/test/content/forge/mods.toml b/test/content/forge/mods.toml
index 124c7ae..01c3140 100644
--- a/test/content/forge/mods.toml
+++ b/test/content/forge/mods.toml
@@ -13,7 +13,7 @@ license="MIT"
     Example mod
     '''
 
-[projects]
+[mc-publish]
     modrinth="AANobbMI"
 [custom.projects]
     curseforge=394468
@@ -46,13 +46,12 @@ license="MIT"
     versionRange="0.2.0"
     ordering="NONE"
     side="BOTH"
-    [dependencies.example-mod.projects]
+    [dependencies.example-mod.mc-publish]
         modrinth="AAAA"
-    [dependencies.example-mod.custom.projects]
         curseforge=42
+        ignore=true
     [dependencies.example-mod.custom.mc-publish]
         github="v0.2.0"
-        ignore=true
 
 
 [[dependencies.example-mod]]