summaryrefslogtreecommitdiff
path: root/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs
blob: 4d96a5552bab9ad3f3c6c00d774326f0daf89f0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#nullable disable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace StardewModdingAPI.Toolkit.Framework.ModData
{
    /// <summary>The raw mod metadata from SMAPI's internal mod list.</summary>
    internal class ModDataModel
    {
        /*********
        ** Accessors
        *********/
        /// <summary>The mod's current unique ID.</summary>
        public string ID { get; set; }

        /// <summary>The former mod IDs (if any).</summary>
        /// <remarks>
        /// This uses a custom format which uniquely identifies a mod across multiple versions and
        /// supports matching other fields if no ID was specified. This doesn't include the latest
        /// ID, if any. If the mod's ID changed over time, multiple variants can be separated by the
        /// <c>|</c> character.
        /// </remarks>
        public string FormerIDs { get; set; }

        /// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary>
        public ModWarning SuppressWarnings { get; set; }

        /// <summary>This field stores properties that aren't mapped to another field before they're parsed into <see cref="Fields"/>.</summary>
        [JsonExtensionData]
        public IDictionary<string, JToken> ExtensionData { get; set; }

        /// <summary>The versioned field data.</summary>
        /// <remarks>
        /// This maps field names to values. This should be accessed via <see cref="GetFields"/>.
        /// Format notes:
        ///   - Each key consists of a field name prefixed with any combination of version range
        ///     and <c>Default</c>, separated by pipes (whitespace trimmed). For example, <c>Name</c>
        ///     will always override the name, <c>Default | Name</c> will only override a blank
        ///     name, and <c>~1.1 | Default | Name</c> will override blank names up to version 1.1.
        ///   - The version format is <c>min~max</c> (where either side can be blank for unbounded), or
        ///     a single version number.
        ///   - The field name itself corresponds to a <see cref="ModDataFieldKey"/> value.
        /// </remarks>
        public IDictionary<string, string> Fields { get; set; } = new Dictionary<string, string>();


        /*********
        ** Public methods
        *********/
        /// <summary>Get a parsed representation of the <see cref="Fields"/>.</summary>
        public IEnumerable<ModDataField> GetFields()
        {
            foreach (KeyValuePair<string, string> pair in this.Fields)
            {
                // init fields
                string packedKey = pair.Key;
                string value = pair.Value;
                bool isDefault = false;
                ISemanticVersion lowerVersion = null;
                ISemanticVersion upperVersion = null;

                // parse
                string[] parts = packedKey.Split('|').Select(p => p.Trim()).ToArray();
                ModDataFieldKey fieldKey = (ModDataFieldKey)Enum.Parse(typeof(ModDataFieldKey), parts.Last(), ignoreCase: true);
                foreach (string part in parts.Take(parts.Length - 1))
                {
                    // 'default'
                    if (part.Equals("Default", StringComparison.OrdinalIgnoreCase))
                    {
                        isDefault = true;
                        continue;
                    }

                    // version range
                    if (part.Contains("~"))
                    {
                        string[] versionParts = part.Split(new[] { '~' }, 2);
                        lowerVersion = versionParts[0] != "" ? new SemanticVersion(versionParts[0]) : null;
                        upperVersion = versionParts[1] != "" ? new SemanticVersion(versionParts[1]) : null;
                        continue;
                    }

                    // single version
                    lowerVersion = new SemanticVersion(part);
                    upperVersion = new SemanticVersion(part);
                }

                yield return new ModDataField(fieldKey, value, isDefault, lowerVersion, upperVersion);
            }
        }

        /// <summary>Get the former mod IDs.</summary>
        public IEnumerable<string> GetFormerIDs()
        {
            if (this.FormerIDs != null)
            {
                foreach (string id in this.FormerIDs.Split('|'))
                    yield return id.Trim();
            }
        }


        /*********
        ** Private methods
        *********/
        /// <summary>The method invoked after JSON deserialization.</summary>
        /// <param name="context">The deserialization context.</param>
        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            if (this.ExtensionData != null)
            {
                this.Fields = this.ExtensionData.ToDictionary(p => p.Key, p => p.Value.ToString());
                this.ExtensionData = null;
            }
        }
    }
}