diff --git a/src/Recyclarr.TrashLib/Config/Parsing/ConfigParser.cs b/src/Recyclarr.TrashLib/Config/Parsing/ConfigParser.cs index 1a9ec653..34875b56 100644 --- a/src/Recyclarr.TrashLib/Config/Parsing/ConfigParser.cs +++ b/src/Recyclarr.TrashLib/Config/Parsing/ConfigParser.cs @@ -1,8 +1,10 @@ +using System.IO.Abstractions; using JetBrains.Annotations; using Recyclarr.TrashLib.Config.Services; using Recyclarr.TrashLib.Config.Services.Radarr; using Recyclarr.TrashLib.Config.Services.Sonarr; using Recyclarr.TrashLib.Config.Yaml; +using Serilog.Context; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; @@ -12,6 +14,7 @@ namespace Recyclarr.TrashLib.Config.Parsing; [UsedImplicitly] public class ConfigParser { + private readonly ILogger _log; private readonly ConfigValidationExecutor _validator; private readonly IDeserializer _deserializer; private readonly ConfigRegistry _configs = new(); @@ -26,14 +29,140 @@ public class ConfigParser public IConfigRegistry Configs => _configs; public ConfigParser( + ILogger log, IYamlSerializerFactory yamlFactory, ConfigValidationExecutor validator) { + _log = log; _validator = validator; _deserializer = yamlFactory.CreateDeserializer(); } - public bool SetCurrentSection(string name) + public void Load(IFileInfo file, string? desiredSection = null) + { + _log.Debug("Loading config file: {File}", file); + using var logScope = LogContext.PushProperty(LogProperty.Scope, file.Name); + + try + { + using var stream = file.OpenText(); + LoadFromStream(stream, desiredSection); + return; + } + catch (EmptyYamlException) + { + _log.Warning("Configuration file yielded no usable configuration (is it empty?)"); + return; + } + catch (YamlException e) + { + var line = e.Start.Line; + switch (e.InnerException) + { + case InvalidCastException: + _log.Error("Incompatible value assigned/used at line {Line}: {Msg}", line, + e.InnerException.Message); + break; + + default: + _log.Error("Exception at line {Line}: {Msg}", line, e.InnerException?.Message ?? e.Message); + break; + } + } + + _log.Error("Due to previous exception, this file will be skipped: {File}", file); + } + + public void LoadFromStream(TextReader stream, string? desiredSection) + { + var parser = new Parser(stream); + + parser.Consume(); + if (parser.Current is StreamEnd) + { + _log.Debug("Skipping this config due to StreamEnd"); + throw new EmptyYamlException(); + } + + parser.Consume(); + if (parser.Current is DocumentEnd) + { + _log.Debug("Skipping this config due to DocumentEnd"); + throw new EmptyYamlException(); + } + + ParseAllSections(parser, desiredSection); + + if (Configs.Count == 0) + { + _log.Debug("Document isn't empty, but still yielded no configs"); + } + } + + private void ParseAllSections(Parser parser, string? desiredSection) + { + parser.Consume(); + while (parser.TryConsume(out var section)) + { + if (desiredSection is not null && desiredSection != section.Value) + { + _log.Debug("Skipping section {Section} because it doesn't match {DesiredSection}", + section.Value, desiredSection); + + continue; + } + + if (!SetCurrentSection(section.Value)) + { + _log.Warning("Unknown service type {Type} at line {Line}; skipping", + section.Value, section.Start.Line); + parser.SkipThisAndNestedEvents(); + continue; + } + + if (!ParseSingleSection(parser)) + { + parser.SkipThisAndNestedEvents(); + } + } + } + + private bool ParseSingleSection(Parser parser) + { + switch (parser.Current) + { + case MappingStart: + ParseAndAdd(parser); + break; + + case SequenceStart: + ParseAndAdd(parser); + break; + + case Scalar: + _log.Debug("End of section"); + return false; + + default: + _log.Warning("Unexpected YAML type at line {Line}; skipping this section", parser.Current?.Start.Line); + return false; + } + + return true; + } + + private void ParseAndAdd(Parser parser) + where TStart : ParsingEvent + where TEnd : ParsingEvent + { + parser.Consume(); + while (!parser.TryConsume(out _)) + { + ParseAndAddConfig(parser); + } + } + + private bool SetCurrentSection(string name) { if (!Enum.TryParse(name, true, out SupportedServices key) || !_configTypes.ContainsKey(key)) { @@ -44,7 +173,7 @@ public class ConfigParser return true; } - public void ParseAndAddConfig(Parser parser) + private void ParseAndAddConfig(Parser parser) { var lineNumber = parser.Current?.Start.Line; diff --git a/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationLoader.cs b/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationLoader.cs index 460158df..ccdd8d01 100644 --- a/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationLoader.cs +++ b/src/Recyclarr.TrashLib/Config/Parsing/ConfigurationLoader.cs @@ -1,155 +1,39 @@ using System.IO.Abstractions; -using Recyclarr.TrashLib.Config.Yaml; -using Serilog.Context; -using YamlDotNet.Core; -using YamlDotNet.Core.Events; namespace Recyclarr.TrashLib.Config.Parsing; public class ConfigurationLoader : IConfigurationLoader { - private readonly ILogger _log; - private readonly ConfigParser _parser; + private readonly Func _parserFactory; - public ConfigurationLoader(ILogger log, ConfigParser parser) + public ConfigurationLoader(Func parserFactory) { - _log = log; - _parser = parser; + _parserFactory = parserFactory; } public IConfigRegistry LoadMany(IEnumerable configFiles, string? desiredSection = null) { + var parser = _parserFactory(); + foreach (var file in configFiles) { - Load(file, desiredSection); + parser.Load(file, desiredSection); } - return _parser.Configs; + return parser.Configs; } public IConfigRegistry Load(IFileInfo file, string? desiredSection = null) { - _log.Debug("Loading config file: {File}", file); - using var logScope = LogContext.PushProperty(LogProperty.Scope, file.Name); - - try - { - using var stream = file.OpenText(); - return LoadFromStream(stream, desiredSection); - } - catch (EmptyYamlException) - { - _log.Warning("Configuration file yielded no usable configuration (is it empty?)"); - return _parser.Configs; - } - catch (YamlException e) - { - var line = e.Start.Line; - switch (e.InnerException) - { - case InvalidCastException: - _log.Error("Incompatible value assigned/used at line {Line}: {Msg}", line, - e.InnerException.Message); - break; - - default: - _log.Error("Exception at line {Line}: {Msg}", line, e.InnerException?.Message ?? e.Message); - break; - } - } - - _log.Error("Due to previous exception, this file will be skipped: {File}", file); - return _parser.Configs; + var parser = _parserFactory(); + parser.Load(file, desiredSection); + return parser.Configs; } public IConfigRegistry LoadFromStream(TextReader stream, string? desiredSection = null) { - var parser = new Parser(stream); - - parser.Consume(); - if (parser.Current is StreamEnd) - { - _log.Debug("Skipping this config due to StreamEnd"); - throw new EmptyYamlException(); - } - - parser.Consume(); - if (parser.Current is DocumentEnd) - { - _log.Debug("Skipping this config due to DocumentEnd"); - throw new EmptyYamlException(); - } - - ParseAllSections(parser, desiredSection); - - if (_parser.Configs.Count == 0) - { - _log.Debug("Document isn't empty, but still yielded no configs"); - } - - return _parser.Configs; - } - - private void ParseAllSections(Parser parser, string? desiredSection) - { - parser.Consume(); - while (parser.TryConsume(out var section)) - { - if (desiredSection is not null && desiredSection != section.Value) - { - _log.Debug("Skipping section {Section} because it doesn't match {DesiredSection}", - section.Value, desiredSection); - - continue; - } - - if (!_parser.SetCurrentSection(section.Value)) - { - _log.Warning("Unknown service type {Type} at line {Line}; skipping", - section.Value, section.Start.Line); - parser.SkipThisAndNestedEvents(); - continue; - } - - if (!ParseSingleSection(parser)) - { - parser.SkipThisAndNestedEvents(); - } - } - } - - private bool ParseSingleSection(Parser parser) - { - switch (parser.Current) - { - case MappingStart: - ParseAndAdd(parser); - break; - - case SequenceStart: - ParseAndAdd(parser); - break; - - case Scalar: - _log.Debug("End of section"); - return false; - - default: - _log.Warning("Unexpected YAML type at line {Line}; skipping this section", parser.Current?.Start.Line); - return false; - } - - return true; - } - - private void ParseAndAdd(Parser parser) - where TStart : ParsingEvent - where TEnd : ParsingEvent - { - parser.Consume(); - while (!parser.TryConsume(out _)) - { - _parser.ParseAndAddConfig(parser); - } + var parser = _parserFactory(); + parser.LoadFromStream(stream, desiredSection); + return parser.Configs; } }