mirror of
https://github.com/fergalmoran/snapp-cli.git
synced 2025-12-22 10:00:52 +00:00
Initial commit
This commit is contained in:
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@@ -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/
|
||||||
34
Commands/AddSnapCommand.cs
Normal file
34
Commands/AddSnapCommand.cs
Normal file
@@ -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<AddSnappsCommand.Settings> {
|
||||||
|
public sealed class Settings : DefaultCommandSettings {
|
||||||
|
public Settings(IOptions<AppSettings> settings) : base(settings) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Execute(CommandContext context, Settings settings) {
|
||||||
|
if (string.IsNullOrEmpty(settings.ServerUrl)) {
|
||||||
|
settings.ServerUrl = AnsiConsole.Prompt(
|
||||||
|
new TextPrompt<string>("Snapp server address?")
|
||||||
|
.PromptStyle("green"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(settings.ApiKey)) {
|
||||||
|
settings.ApiKey = AnsiConsole.Prompt(
|
||||||
|
new TextPrompt<string>("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;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Commands/DebugCommand.cs
Normal file
17
Commands/DebugCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace Snapp.Cli.Commands;
|
||||||
|
|
||||||
|
public class DebugCommand : AsyncCommand<DebugCommand.Settings> {
|
||||||
|
public class Settings : CommandSettings {
|
||||||
|
[CommandArgument(1, "<ECHO-TEXT>")]
|
||||||
|
[Description("Gimme some text to echo.")]
|
||||||
|
public string? EchoText { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings) {
|
||||||
|
Console.WriteLine($"Echoing: {settings.EchoText}");
|
||||||
|
return await Task.FromResult(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Commands/DefaultCommandSettings.cs
Normal file
43
Commands/DefaultCommandSettings.cs
Normal file
@@ -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<AppSettings> 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<string>("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<string>("Enter your API Key:")
|
||||||
|
.PromptStyle("green")
|
||||||
|
.Validate(serverAddress => !string.IsNullOrWhiteSpace(serverAddress)
|
||||||
|
? ValidationResult.Success()
|
||||||
|
: ValidationResult.Error("API Key cannot be empty.")));
|
||||||
|
}
|
||||||
24
Commands/ListSnappsCommand.cs
Normal file
24
Commands/ListSnappsCommand.cs
Normal file
@@ -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<ListSnappsCommand.Settings> {
|
||||||
|
private readonly SnappService _snappService;
|
||||||
|
|
||||||
|
public ListSnappsCommand(SnappService snappService) {
|
||||||
|
_snappService = snappService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Settings : CommandSettings { }
|
||||||
|
// public sealed class Settings : CommandSettings {
|
||||||
|
// public Settings(IOptions<AppSettings> settings) : base(settings) { }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings) {
|
||||||
|
Console.WriteLine($"Listing shit");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Helpers/AppSettingsHelper.cs
Normal file
43
Helpers/AppSettingsHelper.cs
Normal file
@@ -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<AppSettings>();
|
||||||
|
if (c is null) {
|
||||||
|
throw new NullReferenceException("Missing settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Helpers/TypeRegistrar.cs
Normal file
32
Helpers/TypeRegistrar.cs
Normal file
@@ -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<object> func) {
|
||||||
|
if (func is null) {
|
||||||
|
throw new ArgumentNullException(nameof(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
_builder.AddSingleton(service, (provider) => func());
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Helpers/TypeResolver.cs
Normal file
19
Helpers/TypeResolver.cs
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Program.cs
Normal file
29
Program.cs
Normal file
@@ -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<SnappService>();
|
||||||
|
registrations.AddSingleton<AppSettingsHelper>();
|
||||||
|
registrations.AddHttpClient<ISnappService, SnappService>();
|
||||||
|
|
||||||
|
var registrar = new Snapp.Cli.Helpers.TypeRegistrar(registrations);
|
||||||
|
|
||||||
|
var app = new CommandApp<DebugCommand>(registrar);
|
||||||
|
|
||||||
|
|
||||||
|
app.Configure(config => {
|
||||||
|
#if DEBUG
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.ValidateExamples();
|
||||||
|
#endif
|
||||||
|
config.AddCommand<DebugCommand>("debug");
|
||||||
|
config.AddCommand<ListSnappsCommand>("list");
|
||||||
|
});
|
||||||
|
|
||||||
|
return await app
|
||||||
|
.RunAsync(args)
|
||||||
|
.ConfigureAwait(false);
|
||||||
14
Resources/defaultconfig.json
Normal file
14
Resources/defaultconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Servers": [
|
||||||
|
{
|
||||||
|
"Default": {
|
||||||
|
"ApiKey": "<YOUR_API_KEY>",
|
||||||
|
"ServerUrl": "<YOUR_SNAPP_SERVER_URL>"
|
||||||
|
},
|
||||||
|
"Other": {
|
||||||
|
"ApiKey": "<YOUR_OTHER_API_KEY>",
|
||||||
|
"ServerUrl": "<YOUR_OTHER_SNAPP_SERVER_URL>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
37
Services/SnappService.cs
Normal file
37
Services/SnappService.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Snapp.Cli.Helpers;
|
||||||
|
|
||||||
|
namespace Snapp.Cli.Services;
|
||||||
|
|
||||||
|
public class SnappApiResponse<T> {
|
||||||
|
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<SnappApiResponse<List<Snapp>>?> 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<SnappApiResponse<List<Snapp>>?> GetSnaps() {
|
||||||
|
var url = Flurl.Url.Combine(_settingsHelper.ServerUrl, "/snapps");
|
||||||
|
var response = await _httpClient.GetAsync(url);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<SnappApiResponse<List<Snapp>>>(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
snapp-cli.csproj
Normal file
24
snapp-cli.csproj
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<RootNamespace>Snapp.Cli</RootNamespace>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Cocona" Version="2.2.0"/>
|
||||||
|
<PackageReference Include="Flurl" Version="4.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0"/>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0"/>
|
||||||
|
<PackageReference Include="Spectre.Console.Cli" Version="0.49.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Resources\config.json" />
|
||||||
|
<EmbeddedResource Include="Resources\defaultconfig.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
25
snapp-cli.sln
Normal file
25
snapp-cli.sln
Normal file
@@ -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
|
||||||
5
snapp-cli.sln.DotSettings.user
Normal file
5
snapp-cli.sln.DotSettings.user
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConfigurationBinder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Ffergalm_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F5890bea1acfbf3fed68846cf88de417726aefd9611192c0103fe2da89fa41_003FConfigurationBinder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Ffergalm_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIConfigurator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Ffergalm_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb292bc073ef4ad0e85311a67c8972e3da13f217b81775ed3eb2fa38e4fa2d32_003FIConfigurator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATypeRegistrar_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003Fhome_003Ffergalm_003F_002Econfig_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fef4e8f922fc4e26f8bd814dcaba442da850104c6523e517b0ac47e83c7e2da_003FTypeRegistrar_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||||
Reference in New Issue
Block a user