Install commands, improvements

This commit is contained in:
2022-11-06 18:36:04 +01:00
parent 8fd6b526f8
commit 30c3266e25
15 changed files with 278 additions and 87 deletions

View File

@@ -5,10 +5,13 @@ public class ModuleConfiguration
public string? Target { get; set; } public string? Target { get; set; }
public Dictionary<string, string>? Links { get; set; } public Dictionary<string, string>? Links { get; set; }
public ModuleConfiguration(string? target, Dictionary<string, string>? links) public string? Install { get; set; }
public ModuleConfiguration(string? target, Dictionary<string, string>? links, string? install)
{ {
Target = target; Target = target;
Links = links; Links = links;
Install = install;
} }
public ModuleConfiguration Merge(ModuleConfiguration merge) public ModuleConfiguration Merge(ModuleConfiguration merge)
@@ -17,10 +20,11 @@ public class ModuleConfiguration
.Concat(merge.Links ?? new Dictionary<string, string>()); .Concat(merge.Links ?? new Dictionary<string, string>());
return new ModuleConfiguration( return new ModuleConfiguration(
merge.Target ?? Target, merge.Target ?? Target,
new Dictionary<string, string>(mergedLinks) new Dictionary<string, string>(mergedLinks),
merge.Install ?? Install
); );
} }
public static ModuleConfiguration Empty() => public static ModuleConfiguration Empty() =>
new(null, new Dictionary<string, string>()); new(null, new Dictionary<string, string>(), null);
} }

View File

@@ -2,6 +2,6 @@ namespace Alma.Services;
public interface IOsInformation public interface IOsInformation
{ {
string GetOsIdentifier(); Task<string> GetOsIdentifierAsync();
bool IsOnPlatform(string platform); Task<bool> IsOnPlatformAsync(string platform);
} }

View File

@@ -0,0 +1,6 @@
namespace Alma.Services;
public interface IShellService
{
Task RunCommandAsync(string command);
}

View File

@@ -5,6 +5,10 @@
<ProjectReference Include="..\Alma.Logging\Alma.Logging.csproj" /> <ProjectReference Include="..\Alma.Logging\Alma.Logging.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>

View File

@@ -4,47 +4,52 @@ using Alma.Services;
namespace Alma.Command.Info; namespace Alma.Command.Info;
public class ModuleInfoCommand : ICommand public class InfoCommand : ICommand
{ {
public string CommandString => "info"; public string CommandString => "info";
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IRepositoryConfiguration _repositoryConfiguration; private readonly IRepositoryConfiguration _repositoryConfiguration;
private readonly ILogger<ModuleInfoCommand> _logger; private readonly ILogger<InfoCommand> _logger;
private readonly IOsInformation _osInformation;
public ModuleInfoCommand( public InfoCommand(
IFolderService folderService, IFolderService folderService,
IRepositoryConfiguration repositoryConfiguration, IRepositoryConfiguration repositoryConfiguration,
ILogger<ModuleInfoCommand> logger ILogger<InfoCommand> logger,
IOsInformation osInformation
) )
{ {
_folderService = folderService; _folderService = folderService;
_repositoryConfiguration = repositoryConfiguration; _repositoryConfiguration = repositoryConfiguration;
_logger = logger; _logger = logger;
_osInformation = osInformation;
} }
public Task Run(List<string> parameters) public async Task Run(List<string> parameters)
{ {
//Add info REPO //Add info REPO
//Add info REPO MODULE //Add info REPO MODULE
_logger.LogInformation("AppData folder: " + _folderService.AppData); _logger.LogInformation("AppData folder: " + _folderService.AppData);
if (_folderService.ConfigRoot is string configRoot) if (_folderService.ConfigRoot is { } configRoot)
{ {
_logger.LogInformation("Configuration folder: " + configRoot); _logger.LogInformation("Configuration folder: " + configRoot);
} }
else else
{ {
_logger.LogInformation("Configuration folder not exists."); _logger.LogInformation("Configuration folder not exists.");
_logger.LogInformation("Preffered configuration folder is: " + Path.Combine(_folderService.GetPreferredConfigurationFolder(), _folderService.ApplicationSubfolderName)); _logger.LogInformation("Preferred configuration folder is: " + Path.Combine(_folderService.GetPreferredConfigurationFolder(), _folderService.ApplicationSubfolderName));
} }
_logger.LogInformation("");
_logger.LogInformation($"Platform is '{await _osInformation.GetOsIdentifierAsync()}'");
_logger.LogInformation(""); _logger.LogInformation("");
if (_repositoryConfiguration.Configuration.Repositories is var repositores && repositores?.Count > 0) if (_repositoryConfiguration.Configuration.Repositories is {Count: > 0} repositories)
{ {
_logger.LogInformation("Repositories:"); _logger.LogInformation("Repositories:");
foreach (var repository in repositores) foreach (var repository in repositories)
{ {
Console.Write(repository.Name); Console.Write(repository.Name);
if (repository.RepositoryPath is not null && !Directory.Exists(repository.RepositoryPath)) if (repository.RepositoryPath is not null && !Directory.Exists(repository.RepositoryPath))
@@ -58,7 +63,5 @@ public class ModuleInfoCommand : ICommand
{ {
_logger.LogInformation("No repositories found"); _logger.LogInformation("No repositories found");
} }
return Task.CompletedTask;
} }
} }

View File

@@ -0,0 +1,74 @@
using System.Diagnostics;
using Alma.Configuration.Repository;
using Alma.Data;
using Alma.Logging;
using Alma.Services;
namespace Alma.Command.Install;
public class InstallCommand : RepositoryModuleCommandBase
{
private readonly ILogger<InstallCommand> _logger;
private readonly IModuleConfigurationResolver _moduleConfigurationResolver;
private readonly IShellService _shellService;
public override string CommandString => "install";
public InstallCommand(
ILogger<InstallCommand> logger,
IRepositoryConfiguration repositoryConfiguration,
IModuleConfigurationResolver moduleConfigurationResolver,
IShellService shellService)
: base(repositoryConfiguration)
{
_logger = logger;
_moduleConfigurationResolver = moduleConfigurationResolver;
_shellService = shellService;
}
public override async Task Run(List<string> parameters)
{
var (repoName, moduleName) = GetRepositoryAndModuleName(parameters);
if (moduleName is null)
{
_logger.LogInformation("No module specified");
return;
}
string sourceDirectory = Path.Combine(Environment.CurrentDirectory);
string targetDirectory = Path.Combine(Environment.CurrentDirectory, "..");
string moduleNameAsPath = moduleName.Replace('/', Path.DirectorySeparatorChar);
(sourceDirectory, _) = GetModuleSourceAndTargetDirectory(repoName, sourceDirectory, targetDirectory);
string moduleDirectory = Path.Combine(sourceDirectory, moduleNameAsPath);
var moduleConfigFileStub = Path.Combine(moduleDirectory, Constants.ModuleConfigFileStub);
var (moduleConfiguration, moduleConfigurationFile) = await _moduleConfigurationResolver.ResolveModuleConfiguration(moduleConfigFileStub);
if (moduleConfiguration is null)
{
_logger.LogInformation("No module configuration found.");
return;
}
var installLines = moduleConfiguration.Install?.Split(Environment.NewLine);
if (installLines is null)
{
_logger.LogInformation("No install command is found");
return;
}
_logger.LogInformation($"Install command: {string.Join("\n", installLines)}");
if (installLines.Length == 1)
{
_logger.LogInformation("Running install command '" + installLines[0] + "'");
await _shellService.RunCommandAsync(installLines[0]);
}
else
{
_logger.LogError("Multi line scripts are not currently supported");
}
}
}

View File

@@ -7,20 +7,21 @@ using Alma.Services;
namespace Alma.Command.Link; namespace Alma.Command.Link;
public class LinkCommand : ICommand public class LinkCommand : RepositoryModuleCommandBase
{ {
private readonly IRepositoryConfiguration _repositoryConfiguration; private readonly IRepositoryConfiguration _repositoryConfiguration;
private readonly IModuleConfigurationResolver _moduleConfigurationResolver; private readonly IModuleConfigurationResolver _moduleConfigurationResolver;
private readonly IMetadataHandler _metadataHandler; private readonly IMetadataHandler _metadataHandler;
private readonly ILogger<LinkCommand> _logger; private readonly ILogger<LinkCommand> _logger;
public string CommandString => "link"; public override string CommandString => "link";
public LinkCommand( public LinkCommand(
IRepositoryConfiguration repositoryConfiguration, IRepositoryConfiguration repositoryConfiguration,
IModuleConfigurationResolver moduleConfigurationResolver, IModuleConfigurationResolver moduleConfigurationResolver,
IMetadataHandler metadataHandler, IMetadataHandler metadataHandler,
ILogger<LinkCommand> logger) ILogger<LinkCommand> logger)
: base(repositoryConfiguration)
{ {
_repositoryConfiguration = repositoryConfiguration; _repositoryConfiguration = repositoryConfiguration;
_moduleConfigurationResolver = moduleConfigurationResolver; _moduleConfigurationResolver = moduleConfigurationResolver;
@@ -28,26 +29,19 @@ public class LinkCommand : ICommand
_logger = logger; _logger = logger;
} }
public async Task Run(List<string> parameters) public override async Task Run(List<string> parameters)
{ {
if (parameters.Count == 0) var (repoName, moduleName) = GetRepositoryAndModuleName(parameters);
if (moduleName is null)
{ {
_logger.LogInformation("No module specified"); _logger.LogInformation("No module specified");
return; return;
} }
string moduleName = parameters[0];
string sourceDirectory = Path.Combine(Environment.CurrentDirectory); string sourceDirectory = Path.Combine(Environment.CurrentDirectory);
string targetDirectory = Path.Combine(Environment.CurrentDirectory, ".."); string targetDirectory = Path.Combine(Environment.CurrentDirectory, "..");
var repoName = GetRepositoryName(parameters); (sourceDirectory, targetDirectory) = GetModuleSourceAndTargetDirectory(repoName, sourceDirectory, targetDirectory);
if (repoName is not null
&& _repositoryConfiguration.Configuration.Repositories.FirstOrDefault(r => r.Name == repoName) is { } repoConfig)
{
sourceDirectory = repoConfig.RepositoryPath ?? sourceDirectory;
targetDirectory = repoConfig.LinkPath ?? targetDirectory;
}
if (!Directory.Exists(sourceDirectory)) if (!Directory.Exists(sourceDirectory))
{ {
@@ -180,13 +174,6 @@ public class LinkCommand : ICommand
return filesToLink.Concat(subDirLinksToAdd); return filesToLink.Concat(subDirLinksToAdd);
} }
private static string? GetRepositoryName(List<string> parameters)
{
//TODO: handle parameters
if (parameters.Count < 2) return null;
return parameters[1];
}
private static string ResolvePath(string path, string currentDirectory) private static string ResolvePath(string path, string currentDirectory)
{ {
if (path.StartsWith("~")) if (path.StartsWith("~"))

View File

@@ -0,0 +1,46 @@
using Alma.Configuration.Repository;
namespace Alma.Command;
public abstract class RepositoryModuleCommandBase : ICommand
{
private readonly IRepositoryConfiguration _repositoryConfiguration;
public abstract string CommandString { get; }
public abstract Task Run(List<string> parameters);
protected RepositoryModuleCommandBase(IRepositoryConfiguration repositoryConfiguration)
{
_repositoryConfiguration = repositoryConfiguration;
}
protected (string?, string?) GetRepositoryAndModuleName(List<string> parameters)
{
//TODO: handle parameters
string? repositoryName = null;
string? moduleName = null;
if (parameters.Count == 1)
{
moduleName = parameters[0];
}
else if (parameters.Count >= 1)
{
repositoryName = parameters[0];
moduleName = parameters[1];
}
return (repositoryName, moduleName);
}
protected (string sourceDirectory, string targetDirectory) GetModuleSourceAndTargetDirectory(string? repoName, string fallbackSourceDirectory, string fallbackTargetDirectory)
{
if (repoName is not null
&& _repositoryConfiguration.Configuration.Repositories.FirstOrDefault(r => r.Name == repoName) is { } repoConfig)
{
fallbackSourceDirectory = repoConfig.RepositoryPath ?? fallbackSourceDirectory;
fallbackTargetDirectory = repoConfig.LinkPath ?? fallbackTargetDirectory;
}
return (fallbackSourceDirectory, fallbackTargetDirectory);
}
}

View File

@@ -21,7 +21,10 @@ public class ModuleConfigurationResolver : IModuleConfigurationResolver
if (moduleConfigRoot is null) return (null, null); if (moduleConfigRoot is null) return (null, null);
var validModuleConfigurations = moduleConfigRoot.Where(m => _osInformation.IsOnPlatform(m.Key)); var validModuleConfigurations = await moduleConfigRoot
.ToAsyncEnumerable()
.WhereAwait(async m => await _osInformation.IsOnPlatformAsync(m.Key))
.ToListAsync();
//TODO: priority order //TODO: priority order
var orderedValidModuleConfigurations = new Dictionary<string, ModuleConfiguration>(validModuleConfigurations); var orderedValidModuleConfigurations = new Dictionary<string, ModuleConfiguration>(validModuleConfigurations);

View File

@@ -6,19 +6,52 @@ public class OsInformation : IOsInformation
{ {
private const string OsIdentifierDefault = "default"; private const string OsIdentifierDefault = "default";
private const string OsIdentifierWin = "windows"; private const string OsIdentifierWin = "windows";
private const string OsIdentifierMac = "macos";
private const string OsIdentifierFreeBsd = "freebsd";
private const string OsIdentifierLinux = "linux"; private const string OsIdentifierLinux = "linux";
public string GetOsIdentifier() private const string LinuxOsRelease = "/etc/os-release";
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return OsIdentifierWin;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return OsIdentifierLinux;
public async Task<string> GetOsIdentifierAsync()
{
string? baseOsIdentifier = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) baseOsIdentifier = OsIdentifierWin;
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) baseOsIdentifier = OsIdentifierMac;
else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) baseOsIdentifier = OsIdentifierFreeBsd;
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
baseOsIdentifier = OsIdentifierLinux;
try
{
if (File.Exists(LinuxOsRelease))
{
var lines = await File.ReadAllLinesAsync(LinuxOsRelease);
var distroName = lines.FirstOrDefault(l => l.StartsWith("id=", StringComparison.InvariantCultureIgnoreCase));
if (distroName is not null)
{
distroName = distroName.ToLower().Substring(distroName.IndexOf("=", StringComparison.Ordinal) + 1);
baseOsIdentifier += "-" + distroName;
}
}
}
catch
{
}
}
if (baseOsIdentifier is null)
return "unknown"; return "unknown";
var architecture = RuntimeInformation.ProcessArchitecture.ToString().ToLower();
return baseOsIdentifier + "-" + architecture;
} }
public bool IsOnPlatform(string platform) public async Task<bool> IsOnPlatformAsync(string platform)
{ {
if (platform == OsIdentifierDefault) return true; return platform == OsIdentifierDefault
return platform == GetOsIdentifier(); || (await GetOsIdentifierAsync()).StartsWith(platform, StringComparison.InvariantCultureIgnoreCase);
} }
} }

View File

@@ -0,0 +1,52 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Alma.Logging;
namespace Alma.Services;
public class ShellService : IShellService
{
private readonly ILogger<ShellService> _logger;
public ShellService(ILogger<ShellService> logger)
{
_logger = logger;
}
public async Task RunCommandAsync(string command)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var processStartInfo = new ProcessStartInfo()
{
FileName = "sh",
ArgumentList = {"-c", command},
RedirectStandardOutput = true,
RedirectStandardInput = true,
CreateNoWindow = true,
UseShellExecute = false
};
var process = Process.Start(processStartInfo);
if (process is null) return;
var reader = process.StandardOutput;
while (!reader.EndOfStream)
{
var content = await reader.ReadLineAsync();
if (content is not null)
{
_logger.LogInformation(content);
}
}
await process.WaitForExitAsync();
return;
}
_logger.LogError("Platform not supported");
throw new NotSupportedException();
}
}

View File

@@ -7,4 +7,6 @@ public interface ILogger<T>
void LogDebug(string logMessage); void LogDebug(string logMessage);
void LogTrace(string logMessage); void LogTrace(string logMessage);
void Log(string logMessage, LogLevel logLevel); void Log(string logMessage, LogLevel logLevel);
void LogError(string logMessage);
void LogCritical(string logMessage);
} }

View File

@@ -2,6 +2,8 @@
public enum LogLevel public enum LogLevel
{ {
Critical,
Error,
Information, Information,
Debug, Debug,
Trace Trace

View File

@@ -22,4 +22,14 @@ public class Logger<T> : ILogger<T>
Console.WriteLine(s); Console.WriteLine(s);
} }
} }
public void LogError(string logMessage)
{
Log(logMessage, LogLevel.Error);
}
public void LogCritical(string logMessage)
{
Log(logMessage, LogLevel.Critical);
}
} }

View File

@@ -1,5 +1,6 @@
using Alma.Command; using Alma.Command;
using Alma.Command.Info; using Alma.Command.Info;
using Alma.Command.Install;
using Alma.Command.Link; using Alma.Command.Link;
using Alma.Command.List; using Alma.Command.List;
using Alma.Command.Unlink; using Alma.Command.Unlink;
@@ -12,42 +13,6 @@ namespace Alma;
public static class Program public static class Program
{ {
/*public static async Task Main(string[] args)
{
var services = BuildServices();
var repositoryConfiguration = services.GetRequiredService<IRepositoryConfiguration>();
await repositoryConfiguration.LoadAsync();
var application = services.GetRequiredService<Application>();
await application.Run(args);
static IServiceProvider BuildServices()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IRepositoryConfiguration, RepositoryConfiguration>();
serviceCollection.AddSingleton<IFolderService, FolderService>();
serviceCollection.AddSingleton<ConfigurationFileReader>();
serviceCollection.AddSingleton<IConfigurationFileReader, JsonConfigurationFileReader>();
serviceCollection.AddSingleton<IOsInformation, OsInformation>();
serviceCollection.AddSingleton<ICommand, LinkCommand>();
serviceCollection.AddSingleton<IModuleConfigurationResolver, ModuleConfigurationResolver>();
serviceCollection.AddSingleton<Application>();
typeof(IRepositoryConfiguration), typeof(RepositoryConfiguration)
typeof(IFolderService), typeof(FolderService)
typeof(ConfigurationFileReader)
typeof(IConfigurationFileReader), typeof(JsonConfigurationFileReader)
typeof(IOsInformation), typeof(OsInformation)
typeof(ICommand), typeof(LinkCommand)
typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver)
typeof(Application)
return serviceCollection.BuildServiceProvider();
}
}*/
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
InitLogging(); InitLogging();
@@ -61,14 +26,12 @@ public static class Program
await application.Run(args); await application.Run(args);
} }
private static ILoggerFactory InitLogging() private static void InitLogging()
{ {
var loggerFactory = new LoggerFactory(); AlmaLoggerFactory = new LoggerFactory();
return AlmaLoggerFactory = loggerFactory;
} }
public static ILoggerFactory AlmaLoggerFactory { get; private set; } public static ILoggerFactory AlmaLoggerFactory { get; private set; } = null!;
} }
[ServiceProvider] [ServiceProvider]
@@ -79,12 +42,14 @@ public static class Program
[Singleton(typeof(IOsInformation), typeof(OsInformation))] [Singleton(typeof(IOsInformation), typeof(OsInformation))]
[Singleton(typeof(ICommand), typeof(LinkCommand))] [Singleton(typeof(ICommand), typeof(LinkCommand))]
[Singleton(typeof(ICommand), typeof(UnlinkCommand))] [Singleton(typeof(ICommand), typeof(UnlinkCommand))]
[Singleton(typeof(ICommand), typeof(ModuleInfoCommand))] [Singleton(typeof(ICommand), typeof(InfoCommand))]
[Singleton(typeof(ICommand), typeof(ListCommand))] [Singleton(typeof(ICommand), typeof(ListCommand))]
[Singleton(typeof(ICommand), typeof(InstallCommand))]
//Dependency cycle //Dependency cycle
//[Singleton(typeof(ICommand), typeof(HelpCommand))] //[Singleton(typeof(ICommand), typeof(HelpCommand))]
[Singleton(typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver))] [Singleton(typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver))]
[Singleton(typeof(IMetadataHandler), typeof(MetadataHandler))] [Singleton(typeof(IMetadataHandler), typeof(MetadataHandler))]
[Singleton(typeof(IShellService), typeof(ShellService))]
[Singleton(typeof(Application))] [Singleton(typeof(Application))]
[Transient(typeof(ILogger<>), Factory = nameof(CustomLoggerFactory))] [Transient(typeof(ILogger<>), Factory = nameof(CustomLoggerFactory))]
internal partial class AlmaServiceProvider internal partial class AlmaServiceProvider