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