From 2daa52aa04918581d249563675775f44e02c8f81 Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Fri, 26 Jul 2024 17:01:40 +0100 Subject: [PATCH] Initial commit --- .gitignore | 16 +++++++++++ Commands/AddSnapCommand.cs | 34 +++++++++++++++++++++++ Commands/DebugCommand.cs | 17 ++++++++++++ Commands/DefaultCommandSettings.cs | 43 ++++++++++++++++++++++++++++++ Commands/ListSnappsCommand.cs | 24 +++++++++++++++++ Helpers/AppSettingsHelper.cs | 43 ++++++++++++++++++++++++++++++ Helpers/TypeRegistrar.cs | 32 ++++++++++++++++++++++ Helpers/TypeResolver.cs | 19 +++++++++++++ Program.cs | 29 ++++++++++++++++++++ Resources/defaultconfig.json | 14 ++++++++++ Services/SnappService.cs | 37 +++++++++++++++++++++++++ snapp-cli.csproj | 24 +++++++++++++++++ snapp-cli.sln | 25 +++++++++++++++++ snapp-cli.sln.DotSettings.user | 5 ++++ 14 files changed, 362 insertions(+) create mode 100644 .gitignore create mode 100644 Commands/AddSnapCommand.cs create mode 100644 Commands/DebugCommand.cs create mode 100644 Commands/DefaultCommandSettings.cs create mode 100644 Commands/ListSnappsCommand.cs create mode 100644 Helpers/AppSettingsHelper.cs create mode 100644 Helpers/TypeRegistrar.cs create mode 100644 Helpers/TypeResolver.cs create mode 100644 Program.cs create mode 100644 Resources/defaultconfig.json create mode 100644 Services/SnappService.cs create mode 100644 snapp-cli.csproj create mode 100644 snapp-cli.sln create mode 100644 snapp-cli.sln.DotSettings.user diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f94171e --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Created by https://www.toptal.com/developers/gitignore/api/dotnetcore +# Edit at https://www.toptal.com/developers/gitignore?templates=dotnetcore + +### DotnetCore ### +# .NET Core build folders +bin/ +obj/ + +# Common node modules locations +/node_modules +/wwwroot/node_modules + +# End of https://www.toptal.com/developers/gitignore/api/dotnetcore + +.idea/ +.krud/ diff --git a/Commands/AddSnapCommand.cs b/Commands/AddSnapCommand.cs new file mode 100644 index 0000000..c830dca --- /dev/null +++ b/Commands/AddSnapCommand.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Options; +using Snapp.Cli.Helpers; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace Snapp.Cli.Commands; + +public class AddSnappsCommand : Command { + public sealed class Settings : DefaultCommandSettings { + public Settings(IOptions settings) : base(settings) { } + } + + public override int Execute(CommandContext context, Settings settings) { + if (string.IsNullOrEmpty(settings.ServerUrl)) { + settings.ServerUrl = AnsiConsole.Prompt( + new TextPrompt("Snapp server address?") + .PromptStyle("green")); + } + + if (string.IsNullOrEmpty(settings.ApiKey)) { + settings.ApiKey = AnsiConsole.Prompt( + new TextPrompt("Snapp server API Key?") + .PromptStyle("green")); + } + + Console.WriteLine( + $"Executing list snapps command with server address: {settings.ServerUrl} and API key: {settings.ApiKey}"); + // var snaps = await service.GetSnaps(); + // foreach (var snap in snaps.Results) { + // Console.WriteLine($"{snap.Id}: {snap.Title}"); + // } + return -1; + } +} diff --git a/Commands/DebugCommand.cs b/Commands/DebugCommand.cs new file mode 100644 index 0000000..50713ee --- /dev/null +++ b/Commands/DebugCommand.cs @@ -0,0 +1,17 @@ +using System.ComponentModel; +using Spectre.Console.Cli; + +namespace Snapp.Cli.Commands; + +public class DebugCommand : AsyncCommand { + public class Settings : CommandSettings { + [CommandArgument(1, "")] + [Description("Gimme some text to echo.")] + public string? EchoText { get; set; } + } + + public override async Task ExecuteAsync(CommandContext context, Settings settings) { + Console.WriteLine($"Echoing: {settings.EchoText}"); + return await Task.FromResult(3); + } +} diff --git a/Commands/DefaultCommandSettings.cs b/Commands/DefaultCommandSettings.cs new file mode 100644 index 0000000..4b12568 --- /dev/null +++ b/Commands/DefaultCommandSettings.cs @@ -0,0 +1,43 @@ +using System.ComponentModel; +using Microsoft.Extensions.Options; +using Snapp.Cli.Helpers; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace Snapp.Cli.Commands; + +public class DefaultCommandSettings : CommandSettings { + public readonly AppSettings Config; + + public DefaultCommandSettings(IOptions settings) { + Config = settings.Value; + } + + [Description("Path to search. Defaults to current directory.")] + [CommandArgument(0, "[-s|--server]")] + public string? ServerUrl { get; set; } + + [Description("API Key for server.")] + [CommandArgument(0, "[-k|--api-key]")] + public string? ApiKey { get; set; } + + public static string AskServerIfMissing(string? current) => + !string.IsNullOrWhiteSpace(current) + ? current + : AnsiConsole.Prompt( + new TextPrompt("Enter your server URL:") + .PromptStyle("green") + .Validate(serverAddress => !string.IsNullOrWhiteSpace(serverAddress) + ? ValidationResult.Success() + : ValidationResult.Error("Server URL cannot be empty."))); + + public static string AskApiKeyIfMissing(string? current) => + !string.IsNullOrWhiteSpace(current) + ? current + : AnsiConsole.Prompt( + new TextPrompt("Enter your API Key:") + .PromptStyle("green") + .Validate(serverAddress => !string.IsNullOrWhiteSpace(serverAddress) + ? ValidationResult.Success() + : ValidationResult.Error("API Key cannot be empty."))); +} diff --git a/Commands/ListSnappsCommand.cs b/Commands/ListSnappsCommand.cs new file mode 100644 index 0000000..c33814d --- /dev/null +++ b/Commands/ListSnappsCommand.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Snapp.Cli.Helpers; +using Snapp.Cli.Services; +using Spectre.Console.Cli; + +namespace Snapp.Cli.Commands; + +public class ListSnappsCommand : AsyncCommand { + private readonly SnappService _snappService; + + public ListSnappsCommand(SnappService snappService) { + _snappService = snappService; + } + + public sealed class Settings : CommandSettings { } + // public sealed class Settings : CommandSettings { + // public Settings(IOptions settings) : base(settings) { } + // } + + public override async Task ExecuteAsync(CommandContext context, Settings settings) { + Console.WriteLine($"Listing shit"); + return 1; + } +} diff --git a/Helpers/AppSettingsHelper.cs b/Helpers/AppSettingsHelper.cs new file mode 100644 index 0000000..595b734 --- /dev/null +++ b/Helpers/AppSettingsHelper.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Configuration; + +namespace Snapp.Cli.Helpers; + +public class AppSettings { + public string? ApiKey { get; set; } + public string? ServerUrl { get; set; } +} + +public class AppSettingsHelper { + private readonly AppSettings _config; + + public string? ApiKey { get => _config.ApiKey; } + public string? ServerUrl { get => _config.ServerUrl; } + + private static string SettingsFile { + get => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "snapp-cli/config.json"); + } + + public AppSettingsHelper() { + _config = _initialiseSettings(); + } + + private AppSettings _initialiseSettings() { + var config = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile(SettingsFile, false, true) + .Build(); + + if (config is null) { + throw new NullReferenceException("Missing settings file"); + } + + var c = config.Get(); + if (c is null) { + throw new NullReferenceException("Missing settings"); + } + + return c; + } +} diff --git a/Helpers/TypeRegistrar.cs b/Helpers/TypeRegistrar.cs new file mode 100644 index 0000000..ccac85d --- /dev/null +++ b/Helpers/TypeRegistrar.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console.Cli; + +namespace Snapp.Cli.Helpers; + +public sealed class TypeRegistrar : ITypeRegistrar { + private readonly IServiceCollection _builder; + + public TypeRegistrar(IServiceCollection builder) { + _builder = builder; + } + + public ITypeResolver Build() { + return new TypeResolver(_builder.BuildServiceProvider()); + } + + public void Register(Type service, Type implementation) { + _builder.AddSingleton(service, implementation); + } + + public void RegisterInstance(Type service, object implementation) { + _builder.AddSingleton(service, implementation); + } + + public void RegisterLazy(Type service, Func func) { + if (func is null) { + throw new ArgumentNullException(nameof(func)); + } + + _builder.AddSingleton(service, (provider) => func()); + } +} \ No newline at end of file diff --git a/Helpers/TypeResolver.cs b/Helpers/TypeResolver.cs new file mode 100644 index 0000000..f65fadd --- /dev/null +++ b/Helpers/TypeResolver.cs @@ -0,0 +1,19 @@ +using Spectre.Console.Cli; + +namespace Snapp.Cli.Helpers; + +public sealed class TypeResolver(IServiceProvider provider) : ITypeResolver, IDisposable { + private readonly IServiceProvider _provider = + provider ?? + throw new ArgumentNullException(nameof(provider)); + + public object? Resolve(Type? type) { + return type == null ? null : _provider.GetService(type); + } + + public void Dispose() { + if (_provider is IDisposable disposable) { + disposable.Dispose(); + } + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..ae1fda1 --- /dev/null +++ b/Program.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Snapp.Cli.Commands; +using Snapp.Cli.Helpers; +using Snapp.Cli.Services; +using Spectre.Console.Cli; + +var registrations = new ServiceCollection(); +registrations.AddSingleton(); +registrations.AddSingleton(); +registrations.AddHttpClient(); + +var registrar = new Snapp.Cli.Helpers.TypeRegistrar(registrations); + +var app = new CommandApp(registrar); + + +app.Configure(config => { +#if DEBUG + config.PropagateExceptions(); + config.ValidateExamples(); +#endif + config.AddCommand("debug"); + config.AddCommand("list"); +}); + +return await app + .RunAsync(args) + .ConfigureAwait(false); diff --git a/Resources/defaultconfig.json b/Resources/defaultconfig.json new file mode 100644 index 0000000..d0d832e --- /dev/null +++ b/Resources/defaultconfig.json @@ -0,0 +1,14 @@ +{ + "Servers": [ + { + "Default": { + "ApiKey": "", + "ServerUrl": "" + }, + "Other": { + "ApiKey": "", + "ServerUrl": "" + } + } + ] +} diff --git a/Services/SnappService.cs b/Services/SnappService.cs new file mode 100644 index 0000000..f7af3aa --- /dev/null +++ b/Services/SnappService.cs @@ -0,0 +1,37 @@ +using System.Text.Json; +using Snapp.Cli.Helpers; + +namespace Snapp.Cli.Services; + +public class SnappApiResponse { + public int Count; + public int Page; + public int Limit; + public string? SortDirection; + public T? Results { get; set; } +} + +public record Snapp(string Id, string Title, string Description); + +public interface ISnappService { + public Task>?> GetSnaps(); +} + +public class SnappService : ISnappService { + private readonly HttpClient _httpClient; + private readonly AppSettingsHelper _settingsHelper; + + public SnappService(HttpClient httpClient, AppSettingsHelper settingsHelper) { + _httpClient = httpClient; + _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {settingsHelper.ApiKey}"); + _settingsHelper = settingsHelper; + } + + public async Task>?> GetSnaps() { + var url = Flurl.Url.Combine(_settingsHelper.ServerUrl, "/snapps"); + var response = await _httpClient.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + + return JsonSerializer.Deserialize>>(content); + } +} diff --git a/snapp-cli.csproj b/snapp-cli.csproj new file mode 100644 index 0000000..4a1eb2e --- /dev/null +++ b/snapp-cli.csproj @@ -0,0 +1,24 @@ + + + + Exe + net8.0 + Snapp.Cli + enable + enable + + + + + + + + + + + + + + + + diff --git a/snapp-cli.sln b/snapp-cli.sln new file mode 100644 index 0000000..d5c89d9 --- /dev/null +++ b/snapp-cli.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "snapp-cli", "snapp-cli.csproj", "{4016DB59-BCF0-4C0B-8229-AD686999EBDE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4016DB59-BCF0-4C0B-8229-AD686999EBDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4016DB59-BCF0-4C0B-8229-AD686999EBDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4016DB59-BCF0-4C0B-8229-AD686999EBDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4016DB59-BCF0-4C0B-8229-AD686999EBDE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {67F03902-702A-4C51-A443-739B736E2A95} + EndGlobalSection +EndGlobal diff --git a/snapp-cli.sln.DotSettings.user b/snapp-cli.sln.DotSettings.user new file mode 100644 index 0000000..e59f8c0 --- /dev/null +++ b/snapp-cli.sln.DotSettings.user @@ -0,0 +1,5 @@ + + ForceIncluded + ForceIncluded + ForceIncluded + ForceIncluded \ No newline at end of file