diff --git a/IPA.Loader/Config/Config.cs b/IPA.Loader/Config/Config.cs
index ff9be00e..81517381 100644
--- a/IPA.Loader/Config/Config.cs
+++ b/IPA.Loader/Config/Config.cs
@@ -21,7 +21,7 @@ namespace IPA.Config
{
static Config()
{
- //JsonConfigProvider.RegisterConfig();
+ JsonConfigProvider.RegisterConfig();
}
///
diff --git a/IPA.Loader/Config/Data/Primitives.cs b/IPA.Loader/Config/Data/Primitives.cs
index 23d4adcb..1d1bd813 100644
--- a/IPA.Loader/Config/Data/Primitives.cs
+++ b/IPA.Loader/Config/Data/Primitives.cs
@@ -35,6 +35,12 @@ namespace IPA.Config.Data
///
public long Value { get; set; }
+ ///
+ /// Coerces this into a .
+ ///
+ /// a representing the closest approximation of
+ public FloatingPoint AsFloat() => Float(Value);
+
///
/// Converts this into a human-readable format.
///
@@ -53,6 +59,12 @@ namespace IPA.Config.Data
///
public double Value { get; set; }
+ ///
+ /// Coerces this into an .
+ ///
+ /// a representing the closest approximation of
+ public Integer AsInteger() => Integer((long)Value);
+
///
/// Converts this into a human-readable format.
///
diff --git a/IPA.Loader/Config/Data/Value.cs b/IPA.Loader/Config/Data/Value.cs
index a2abb681..a139dc27 100644
--- a/IPA.Loader/Config/Data/Value.cs
+++ b/IPA.Loader/Config/Data/Value.cs
@@ -38,6 +38,7 @@ namespace IPA.Config.Data
///
/// an empty
///
+ ///
public static Map Map() => new Map();
///
@@ -122,7 +123,18 @@ namespace IPA.Config.Data
/// the dictionary of s to initialize the wtih
/// a containing the content of
///
- public static Map From(IDictionary vals)
+ ///
+ public static Map From(IDictionary vals) => From(vals as IEnumerable>);
+
+ ///
+ /// Creates a new holding the content of an
+ /// of of to .
+ ///
+ /// the enumerable of of name to
+ /// a containing the content of
+ ///
+ ///
+ public static Map From(IEnumerable> vals)
{
if (vals == null) return null;
var m = Map();
diff --git a/IPA.Loader/Config/IConfigProvider.cs b/IPA.Loader/Config/IConfigProvider.cs
index 710145f6..823c7fb0 100644
--- a/IPA.Loader/Config/IConfigProvider.cs
+++ b/IPA.Loader/Config/IConfigProvider.cs
@@ -8,7 +8,17 @@ namespace IPA.Config
/// An interface for configuration providers.
///
///
+ ///
/// Implementers must provide a default constructor. Do not assume that will ever be set for a given object.
+ ///
+ ///
+ /// Implementers are expected to preserve the typing of values passed to when returned from .
+ /// The only exceptions to this are the numeric types, and , since they can be coerced
+ /// to each other with and respectively. The provider should
+ /// however store and recover with as much precision as is possible. For example, a JSON provider may decide to
+ /// decode all numbers that have an integral value, even if they were originally , as .
+ /// This is reasonable, as is more precise, particularly with larger values, than .
+ ///
///
public interface IConfigProvider
{
diff --git a/IPA.Loader/Config/Providers/JsonConfigProvider.cs b/IPA.Loader/Config/Providers/JsonConfigProvider.cs
index 1ef6e04a..e30dcbe2 100644
--- a/IPA.Loader/Config/Providers/JsonConfigProvider.cs
+++ b/IPA.Loader/Config/Providers/JsonConfigProvider.cs
@@ -1,14 +1,17 @@
-using IPA.Logging;
+using IPA.Config.Data;
+using IPA.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
+using System.Linq;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
+using System.Collections.Generic;
+using Boolean = IPA.Config.Data.Boolean;
namespace IPA.Config.Providers
-{ // TODO: implement this for the new provider system
- /*[Config.Type("json")]
+{
internal class JsonConfigProvider : IConfigProvider
{
public static void RegisterConfig()
@@ -16,112 +19,138 @@ namespace IPA.Config.Providers
Config.Register();
}
- private JObject jsonObj;
+ public string Extension => "json";
-#if NET4
- // TODO: create a wrapper that allows empty object creation
- public dynamic Dynamic => jsonObj;
-#endif
+ public FileInfo File { get; set; }
- public bool HasChanged { get; private set; }
- public bool InMemoryChanged { get; set; }
-
- public DateTime LastModified => File.GetLastWriteTime(Filename + ".json");
-
- private string _filename;
-
- public string Filename
+ public Value Load()
{
- get => _filename;
- set
+ if (!File.Exists) return Value.Null();
+
+ JToken jtok;
+ using (var sreader = new StreamReader(File.OpenRead()))
{
- if (_filename != null)
- throw new InvalidOperationException("Can only assign to Filename once");
- _filename = value;
+ using var jreader = new JsonTextReader(sreader);
+ jtok = JToken.ReadFrom(jreader);
}
- }
-
- public void Load()
- {
- Logger.config.Debug($"Loading file {Filename}.json");
- var fileInfo = new FileInfo(Filename + ".json");
- if (fileInfo.Exists)
+ try
{
- string json = File.ReadAllText(fileInfo.FullName);
- try
- {
- jsonObj = JObject.Parse(json);
- }
- catch (Exception e)
- {
- Logger.config.Error($"Error parsing JSON in file {Filename}.json; resetting to empty JSON");
- Logger.config.Error(e);
- jsonObj = new JObject();
- File.WriteAllText(fileInfo.FullName, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
- }
+ return VisitToValue(jtok);
}
- else
+ catch (Exception e)
{
- jsonObj = new JObject();
+ Logger.config.Error($"Error reading JSON file {File.FullName}; ignoring");
+ Logger.config.Error(e);
+ return Value.Null();
}
-
- SetupListeners();
- InMemoryChanged = true;
}
- private void SetupListeners()
+ private Value VisitToValue(JToken tok)
{
- jsonObj.PropertyChanged += JsonObj_PropertyChanged;
- jsonObj.ListChanged += JsonObj_ListChanged;
-#if NET4
- jsonObj.CollectionChanged += JsonObj_CollectionChanged;
-#endif
- }
+ if (tok == null) return Value.Null();
-#if NET4
- private void JsonObj_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- HasChanged = true;
- InMemoryChanged = true;
- }
-#endif
-
- private void JsonObj_ListChanged(object sender, ListChangedEventArgs e)
- {
- HasChanged = true;
- InMemoryChanged = true;
- }
-
- private void JsonObj_PropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- HasChanged = true;
- InMemoryChanged = true;
+ switch (tok.Type)
+ {
+ case JTokenType.Raw: // idk if the parser will normally emit a Raw type, but just to be safe
+ return VisitToValue(JToken.Parse((tok as JRaw).Value as string));
+ case JTokenType.Undefined:
+ case JTokenType.Bytes: // never used by Newtonsoft
+ case JTokenType.Comment: // never used by Newtonsoft
+ case JTokenType.Constructor: // never used by Newtonsoft
+ case JTokenType.Property: // never used by Newtonsoft
+ case JTokenType.Null:
+ return Value.Null();
+ case JTokenType.Boolean:
+ return Value.Bool(((tok as JValue).Value as bool?) ?? false);
+ case JTokenType.String:
+ var val = (tok as JValue).Value;
+ if (val is string s) return Value.Text(s);
+ else if (val is char c) return Value.Text("" + c);
+ else return Value.Text(string.Empty);
+ case JTokenType.Integer:
+ val = (tok as JValue).Value;
+ if (val is long l) return Value.Integer(l);
+ else if (val is ulong u) return Value.Integer((long)u);
+ else return Value.Integer(0);
+ case JTokenType.Float:
+ val = (tok as JValue).Value;
+ if (val is decimal dec) return Value.Float((double)dec);
+ else if (val is double dou) return Value.Float(dou);
+ else if (val is float flo) return Value.Float(flo);
+ else return Value.Float(0); // default to 0 if something breaks
+ case JTokenType.Date:
+ val = (tok as JValue).Value;
+ if (val is DateTime dt) return Value.Text(dt.ToString());
+ else if (val is DateTimeOffset dto) return Value.Text(dto.ToString());
+ else return Value.Text("Unknown Date-type token");
+ case JTokenType.TimeSpan:
+ val = (tok as JValue).Value;
+ if (val is TimeSpan ts) return Value.Text(ts.ToString());
+ else return Value.Text("Unknown TimeSpan-type token");
+ case JTokenType.Guid:
+ val = (tok as JValue).Value;
+ if (val is Guid g) return Value.Text(g.ToString());
+ else return Value.Text("Unknown Guid-type token");
+ case JTokenType.Uri:
+ val = (tok as JValue).Value;
+ if (val is Uri ur) return Value.Text(ur.ToString());
+ else return Value.Text("Unknown Uri-type token");
+ case JTokenType.Array:
+ return Value.From((tok as JArray).Select(VisitToValue));
+ case JTokenType.Object:
+ return Value.From((tok as IEnumerable>)
+ .Select(kvp => new KeyValuePair(kvp.Key, VisitToValue(kvp.Value))));
+ default:
+ throw new ArgumentException($"Unknown {nameof(JTokenType)} in parameter");
+ }
}
- public T Parse()
+ public void Store(Value value)
{
- if (jsonObj == null)
- return default(T);
- return jsonObj.ToObject();
- }
+ if (File.Directory.Exists)
+ File.Directory.Create();
- public void Save()
- {
- Logger.config.Debug($"Saving file {Filename}.json");
- if (!Directory.Exists(Path.GetDirectoryName(Filename)))
- Directory.CreateDirectory(Path.GetDirectoryName(Filename) ?? throw new InvalidOperationException());
- File.WriteAllText(Filename + ".json", JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
+ try
+ {
+ var tok = VisitToToken(value);
- HasChanged = false;
+ using var swriter = new StreamWriter(File.OpenWrite());
+ using var jwriter = new JsonTextWriter(swriter);
+ tok.WriteTo(jwriter);
+ }
+ catch (Exception e)
+ {
+ Logger.config.Error($"Error serializing value for {File.FullName}");
+ Logger.config.Error(e);
+ }
}
- public void Store(T obj)
- {
- jsonObj = JObject.FromObject(obj);
- SetupListeners();
- HasChanged = true;
- InMemoryChanged = true;
+ private JToken VisitToToken(Value val)
+ {
+ switch (val)
+ {
+ case Text t:
+ return new JValue(t.Value);
+ case Boolean b:
+ return new JValue(b.Value);
+ case Integer i:
+ return new JValue(i.Value);
+ case FloatingPoint f:
+ return new JValue(f.Value);
+ case List l:
+ var jarr = new JArray();
+ foreach (var tok in l.Select(VisitToToken)) jarr.Add(tok);
+ return jarr;
+ case Map m:
+ var jobj = new JObject();
+ foreach (var kvp in m) jobj.Add(kvp.Key, VisitToToken(kvp.Value));
+ return jobj;
+ case null:
+ return JValue.CreateNull();
+ default:
+ throw new ArgumentException($"Unsupported subtype of {nameof(Value)}");
+ }
}
- }*/
+ }
}
\ No newline at end of file
diff --git a/IPA.Loader/Config/Stores/GeneratedStore.cs b/IPA.Loader/Config/Stores/GeneratedStore.cs
index 0f4dd0d2..37e2ce7e 100644
--- a/IPA.Loader/Config/Stores/GeneratedStore.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStore.cs
@@ -106,6 +106,7 @@ namespace IPA.Config.Stores
{
// TODO: implement
Logger.config.Debug("Generated impl ReadFrom");
+ Logger.config.Debug($"Read {provider.Load()}");
}
internal static MethodInfo WriteToMethod = typeof(Impl).GetMethod(nameof(WriteTo));