Skip to content

Commit d5aec75

Browse files
committed
Start on the CLI add README.md and LICENSE.md
1 parent 845dc68 commit d5aec75

8 files changed

+848
-0
lines changed

LICENSE.md

+595
Large diffs are not rendered by default.

NexusModsApp.sln

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.Abstraction
2222
EndProject
2323
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.BethesdaGameStudios.Tests", "tests\Games\NexusMods.Games.BethesdaGameStudios.Tests\NexusMods.Games.BethesdaGameStudios.Tests.csproj", "{DC3AE1FC-8E93-4F33-B937-C51C2F7C350F}"
2424
EndProject
25+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.CLI", "src\NexusMods.CLI\NexusMods.CLI.csproj", "{D66C4BA1-C129-4DC1-954E-6D9E0B9CB691}"
26+
EndProject
2527
Global
2628
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2729
Debug|Any CPU = Debug|Any CPU
@@ -58,6 +60,10 @@ Global
5860
{DC3AE1FC-8E93-4F33-B937-C51C2F7C350F}.Debug|Any CPU.Build.0 = Debug|Any CPU
5961
{DC3AE1FC-8E93-4F33-B937-C51C2F7C350F}.Release|Any CPU.ActiveCfg = Release|Any CPU
6062
{DC3AE1FC-8E93-4F33-B937-C51C2F7C350F}.Release|Any CPU.Build.0 = Release|Any CPU
63+
{D66C4BA1-C129-4DC1-954E-6D9E0B9CB691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
64+
{D66C4BA1-C129-4DC1-954E-6D9E0B9CB691}.Debug|Any CPU.Build.0 = Debug|Any CPU
65+
{D66C4BA1-C129-4DC1-954E-6D9E0B9CB691}.Release|Any CPU.ActiveCfg = Release|Any CPU
66+
{D66C4BA1-C129-4DC1-954E-6D9E0B9CB691}.Release|Any CPU.Build.0 = Release|Any CPU
6167
EndGlobalSection
6268
GlobalSection(NestedProjects) = preSolution
6369
{9025900A-5342-4EB3-97C8-A9F067929879} = {B93AF89E-9ED7-4FD0-B58E-688AA178AB53}

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Nexus Mods App
2+
3+
This is the repository for the new Nexus Mods App, a mod installer, creator and manager for all your popular games
4+
5+
6+
## ... add more here...
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System.CommandLine;
2+
using System.CommandLine.Invocation;
3+
using System.CommandLine.NamingConventionBinder;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using NexusMods.Interfaces.Components;
6+
using NexusMods.Paths;
7+
8+
namespace NexusMods.CLI;
9+
10+
public class CommandLineBuilder
11+
{
12+
private static IServiceProvider _provider = null!;
13+
public CommandLineBuilder(IServiceProvider provider)
14+
{
15+
_provider = provider;
16+
}
17+
18+
public async Task<int> Run(string[] args)
19+
{
20+
var root = new RootCommand();
21+
foreach (var verb in _commands)
22+
{
23+
root.Add(MakeCommend(verb.Type, verb.Handler, verb.Definition));
24+
}
25+
26+
return await root.InvokeAsync(args);
27+
}
28+
29+
private Dictionary<Type, Func<OptionDefinition, Option>> _optionCtors => new()
30+
{
31+
{
32+
typeof(string),
33+
d => new Option<string>(d.Aliases, description: d.Description)
34+
},
35+
{
36+
typeof(AbsolutePath),
37+
d => new Option<AbsolutePath>(d.Aliases, description: d.Description, parseArgument: d => d.Tokens.Single().Value.ToAbsolutePath())
38+
},
39+
{
40+
typeof(Uri),
41+
d => new Option<Uri>(d.Aliases, description: d.Description)
42+
},
43+
{
44+
typeof(bool),
45+
d => new Option<bool>(d.Aliases, description: d.Description)
46+
},
47+
{
48+
typeof(IGame),
49+
d => new Option<IGame>(d.Aliases, description: d.Description, parseArgument: d =>
50+
{
51+
var s = d.Tokens.Single().Value;
52+
var games = _provider.GetRequiredService<IEnumerable<IGame>>();
53+
return games.First(d => d.Slug.Equals(s, StringComparison.InvariantCultureIgnoreCase));
54+
})
55+
},
56+
{
57+
typeof(Version),
58+
d => new Option<Version>(d.Aliases, description: d.Description, parseArgument: d => Version.Parse(d.Tokens.Single().Value))
59+
}
60+
};
61+
62+
63+
private Command MakeCommend(Type verbType, Func<object, Delegate> verbHandler, VerbDefinition definition)
64+
{
65+
var command = new Command(definition.Name, definition.Description);
66+
foreach (var option in definition.Options)
67+
{
68+
command.Add(_optionCtors[option.Type](option));
69+
}
70+
command.Handler = new HandlerDelegate(_provider, verbType, verbHandler);
71+
return command;
72+
}
73+
74+
private class HandlerDelegate : ICommandHandler
75+
{
76+
private IServiceProvider _provider;
77+
private Type _type;
78+
private readonly Func<object, Delegate> _delgate;
79+
80+
public HandlerDelegate(IServiceProvider provider, Type type, Func<object, Delegate> inner)
81+
{
82+
_provider = provider;
83+
_type = type;
84+
_delgate = inner;
85+
}
86+
public int Invoke(InvocationContext context)
87+
{
88+
var service = _provider.GetRequiredService(_type);
89+
var handler = CommandHandler.Create(_delgate(service));
90+
return handler.Invoke(context);
91+
}
92+
93+
public Task<int> InvokeAsync(InvocationContext context)
94+
{
95+
var service = _provider.GetRequiredService(_type);
96+
var handler = CommandHandler.Create(_delgate(service));
97+
return handler.InvokeAsync(context);
98+
}
99+
}
100+
101+
private static List<(Type Type, VerbDefinition Definition, Func<object, Delegate> Handler)> _commands { get; set; } = new();
102+
public static IEnumerable<Type> Verbs => _commands.Select(c => c.Type);
103+
104+
public static void RegisterCommand<T>(VerbDefinition definition, Func<object, Delegate> handler)
105+
{
106+
_commands.Add((typeof(T), definition, handler));
107+
108+
}
109+
}
110+
111+
public record OptionDefinition(Type Type, string ShortOption, string LongOption, string Description)
112+
{
113+
public string[] Aliases
114+
{
115+
get
116+
{
117+
return new[] { "-" + ShortOption, "--" + LongOption };
118+
}
119+
}
120+
}
121+
122+
public record VerbDefinition(string Name, string Description, OptionDefinition[] Options)
123+
{
124+
}
125+
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net7.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
11+
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\Games\Abstractions\NexusMods.Games.Abstractions\NexusMods.Games.Abstractions.csproj" />
16+
<ProjectReference Include="..\Games\NexusMods.Games.BethesdaGameStudios\NexusMods.Games.BethesdaGameStudios.csproj" />
17+
<ProjectReference Include="..\NexusMods.Paths\NexusMods.Paths.csproj" />
18+
<ProjectReference Include="..\NexusMods.StandardGameLocators\NexusMods.StandardGameLocators.csproj" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<None Update="VerbRegistration.tt">
23+
<Generator>TextTemplatingFileGenerator</Generator>
24+
<LastGenOutput>VerbRegistration.cs</LastGenOutput>
25+
</None>
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<Compile Update="VerbRegistration.cs">
30+
<AutoGen>True</AutoGen>
31+
<DesignTime>True</DesignTime>
32+
<DependentUpon>VerbRegistration.tt</DependentUpon>
33+
</Compile>
34+
</ItemGroup>
35+
36+
</Project>

src/NexusMods.CLI/VerbRegistration.cs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+

2+
// This file is automatically generated by the T4 template: VerbRegistration.tt
3+
// do not edit this file directly
4+
using Microsoft.Extensions.DependencyInjection;
5+
namespace NexusMods.CLI;
6+
using NexusMods.CLI.Verbs;
7+
8+
public static class CommandLineBuilderExtensions{
9+
10+
public static void AddCLIVerbs(this IServiceCollection services) {
11+
CommandLineBuilder.RegisterCommand<ListGames>(ListGames.Definition, c => ((ListGames)c).Run);
12+
services.AddSingleton<ListGames>();
13+
}
14+
}

src/NexusMods.CLI/VerbRegistration.tt

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<#@ template language="C#v3.5" #>
2+
<#@ assembly name="System.Core" #>
3+
<#@ import namespace="System.Text" #>
4+
<#@ import namespace="System.Linq" #>
5+
<#@ import namespace="System.IO"#>
6+
<#@ import namespace="System.Collections.Generic" #>
7+
8+
// This file is automatically generated by the T4 template: VerbRegistration.tt
9+
// do not edit this file directly
10+
using Microsoft.Extensions.DependencyInjection;
11+
namespace NexusMods.CLI;
12+
using NexusMods.CLI.Verbs;
13+
14+
public static class CommandLineBuilderExtensions{
15+
16+
public static void AddCLIVerbs(this IServiceCollection services) {
17+
<#
18+
foreach (var verb in Directory.EnumerateFiles("Verbs"))
19+
{
20+
var klass = verb.Split('\\').Last().Split('.').First();
21+
#>
22+
CommandLineBuilder.RegisterCommand<<#=klass#>>(<#=klass#>.Definition, c => ((<#=klass#>)c).Run);
23+
services.AddSingleton<<#=klass#>>();
24+
<#
25+
}
26+
27+
#>
28+
}
29+
}

src/NexusMods.CLI/Verbs/ListGames.cs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Microsoft.Extensions.Logging;
2+
using NexusMods.Interfaces.Components;
3+
using NexusMods.Paths;
4+
5+
namespace NexusMods.CLI.Verbs;
6+
7+
public class ListGames
8+
{
9+
private readonly IEnumerable<IGame> _games;
10+
11+
public static readonly VerbDefinition Definition = new("list-games",
12+
"Lists all the installed games",
13+
Array.Empty<OptionDefinition>());
14+
15+
private readonly ILogger<ListGames> _logger;
16+
17+
18+
public ListGames(ILogger<ListGames> logger, IEnumerable<IGame> games)
19+
{
20+
_logger = logger;
21+
_games = games;
22+
}
23+
24+
25+
public async Task<int> Run()
26+
{
27+
var installs = from game in _games.OrderBy(g => g.Name)
28+
from install in game.Installations.OrderBy(g => g.Version)
29+
select install;
30+
foreach (var install in installs)
31+
{
32+
_logger.LogInformation("{Install} at {Path}", install, install.Locations[GameFolderType.Game]);
33+
}
34+
35+
return 0;
36+
}
37+
}

0 commit comments

Comments
 (0)