From 2eefdc03258ba491478de2057e5e89255d2d024c Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Mon, 19 Apr 2021 17:27:16 -0500 Subject: [PATCH] feat: New `create-config` subcommand Allows the user to create a starter YAML config --- CHANGELOG.md | 4 ++ README.md | 27 ++++++---- .../CreateConfig/CreateConfigCommandTest.cs | 46 +++++++++++++++++ .../ProfileDataValueFormatter.cs | 19 +++++++ src/Trash/Command/BaseCommand.cs | 5 +- src/Trash/CompositionRoot.cs | 42 ++-------------- src/Trash/CreateConfig/CreateConfigCommand.cs | 50 +++++++++++++++++++ src/Trash/Trash.csproj | 4 ++ src/Trash/trash-config-template.yml | 33 ++++++++++++ 9 files changed, 179 insertions(+), 51 deletions(-) create mode 100644 src/Trash.Tests/CreateConfig/CreateConfigCommandTest.cs create mode 100644 src/Trash.Tests/ValueFormatters/ProfileDataValueFormatter.cs create mode 100644 src/Trash/CreateConfig/CreateConfigCommand.cs create mode 100644 src/Trash/trash-config-template.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e967794..f93f86fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- New `create-config` subcommand to create a starter YAML config file + ## [1.1.0] - 2021-04-18 ### Added diff --git a/README.md b/README.md index 864b1eac..8a0071aa 100644 --- a/README.md +++ b/README.md @@ -61,21 +61,28 @@ too. Run this from the directory where you want `trash` to be installed. ## Getting Started -> **TL;DR**: Run `trash [sonarr|radarr] --help` for help with available command line options. Visit -> [the wiki](https://github.com/rcdailey/trash-updater/wiki) for in-depth documentation about the -> command line, configuration, and other topics. +Trash Updater requires a YML configuration file in order to work. Run the steps below if you want to +get started with minimal configuration file template. -The `trash` executable provides one subcommand per distinct service. This means, for example, you -can run `trash sonarr` and `trash radarr`. When you run these subcommands, the relevant service -configuration is read from the YAML files. +- Run `trash create-config` to create a starter `trash.yml` file in the same directory as the + executable. You can also use `--config` to +- Open the generated YAML file from the previous step. At a minimum you must update the `base_url` + and `api_key` settings for each service that you want to use. +- Run `trash sonarr` and/or `trash.radarr` as needed to update those instances. -That's all you need to do on the command line to get the program to parse guides and push settings -to the respective service. Most of the documentation will be for the YAML configuration, which is -what drives the behavior of the program. +The last step above will do a "basic" sync from the guides to Sonarr and/or Radarr. The starter YAML +config is very minimal. See the next section for further reading and links to the wiki for +additional topics and more advanced customization. + +Lastly, each subcommand supports printing help on the command line. Simply run `trash --help` for +the main help output and a list of subcommands. You can then see the help for each subcommand by +running `trash [subcommand] --help`, where `[subcommand]` is one of those subcommands (e.g. +`sonarr`) ### Read the Documentation -Main documentation is located in the wiki. Links provided below for some main topics. +Main documentation is located in [the wiki](https://github.com/rcdailey/trash-updater/wiki). Links +provided below for some main topics. - [Command Line Reference](../../wiki/Command-Line-Reference) - [Configuration Reference](../../wiki/Configuration-Reference) diff --git a/src/Trash.Tests/CreateConfig/CreateConfigCommandTest.cs b/src/Trash.Tests/CreateConfig/CreateConfigCommandTest.cs new file mode 100644 index 00000000..1c94372b --- /dev/null +++ b/src/Trash.Tests/CreateConfig/CreateConfigCommandTest.cs @@ -0,0 +1,46 @@ +using System.IO.Abstractions; +using System.Threading.Tasks; +using CliFx.Infrastructure; +using NSubstitute; +using NUnit.Framework; +using Serilog; +using Trash.CreateConfig; + +// ReSharper disable MethodHasAsyncOverload + +namespace Trash.Tests.CreateConfig +{ + [TestFixture] + [Parallelizable(ParallelScope.All)] + public class CreateConfigCommandTest + { + [Test] + public async Task CreateConfig_DefaultPath_FileIsCreated() + { + var logger = Substitute.For(); + var filesystem = Substitute.For(); + var cmd = new CreateConfigCommand(logger, filesystem); + + await cmd.ExecuteAsync(Substitute.For()); + + filesystem.File.Received().Exists(Arg.Is(s => s.EndsWith("trash.yml"))); + filesystem.File.Received().WriteAllText(Arg.Is(s => s.EndsWith("trash.yml")), Arg.Any()); + } + + [Test] + public async Task CreateConfig_SpecifyPath_FileIsCreated() + { + var logger = Substitute.For(); + var filesystem = Substitute.For(); + var cmd = new CreateConfigCommand(logger, filesystem) + { + Path = "some/other/path.yml" + }; + + await cmd.ExecuteAsync(Substitute.For()); + + filesystem.File.Received().Exists(Arg.Is("some/other/path.yml")); + filesystem.File.Received().WriteAllText(Arg.Is("some/other/path.yml"), Arg.Any()); + } + } +} diff --git a/src/Trash.Tests/ValueFormatters/ProfileDataValueFormatter.cs b/src/Trash.Tests/ValueFormatters/ProfileDataValueFormatter.cs new file mode 100644 index 00000000..4d2dfa4d --- /dev/null +++ b/src/Trash.Tests/ValueFormatters/ProfileDataValueFormatter.cs @@ -0,0 +1,19 @@ +using FluentAssertions.Formatting; +using Trash.Sonarr.ReleaseProfile; + +namespace Trash.Tests.ValueFormatters +{ + public class ProfileDataValueFormatter : IValueFormatter + { + public bool CanHandle(object value) + { + return value is ProfileData; + } + + public string Format(object value, FormattingContext context, FormatChild formatChild) + { + var profileData = (ProfileData) value; + return $"{profileData.Ignored}"; + } + } +} diff --git a/src/Trash/Command/BaseCommand.cs b/src/Trash/Command/BaseCommand.cs index 76393fb1..408c0bb8 100644 --- a/src/Trash/Command/BaseCommand.cs +++ b/src/Trash/Command/BaseCommand.cs @@ -27,6 +27,8 @@ namespace Trash.Command Log = logger; } + public static string DefaultConfigPath { get; } = Path.Join(AppContext.BaseDirectory, "trash.yml"); + protected ILogger Log { get; } [CommandOption("preview", 'p', Description = @@ -40,8 +42,7 @@ namespace Trash.Command [CommandOption("config", 'c', Description = "One or more YAML config files to use. All configs will be used and settings are additive. " + "If not specified, the script will look for `trash.yml` in the same directory as the executable.")] - public List Config { get; [UsedImplicitly] set; } = - new() {Path.Join(AppContext.BaseDirectory, "trash.yml")}; + public List Config { get; [UsedImplicitly] set; } = new() {DefaultConfigPath}; public async ValueTask ExecuteAsync(IConsole console) { diff --git a/src/Trash/CompositionRoot.cs b/src/Trash/CompositionRoot.cs index 8f1058df..5d583072 100644 --- a/src/Trash/CompositionRoot.cs +++ b/src/Trash/CompositionRoot.cs @@ -1,9 +1,9 @@ using System.IO.Abstractions; using System.Reflection; using Autofac; +using CliFx; using Serilog; using Serilog.Core; -using Trash.Command; using Trash.Config; using Trash.Radarr.Api; using Trash.Radarr.QualityDefinition; @@ -16,42 +16,6 @@ namespace Trash { public static class CompositionRoot { - // private static void SetupMediator(ContainerBuilder builder) - // { - // builder - // .RegisterType() - // .As() - // .InstancePerLifetimeScope(); - // - // builder.Register(context => - // { - // var c = context.Resolve(); - // return t => c.Resolve(t); - // }); - // - // builder.RegisterAssemblyTypes(typeof(CompositionRoot).GetTypeInfo().Assembly).AsImplementedInterfaces(); - // } - - // private static void RegisterConfiguration(ContainerBuilder builder) - // where T : BaseConfiguration - // { - // - // builder.Register(ctx => - // { - // var selector = ctx.Resolve>(); - // if (selector.ActiveConfiguration == null) - // { - // // If this exception is thrown, that means that a BaseCommand subclass has not implemented the - // // appropriate logic to set the active configuration via an IConfigurationSelector. - // throw new InvalidOperationException("No valid configuration has been selected"); - // } - // - // return selector.ActiveConfiguration; - // }) - // .As() - // .AsSelf(); - // } - private static void SetupLogging(ContainerBuilder builder) { builder.RegisterType().SingleInstance(); @@ -101,9 +65,9 @@ namespace Trash .As(typeof(IConfigurationProvider<>)) .SingleInstance(); - // Register all types deriving from BaseCommand. These are all of our supported subcommands. + // Register all types deriving from CliFx's ICommand. These are all of our supported subcommands. builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) - .Where(t => t.IsAssignableTo(typeof(IBaseCommand))); + .Where(t => t.IsAssignableTo(typeof(ICommand))); SetupLogging(builder); SonarrRegistrations(builder); diff --git a/src/Trash/CreateConfig/CreateConfigCommand.cs b/src/Trash/CreateConfig/CreateConfigCommand.cs new file mode 100644 index 00000000..570bbf54 --- /dev/null +++ b/src/Trash/CreateConfig/CreateConfigCommand.cs @@ -0,0 +1,50 @@ +using System.IO.Abstractions; +using System.Threading.Tasks; +using CliFx; +using CliFx.Attributes; +using CliFx.Exceptions; +using CliFx.Infrastructure; +using Common; +using JetBrains.Annotations; +using Serilog; +using Trash.Command; + +namespace Trash.CreateConfig +{ + [Command("create-config", Description = "Create a starter YAML configuration file")] + [UsedImplicitly] + public class CreateConfigCommand : ICommand + { + private readonly IFileSystem _fileSystem; + + public CreateConfigCommand(ILogger logger, IFileSystem fileSystem) + { + Log = logger; + _fileSystem = fileSystem; + } + + private ILogger Log { get; } + + [CommandOption("path", 'p', Description = + "Path where the new YAML file should be created. Must include the filename (e.g. path/to/config.yml). " + + "File must not already exist. If not specified, uses the default path of `trash.yml` right next to the " + + "executable.")] + public string Path { get; [UsedImplicitly] set; } = BaseCommand.DefaultConfigPath; + + public ValueTask ExecuteAsync(IConsole console) + { + var reader = new ResourceDataReader(typeof(Program)); + var ymlData = reader.ReadData("trash-config-template.yml"); + + if (_fileSystem.File.Exists(Path)) + { + throw new CommandException($"The file {Path} already exists. Please choose another path or " + + "delete/move the existing file and run this command again."); + } + + _fileSystem.File.WriteAllText(Path, ymlData); + Log.Information("Created configuration at: {Path}", Path); + return default; + } + } +} diff --git a/src/Trash/Trash.csproj b/src/Trash/Trash.csproj index 2e3c6805..962065ba 100644 --- a/src/Trash/Trash.csproj +++ b/src/Trash/Trash.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/src/Trash/trash-config-template.yml b/src/Trash/trash-config-template.yml new file mode 100644 index 00000000..37cd56c2 --- /dev/null +++ b/src/Trash/trash-config-template.yml @@ -0,0 +1,33 @@ +# A starter config to use with Trash Updater. Most values are set to "reasonable defaults". +# Update the values below as needed for your instance. You will be required to update the +# API Key and URL for each instance you want to use. +# +# Many optional settings have been omitted to keep this template simple. +# +# For more details on the configuration, see the Configuration Reference on the wiki here: +# https://github.com/rcdailey/trash-updater/wiki/Configuration-Reference + +# Configuration specific to Sonarr +sonarr: + # Set the URL/API Key to your actual instance + - base_url: http://localhost:8989 + api_key: f7e74ba6c80046e39e076a27af5a8444 + + # Quality definitions from the guide to sync to Sonarr. Choice: anime, series, hybrid + quality_definition: hybrid + + # Release profiles from the guide to sync to Sonarr. Types: anime, series + # You can optionally add tags and make negative scores strictly ignored + release_profiles: + - type: anime + - type: series + +# Configuration specific to Radarr. +radarr: + # Set the URL/API Key to your actual instance + - base_url: http://localhost:7878 + api_key: bf99da49d0b0488ea34e4464aa63a0e5 + + # Which quality definition in the guide to sync to Radarr. Only choice right now is 'movie' + quality_definition: + type: movie