2 Commits

Author SHA1 Message Date
46c1a78e3f Dotnet 7, publish scripts 2022-11-02 12:03:54 +01:00
fb381e56c5 DevContainer 2022-11-02 12:02:24 +01:00
50 changed files with 208 additions and 1369 deletions

View File

@@ -1,3 +0,0 @@
{
"image": "mcr.microsoft.com/dotnet/sdk:8.0-alpine"
}

View File

@@ -1,3 +0,0 @@
{
"image": "mcr.microsoft.com/dotnet/sdk:8.0"
}

View File

@@ -0,0 +1,3 @@
{
"image": "mcr.microsoft.com/dotnet/sdk:7.0-alpine"
}

View File

@@ -1,2 +0,0 @@
[*.cs]

View File

@@ -1,87 +0,0 @@
name: Alma build
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ${{ matrix.target.runner }}
strategy:
matrix:
target:
- runtime: win-x64
runner: windows-latest
name: windows
outputname: Alma.exe
version_script: .scripts/versioning.ps1
- runtime: linux-x64
runner: ubuntu-latest
name: linux
outputname: Alma
version_script: .scripts/versioning.sh
- runtime: linux-musl-x64
runner: ubuntu-latest
name: linux-musl
outputname: Alma
version_script: .scripts/versioning.sh
steps:
- uses: actions/checkout@v3
- name: Setup dotnet
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Patch version
run: ${{ matrix.target.version_script }}
continue-on-error: true
- name: Restore dependencies
run: dotnet restore src/Alma
- name: Build
run: dotnet publish -c Release -p:PublishAot=true -r ${{ matrix.target.runtime }} -o app/ src/Alma
- uses: actions/upload-artifact@v3
with:
name: alma-${{ matrix.target.name }}
path: app/${{ matrix.target.outputname }}
release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
name: Download Windows artifacts
with:
name: alma-windows
path: app/windows/
- uses: actions/download-artifact@v3
name: Download Linux artifacts
with:
name: alma-linux
path: app/linux/
- uses: actions/download-artifact@v3
name: Download Linux musl artifacts
with:
name: alma-linux
path: app/linux-musl/
- name: Create release directory
run: mkdir release
- name: Copy windows executable
run: cp app/windows/Alma.exe release/alma.exe
- name: Copy linux executable
run: cp app/linux/Alma release/alma-linux
- name: Copy linux-musl executable
run: cp app/linux-musl/Alma release/alma-linux-musl
- uses: "marvinpinto/action-automatic-releases@latest"
name: Create release
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: latest
prerelease: false
draft: true
files: |
release/alma*

View File

@@ -1,12 +0,0 @@
if((Get-Content env:\GITHUB_REF_TYPE) -ne "tag")
{
Return
}
$version = (Get-Content env:\GITHUB_REF).Replace("refs/tags/v", "")
$git_sha = (Get-Content env:\GITHUB_SHA).Substring(0, 8)
Write-Host $version
Write-Host $git_sha
(Get-Content src\Alma\Alma.csproj).Replace("0.0.0", $version).Replace("development", $git_sha) | Set-Content src\Alma\Alma.csproj

View File

@@ -1,13 +0,0 @@
if [ ${GITHUB_REF_TYPE} != "tag" ]; then
exit 1
fi
version="${GITHUB_REF:11}"
git_hash="${GITHUB_SHA}"
echo $git_hash
git_hash=`expr substr $git_hash 1 8`
echo $version
echo $git_hash
sed -i "s/0.0.0/$version/g;s/development/$git_hash/g" src/Alma/Alma.csproj

View File

@@ -1,25 +0,0 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine as BUILD
RUN apk add -U clang zlib-dev
WORKDIR /build
COPY . .
RUN dotnet publish src/Alma/Alma.csproj -c Release -r linux-musl-x64 -p:PublishAot=true -o /app
FROM alpine:edge as FINAL
RUN mkdir /data
WORKDIR /data
ENV ALMA_APP_DATA_FALLBACK=/appdata
ENV ALMA_CONFIG_FALLBACK=/appconfig
RUN mkdir /appdata
RUN mkdir /appconfig
RUN apk add -U icu-libs libstdc++
COPY --from=BUILD /app/Alma /alma
ENTRYPOINT ["/alma"]
CMD ["help"]

View File

@@ -1,32 +0,0 @@
# Alma
Alma (aka Advanced Link Manager Application) is another dotfiles management tool.
## Installation
**Linux**
```
sudo wget https://github.com/ADIX7/Alma/releases/download/latest/alma-linux -O /usr/local/bin/alma
sudo chmod +x /usr/local/bin/alma
```
**Windows**
This PowerShell command will download the alma.exe to the current folder. Move it to a folder that is in PATH.
```
Invoke-WebRequest https://github.com/ADIX7/Alma/releases/download/latest/Alma.exe -OutFile alma.exe
```
**As a Docker tool**
You can run it with Docker/Podman. You should mount every directory (source and target too) to the same path as they are on the host.
For example, if you have your dotfiles cloned to your home folder and you have your repository.json in ~/.config/alma/repository.json, then you can run this command and the links will be correct on the host.
```
docker run --rm -it -v /home/myuser:/home/myuser -e "HOME=/home/myuser" adix7/alma:latest ...
```
If you don't have repository.json, you can set the WORKDIR (or ALMA_WORKDIR) env var to your repository. For example, if you cloned your dotfiles to ~/dotfiles, you can use this command.
```
docker run --rm -it -v /home/myuser:/home/myuser -e "WORKDIR=/home/myuser/dotfiles" adix7/alma:latest ...
```

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Alma</RootNamespace>

View File

@@ -2,7 +2,6 @@ namespace Alma.Command;
public interface ICommand
{
string CommandString { get; }
string[] CommandAliases { get; }
Task Run(List<string> parameters);
public string CommandString { get; }
public Task Run(List<string> parameters);
}

View File

@@ -4,18 +4,11 @@ public class ModuleConfiguration
{
public string? Target { get; set; }
public Dictionary<string, string>? Links { get; set; }
public List<string>? Exclude { get; set; }
public bool ExcludeReadme { get; set; } = true;
public string? Install { get; set; }
public string? Configure { get; set; }
public ModuleConfiguration(string? target, Dictionary<string, string>? links, string? install, string? configure)
public ModuleConfiguration(string? target, Dictionary<string, string>? links)
{
Target = target;
Links = links;
Install = install;
Configure = configure;
}
public ModuleConfiguration Merge(ModuleConfiguration merge)
@@ -24,12 +17,10 @@ public class ModuleConfiguration
.Concat(merge.Links ?? new Dictionary<string, string>());
return new ModuleConfiguration(
merge.Target ?? Target,
new Dictionary<string, string>(mergedLinks),
merge.Install ?? Install,
merge.Configure ?? Configure
new Dictionary<string, string>(mergedLinks)
);
}
public static ModuleConfiguration Empty() =>
new(null, new Dictionary<string, string>(), null, null);
new(null, new Dictionary<string, string>());
}

View File

@@ -1,8 +0,0 @@
namespace Alma.Data;
public static class ColorCodes
{
public const string Reset = "\u001b[0m";
public const string RedForeground = "\u001b[38;5;1m";
public const string GreenForeground = "\u001b[38;5;2m";
}

View File

@@ -1,5 +0,0 @@
using Alma.Configuration.Module;
namespace Alma.Data;
public record ModuleConfigurationWithName(string Name, ModuleConfiguration Configuration);

View File

@@ -1,3 +0,0 @@
namespace Alma.Data;
public record SpecialPathResolver(string PathName, Func<string> Resolver, bool? SkipCombiningCurrentDirectory = null);

View File

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

View File

@@ -1,6 +0,0 @@
namespace Alma.Services;
public interface IPathHelperService
{
string ResolvePath(string path, string? currentDirectory = null);
}

View File

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

View File

@@ -1,6 +0,0 @@
namespace Alma.Services;
public interface IVersionService
{
public string GetVersion();
}

View File

@@ -5,13 +5,8 @@
<ProjectReference Include="..\Alma.Logging\Alma.Logging.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.19" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Alma</RootNamespace>

View File

@@ -1,4 +1,5 @@
using Alma.Command;
using Alma.Command.Help;
using Alma.Logging;
namespace Alma;
@@ -8,9 +9,9 @@ public class Application
private readonly IList<ICommand> _commands;
private readonly ILogger<Application> _logger;
public Application(IEnumerable<ICommand> commands, ILogger<Application> logger)
public Application(IEnumerable<ICommand> commands, ILogger<Application> logger, ILogger<HelpCommand> helpCommandLogger)
{
_commands = commands.ToList();
_commands = commands.Append(new HelpCommand(() => _commands!, helpCommandLogger)).ToList();
_logger = logger;
}
@@ -24,7 +25,7 @@ public class Application
var commandString = args[0];
var command = _commands.FirstOrDefault(c => c.CommandString == commandString || c.CommandAliases.Contains(commandString));
var command = _commands.FirstOrDefault(c => c.CommandString == commandString);
if (command is null)
{

View File

@@ -1,63 +0,0 @@
using Alma.Command.Install;
using Alma.Configuration.Repository;
using Alma.Logging;
using Alma.Services;
namespace Alma.Command.Configure;
public class ConfigureCommand : RepositoryModuleCommandBase
{
private readonly ILogger<InstallCommand> _logger;
private readonly IShellService _shellService;
public override string CommandString => "configure";
public override string[] CommandAliases => Array.Empty<string>();
public ConfigureCommand(
ILogger<InstallCommand> logger,
IRepositoryConfiguration repositoryConfiguration,
IModuleConfigurationResolver moduleConfigurationResolver,
IShellService shellService,
IPathHelperService pathHelperService)
: base(repositoryConfiguration, pathHelperService, moduleConfigurationResolver)
{
_logger = logger;
_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;
}
var (moduleConfiguration, _) = await GetModuleConfiguration(repoName, moduleName);
if (moduleConfiguration is null)
{
_logger.LogInformation($"No module configuration found for module '{moduleName}'{(repoName is null ? "" : $" in repository '{repoName}'")}");
return;
}
var configureLines = moduleConfiguration.Configure?.Split(Environment.NewLine);
if (configureLines is null)
{
_logger.LogInformation("No configure command is found");
return;
}
_logger.LogInformation($"Configure command: {string.Join("\n", configureLines)}");
if (configureLines.Length == 1)
{
_logger.LogInformation("Running configure command '" + configureLines[0] + "'");
await _shellService.RunCommandAsync(configureLines[0]);
}
else
{
_logger.LogError("Multi line scripts are not currently supported");
}
}
}

View File

@@ -1,131 +0,0 @@
using System.Reflection;
using Alma.Logging;
namespace Alma.Command.Diag;
public class DiagCommand : ICommand
{
private readonly ILogger<DiagCommand> _logger;
public string CommandString => "diag";
public string[] CommandAliases => Array.Empty<string>();
private readonly Lazy<IReadOnlyList<MethodInfo>> _diagnosticHelpersLazy;
public DiagCommand(ILogger<DiagCommand> logger)
{
_logger = logger;
_diagnosticHelpersLazy = new Lazy<IReadOnlyList<MethodInfo>>(FindDiagnosticHelpers);
}
public Task Run(List<string> parameters)
{
if (parameters.Count < 1)
{
_logger.LogInformation("No diagnostic helper specified.");
return Task.CompletedTask;
}
var command = parameters[0];
if(command == "--help")
{
_logger.LogInformation(
"""
Usage:
diag list
diag <diag-helper>
"""
);
return Task.CompletedTask;
}
if (command == "list")
{
ListDiagnosticHelpers();
return Task.CompletedTask;
}
var diagnosticHelpers = _diagnosticHelpersLazy.Value;
var helper = diagnosticHelpers.FirstOrDefault(
h => GetDiagnosticHelper(h) is { } attr && attr.Command == command
);
if (helper is null)
{
_logger.LogInformation($"Diagnostic helper {command} is not found.");
return Task.CompletedTask;
}
if (!helper.IsStatic)
{
_logger.LogInformation($"Diagnostic helper {helper.Name} is not static.");
return Task.CompletedTask;
}
HandleHelper(helper, parameters.Skip(1));
return Task.CompletedTask;
}
private void ListDiagnosticHelpers()
{
var diagnosticHelpers = _diagnosticHelpersLazy.Value;
var commands = diagnosticHelpers
.Select(h => GetDiagnosticHelper(h)?.Command)
.OfType<string>()
.Order()
.ToList();
_logger.LogInformation("Available diagnostic helpers:");
foreach (var command in commands)
{
_logger.LogInformation(command);
}
}
private void HandleHelper(MethodInfo helper, IEnumerable<string> parameters)
{
var helperParameters = helper.GetParameters();
var helperArguments = new object[helperParameters.Length];
for (var i = 0; i < helperParameters.Length; i++)
{
var parameterType = helperParameters[i].ParameterType;
if (parameterType == typeof(IEnumerable<string>))
{
helperArguments[i] = parameters;
}
else if (parameterType == typeof(ILogger))
{
helperArguments[i] = _logger;
}
else
{
_logger.LogInformation($"Diagnostic helper {helper.Name} has wrong parameter type, could not resolve '{parameterType}'.");
return;
}
}
helper.Invoke(null, helperArguments);
}
private IReadOnlyList<MethodInfo> FindDiagnosticHelpers()
{
var assemblies = new[]
{
Assembly.GetExecutingAssembly(),
};
return assemblies.SelectMany(
a =>
a
.GetTypes()
.SelectMany(t => t.GetMethods())
.Where(t => t.GetCustomAttributes(typeof(DiagnosticHelper)).Any()))
.ToList()
.AsReadOnly();
}
private DiagnosticHelper? GetDiagnosticHelper(MethodInfo o)
=> o.GetCustomAttributes(typeof(DiagnosticHelper)).FirstOrDefault() as DiagnosticHelper;
}

View File

@@ -1,12 +0,0 @@
namespace Alma.Command.Diag;
[AttributeUsage(AttributeTargets.Method)]
public class DiagnosticHelper : Attribute
{
public string Command { get; }
public DiagnosticHelper(string command)
{
Command = command;
}
}

View File

@@ -9,14 +9,12 @@ public class HelpCommand : ICommand
public string CommandString => "help";
public string[] CommandAliases { get; } = ["--help", "-h"];
public HelpCommand(
IServiceProvider serviceProvider,
Func<IEnumerable<ICommand>> commandsProvider,
ILogger<HelpCommand> logger
)
{
_commandsProvider = () => (IEnumerable<ICommand>?)serviceProvider.GetService(typeof(IEnumerable<ICommand>)) ?? throw new ApplicationException();
_commandsProvider = commandsProvider;
_logger = logger;
}

View File

@@ -1,216 +0,0 @@
using Alma.Configuration.Module;
using Alma.Configuration.Repository;
using Alma.Data;
using Alma.Logging;
using Alma.Services;
namespace Alma.Command.Info;
public class InfoCommand : RepositoryModuleCommandBase
{
public override string CommandString => "info";
public override string[] CommandAliases => Array.Empty<string>();
private readonly IFolderService _folderService;
private readonly IRepositoryConfiguration _repositoryConfiguration;
private readonly IModuleConfigurationResolver _moduleConfigurationResolver;
private readonly ILogger<InfoCommand> _logger;
private readonly IOsInformation _osInformation;
private readonly IVersionService _versionService;
private readonly IPathHelperService _pathHelperService;
private static readonly List<Func<ModuleConfiguration, string?>> _moduleInfoDetailResolvers = new()
{
(m) =>
{
var linkCount = m.Links?.Count ?? 0;
return linkCount.ToString().PadLeft(3) + $" link{(linkCount > 1 ? "s" : "")}".PadRight(6);
},
(m) => m.Install is not null ? "[Install]" : null,
(m) => m.Configure is not null ? "[Configure]" : null,
};
public InfoCommand(
IFolderService folderService,
IRepositoryConfiguration repositoryConfiguration,
IModuleConfigurationResolver moduleConfigurationResolver,
ILogger<InfoCommand> logger,
IOsInformation osInformation,
IVersionService versionService,
IPathHelperService pathHelperService
) : base(repositoryConfiguration, pathHelperService, moduleConfigurationResolver)
{
_folderService = folderService;
_repositoryConfiguration = repositoryConfiguration;
_moduleConfigurationResolver = moduleConfigurationResolver;
_logger = logger;
_osInformation = osInformation;
_versionService = versionService;
_pathHelperService = pathHelperService;
}
public override async Task Run(List<string> parameters)
{
var (repoName, moduleName) = GetRepositoryAndModuleName(parameters, true);
if (repoName is not null && moduleName is null)
{
await ProcessRepoInfoAsync(repoName);
}
else if (repoName is not null && moduleName is not null)
{
await ProcessModuleInfoAsync(repoName, moduleName);
}
else
{
await ProcessGeneralInfoAsync();
}
}
private async Task ProcessGeneralInfoAsync()
{
_logger.LogInformation("Alma " + _versionService.GetVersion());
_logger.LogInformation("");
_logger.LogInformation("AppData folder: " + _folderService.AppData);
if (_folderService.ConfigRoot is { } configRoot)
{
_logger.LogInformation("Configuration folder: " + configRoot);
}
else
{
_logger.LogInformation("Configuration folder not exists.");
_logger.LogInformation("Preferred configuration folder is: " + Path.Combine(_folderService.GetPreferredConfigurationFolder(), _folderService.ApplicationSubfolderName));
}
_logger.LogInformation("");
_logger.LogInformation($"Platform is '{await _osInformation.GetOsIdentifierAsync()}'");
_logger.LogInformation("");
if (_repositoryConfiguration.Configuration.Repositories is { Count: > 0 } repositories)
{
_logger.LogInformation("Repositories:");
foreach (var repository in repositories)
{
Console.Write(repository.Name);
if (repository.RepositoryPath is not null && !Directory.Exists(_pathHelperService.ResolvePath(repository.RepositoryPath)))
{
Console.Write($" (containing folder not exists {repository.RepositoryPath})");
}
_logger.LogInformation("");
}
}
else
{
_logger.LogInformation("No repositories found");
}
}
private async Task ProcessRepoInfoAsync(string repoName)
{
var (repoSourceDirectory, _) = GetRepositorySourceAndTargetDirectory(repoName);
var repoRoot = new DirectoryInfo(repoSourceDirectory);
var modules = (await TraverseRepoFolder(repoRoot, repoRoot)).OrderBy(e => e.Name).ToList();
var maxNameLength = modules.Max(m => m.Name.Length);
_logger.LogInformation($"Repository '{repoName}' contains {modules.Count} modules:");
_logger.LogInformation("");
foreach (var module in modules)
{
var moduleDetails = _moduleInfoDetailResolvers
.Select(m => m(module.Configuration))
.Where(m => m is not null)
.ToList();
_logger.LogInformation($"{module.Name.PadRight(maxNameLength + 3)} {string.Join(" ", moduleDetails)}");
}
}
async Task<IEnumerable<ModuleConfigurationWithName>> TraverseRepoFolder(DirectoryInfo repoRoot, DirectoryInfo currentDirectory)
{
var modulesFound = Enumerable.Empty<ModuleConfigurationWithName>();
var moduleConfigFileStub = Path.Combine(currentDirectory.FullName, Constants.ModuleConfigFileStub);
var (moduleConfig, _) = await _moduleConfigurationResolver.ResolveModuleConfiguration(moduleConfigFileStub);
if (moduleConfig is not null)
{
var moduleName = currentDirectory.FullName[(repoRoot.FullName.TrimEnd(Path.DirectorySeparatorChar).Length + 1)..].Replace(Path.DirectorySeparatorChar, '/');
modulesFound = modulesFound.Append(new(moduleName, moduleConfig));
}
foreach (var subDir in currentDirectory.GetDirectories())
{
modulesFound = modulesFound.Concat(await TraverseRepoFolder(repoRoot, subDir));
}
return modulesFound;
}
async Task ProcessModuleInfoAsync(string repoName, string moduleName)
{
var (moduleConfiguration, moduleConfigFileName) = await GetModuleConfiguration(repoName, moduleName);
if (moduleConfiguration is null)
{
_logger.LogInformation($"No configuration is found for module '{moduleName}' in repository '{repoName}':");
return;
}
_logger.LogInformation($"Information about module '{moduleName}' in repository '{repoName}':");
_logger.LogInformation("");
var moduleTargetPath = moduleConfiguration.Target is not null
? _pathHelperService.ResolvePath(moduleConfiguration.Target)
: null;
if (moduleTargetPath is not null)
{
_logger.LogInformation($"Target directory is: {moduleTargetPath}");
_logger.LogInformation("");
}
if (moduleConfiguration.Install is not null)
{
_logger.LogInformation("Can be installed.");
}
if (moduleConfiguration.Configure is not null)
{
_logger.LogInformation("Can be configured.");
}
if (moduleConfiguration.Links is { } links && links.Count != 0)
{
var linkCount = links.Count;
_logger.LogInformation("");
_logger.LogInformation($"Has {linkCount} link{(linkCount > 1 ? "s" : "")}:");
_logger.LogInformation("");
foreach (var link in links)
{
var sourcePath = Path.Combine(new FileInfo(moduleConfigFileName!).Directory!.FullName, link.Key);
var sourceExists = File.Exists(sourcePath) || Directory.Exists(sourcePath);
var sourceColor = sourceExists ? ColorCodes.GreenForeground : ColorCodes.RedForeground;
var targetColor = ColorCodes.RedForeground;
if (moduleTargetPath is not null)
{
var targetPath = Path.Combine(moduleTargetPath, link.Key);
var targetExists = File.Exists(targetPath) || Directory.Exists(targetPath);
targetColor = targetExists ? ColorCodes.GreenForeground : ColorCodes.RedForeground;
}
_logger.LogInformation($"{sourceColor}{link.Key}{ColorCodes.Reset} -> {targetColor}{link.Value}");
}
}
else
{
_logger.LogInformation("Has no links.");
}
}
}

View File

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

View File

@@ -1,62 +0,0 @@
using Alma.Configuration.Repository;
using Alma.Logging;
using Alma.Services;
namespace Alma.Command.Install;
public class InstallCommand : RepositoryModuleCommandBase
{
private readonly ILogger<InstallCommand> _logger;
private readonly IShellService _shellService;
public override string CommandString => "install";
public override string[] CommandAliases => Array.Empty<string>();
public InstallCommand(
ILogger<InstallCommand> logger,
IRepositoryConfiguration repositoryConfiguration,
IModuleConfigurationResolver moduleConfigurationResolver,
IShellService shellService,
IPathHelperService pathHelperService)
: base(repositoryConfiguration, pathHelperService, moduleConfigurationResolver)
{
_logger = logger;
_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;
}
var (moduleConfiguration, _) = await GetModuleConfiguration(repoName, moduleName);
if (moduleConfiguration is null)
{
_logger.LogInformation($"No module configuration found for module '{moduleName}'{(repoName is null ? "" : $" in repository '{repoName}'")}");
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

@@ -3,65 +3,51 @@ using Alma.Configuration.Module;
using Alma.Configuration.Repository;
using Alma.Data;
using Alma.Logging;
using Alma.Models;
using Alma.Services;
namespace Alma.Command.Link;
public class LinkCommand : RepositoryModuleCommandBase
public class LinkCommand : ICommand
{
private readonly IRepositoryConfiguration _repositoryConfiguration;
private readonly IModuleConfigurationResolver _moduleConfigurationResolver;
private readonly IMetadataHandler _metadataHandler;
private readonly IPathHelperService _pathHelperService;
private readonly ILogger<LinkCommand> _logger;
public override string CommandString => "link";
public override string[] CommandAliases => Array.Empty<string>();
public string CommandString => "link";
public LinkCommand(
IRepositoryConfiguration repositoryConfiguration,
IModuleConfigurationResolver moduleConfigurationResolver,
IMetadataHandler metadataHandler,
IPathHelperService pathHelperService,
ILogger<LinkCommand> logger)
: base(repositoryConfiguration, pathHelperService, moduleConfigurationResolver)
{
_repositoryConfiguration = repositoryConfiguration;
_moduleConfigurationResolver = moduleConfigurationResolver;
_metadataHandler = metadataHandler;
_pathHelperService = pathHelperService;
_logger = logger;
}
public override async Task Run(List<string> parameters)
public async Task Run(List<string> parameters)
{
if (parameters.Contains("--help"))
{
_logger.LogInformation(
"""
Usage:
alma link [module]
alma link [repository] [module]
Options:
--help Show this message
-d, --dry-run Show what would be linked without actually linking
"""
);
return;
}
var dryRun = parameters.Contains("-d") || parameters.Contains("--dry-run");
var (repoName, moduleName) = GetRepositoryAndModuleName(parameters);
if (moduleName is null)
if (parameters.Count == 0)
{
_logger.LogInformation("No module specified");
return;
}
var (sourceDirectory, targetDirectory) = GetRepositorySourceAndTargetDirectory(repoName);
string moduleName = parameters[0];
string sourceDirectory = Path.Combine(Environment.CurrentDirectory);
string targetDirectory = Path.Combine(Environment.CurrentDirectory, "..");
var repoName = GetRepositoryName(parameters);
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))
{
@@ -83,7 +69,7 @@ public class LinkCommand : RepositoryModuleCommandBase
if (moduleConfiguration?.Target is string moduleTargetDir)
{
targetDirectory = _pathHelperService.ResolvePath(moduleTargetDir, targetDirectory);
targetDirectory = ResolvePath(moduleTargetDir, targetDirectory);
}
if (!Directory.Exists(targetDirectory))
@@ -101,46 +87,14 @@ public class LinkCommand : RepositoryModuleCommandBase
moduleDir,
currentTargetDirectory,
moduleConfiguration)).ToList();
// Exclude
if (moduleConfigurationFile is not null) itemsToLink.RemoveAll(i => i.SourcePath == moduleConfigurationFileFullPath);
if (moduleConfiguration?.Exclude is { } excludeList)
{
foreach (var itemToExclude in excludeList)
{
var excludePath = Path.Combine(moduleDirectory, Path.Combine(itemToExclude.Split('/')));
itemsToLink.RemoveAll(
i => i.SourcePath == excludePath
|| i.SourcePath.StartsWith(excludePath + Path.DirectorySeparatorChar)
);
}
}
if(moduleConfiguration?.ExcludeReadme ?? false)
{
foreach (var readmeFile in Enum.GetValues<ReadmeFiles>())
{
var readmeFilePath = Path.Combine(moduleDirectory, readmeFile.GetFileName());
itemsToLink.RemoveAll(i => i.SourcePath == readmeFilePath);
}
}
// Linking
if (dryRun)
{
_logger.LogInformation("Dry run. No links will be created. The following links would be created:");
}
var successfulLinks = CreateLinks(itemsToLink, dryRun);
if (dryRun) return;
var successfulLinks = CreateLinks(itemsToLink);
await _metadataHandler.SaveLinkedItemsAsync(successfulLinks, moduleDir, currentTargetDirectory);
}
private List<ItemToLink> CreateLinks(List<ItemToLink> itemsToLink, bool dryRun)
private List<ItemToLink> CreateLinks(List<ItemToLink> itemsToLink)
{
var successfulLinks = new List<ItemToLink>();
@@ -159,21 +113,18 @@ public class LinkCommand : RepositoryModuleCommandBase
_logger.LogInformation($"Linking: '{itemToLink.SourcePath}' '{itemToLink.TargetPath}'");
if (!dryRun)
if (sourceFileExists)
{
if (sourceFileExists)
{
File.CreateSymbolicLink(itemToLink.TargetPath, itemToLink.SourcePath);
}
else if (sourceDirectoryExists)
{
Directory.CreateSymbolicLink(itemToLink.TargetPath, itemToLink.SourcePath);
}
else
{
_logger.LogInformation("Source not exists: " + itemToLink.SourcePath);
continue;
}
File.CreateSymbolicLink(itemToLink.TargetPath, itemToLink.SourcePath);
}
else if (sourceDirectoryExists)
{
Directory.CreateSymbolicLink(itemToLink.TargetPath, itemToLink.SourcePath);
}
else
{
_logger.LogInformation("Source not exists: " + itemToLink.SourcePath);
continue;
}
successfulLinks.Add(itemToLink);
@@ -184,7 +135,7 @@ public class LinkCommand : RepositoryModuleCommandBase
_logger.LogInformation("An error occured while creating links: " + e.Message);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_logger.LogInformation("On Windows symlinks can be created only with Administrator privileges.");
_logger.LogInformation("On Windows symlinks can be greated only with Administrator privileges.");
}
}
@@ -211,7 +162,7 @@ public class LinkCommand : RepositoryModuleCommandBase
var relativePath = GetRelativePath(subDir.FullName, moduleDirectory.FullName);
if (moduleConfiguration?.Links?.ContainsKey(relativePath) ?? false)
{
filesToLink.Add(new ItemToLink(subDir.FullName, _pathHelperService.ResolvePath(moduleConfiguration.Links[relativePath], targetDirectory.FullName)));
filesToLink.Add(new ItemToLink(subDir.FullName, ResolvePath(moduleConfiguration.Links[relativePath], targetDirectory.FullName)));
}
else
{
@@ -229,5 +180,24 @@ public class LinkCommand : RepositoryModuleCommandBase
return filesToLink.Concat(subDirLinksToAdd);
}
private static string GetRelativePath(string full, string parent) => full[parent.Length..].TrimStart(Path.DirectorySeparatorChar);
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)
{
if (path.StartsWith("~"))
{
path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), path.Substring(2));
}
//TODO: more special character
return Path.Combine(currentDirectory, path);
}
private static string GetRelativePath(string full, string parent) => full.Substring(parent.Length).TrimStart(Path.DirectorySeparatorChar);
}

View File

@@ -12,7 +12,6 @@ public class ListCommand : ICommand
private readonly ILogger<ListCommand> _logger;
public string CommandString => "ls";
public string[] CommandAliases => Array.Empty<string>();
public ListCommand(
IRepositoryConfiguration repositoryConfiguration,

View File

@@ -1,92 +0,0 @@
using Alma.Command.Install;
using Alma.Configuration.Repository;
using Alma.Logging;
using Alma.Models;
using Alma.Services;
namespace Alma.Command.List;
public class ReadMeCommand : RepositoryModuleCommandBase
{
private ILogger<InstallCommand> _logger;
public override string CommandString => "readme";
public override string[] CommandAliases => Array.Empty<string>();
private readonly Dictionary<ReadmeFiles, Func<string, Task>> _readmeFilePrinters;
public ReadMeCommand(
ILogger<InstallCommand> logger,
IRepositoryConfiguration repositoryConfiguration,
IModuleConfigurationResolver moduleConfigurationResolver,
IPathHelperService pathHelperService)
: base(repositoryConfiguration, pathHelperService, moduleConfigurationResolver)
{
_logger = logger;
_readmeFilePrinters = new Dictionary<ReadmeFiles, Func<string, Task>>
{
{ ReadmeFiles.Markdown, PrintReadMeMd },
{ ReadmeFiles.Text, PrintReadMeText },
{ ReadmeFiles.NoExtension, PrintReadMeText },
};
}
public override async Task Run(List<string> parameters)
{
var (repoName, moduleName) = GetRepositoryAndModuleName(parameters);
if (moduleName is null)
{
_logger.LogInformation("No module specified");
return;
}
var (repoSource, _) = GetRepositorySourceAndTargetDirectory(repoName);
if (repoSource is null)
{
_logger.LogInformation("No repository source found");
return;
}
var fileFound = false;
foreach (var readmeFile in _readmeFilePrinters.Keys)
{
// TODO: make this case insensitive
var readmeFilePath = Path.Combine(repoSource, moduleName, readmeFile.ToString());
if (File.Exists(readmeFilePath))
{
fileFound = true;
await _readmeFilePrinters[readmeFile](readmeFilePath);
break;
}
}
if (!fileFound)
{
_logger.LogInformation("No README file found. Supported formats: README.md, README.txt, README");
}
}
private async Task PrintReadMeMd(string filePath)
{
var content = await File.ReadAllLinesAsync(filePath);
foreach (var line in content)
{
//TODO: Add support for markdown
_logger.LogInformation(line);
}
}
private async Task PrintReadMeText(string filePath)
{
var content = await File.ReadAllLinesAsync(filePath);
foreach (var line in content)
{
_logger.LogInformation(line);
}
}
}

View File

@@ -1,89 +0,0 @@
using Alma.Configuration.Repository;
using Alma.Data;
using Alma.Services;
namespace Alma.Command;
public abstract class RepositoryModuleCommandBase : ICommand
{
private readonly IRepositoryConfiguration _repositoryConfiguration;
private readonly IModuleConfigurationResolver _moduleConfigurationResolver;
private readonly IPathHelperService _pathHelperService;
public abstract string CommandString { get; }
public abstract string[] CommandAliases { get; }
public abstract Task Run(List<string> parameters);
protected RepositoryModuleCommandBase(
IRepositoryConfiguration repositoryConfiguration,
IPathHelperService pathHelperService,
IModuleConfigurationResolver moduleConfigurationResolver)
{
_repositoryConfiguration = repositoryConfiguration;
_pathHelperService = pathHelperService;
_moduleConfigurationResolver = moduleConfigurationResolver;
}
protected async Task<(Configuration.Module.ModuleConfiguration? mergedModuleConfig, string? moduleConfigFileName)> GetModuleConfiguration(string? repoName, string moduleName)
{
var (repoSourceDirectory, _) = GetRepositorySourceAndTargetDirectory(repoName);
var moduleNameAsPath = moduleName.Replace('/', Path.DirectorySeparatorChar);
var moduleDirectory = Path.Combine(repoSourceDirectory, moduleNameAsPath);
var moduleConfigFileStub = Path.Combine(moduleDirectory, Constants.ModuleConfigFileStub);
return await _moduleConfigurationResolver.ResolveModuleConfiguration(moduleConfigFileStub);
}
protected (string? repoName, string? moduleName) GetRepositoryAndModuleName(List<string> parameters, bool singleParamIsRepo = false)
{
//TODO: handle parameters
string? repositoryName = null;
string? moduleName = null;
parameters = parameters.Where(p => !p.StartsWith("-")).ToList();
if (parameters.Count == 1)
{
if (singleParamIsRepo)
{
repositoryName = parameters[0];
}
else
{
moduleName = parameters[0];
}
}
else if (parameters.Count >= 1)
{
repositoryName = parameters[0];
moduleName = parameters[1];
}
return (repositoryName, moduleName);
}
protected (string repoSourceDirectory, string repoTargetDirectory) GetRepositorySourceAndTargetDirectory(string? repoName)
{
string repoSourceDirectory = Path.Combine(Environment.CurrentDirectory);
string repoTargetDirectory = Path.Combine(Environment.CurrentDirectory, "..");
return GetRepositorySourceAndTargetDirectory(repoName, repoSourceDirectory, repoTargetDirectory);
}
protected (string repoSourceDirectory, string repoTargetDirectory) GetRepositorySourceAndTargetDirectory(string? repoName, string fallbackSourceDirectory, string fallbackTargetDirectory)
{
if (repoName is not null
&& _repositoryConfiguration.Configuration.Repositories.Find(r => r.Name == repoName) is { } repoConfig)
{
fallbackSourceDirectory =
repoConfig.RepositoryPath is { } repoPath
? _pathHelperService.ResolvePath(repoPath)
: fallbackSourceDirectory;
fallbackTargetDirectory =
repoConfig.LinkPath is { } linkPath
? _pathHelperService.ResolvePath(linkPath)
: fallbackTargetDirectory;
}
return (fallbackSourceDirectory, fallbackTargetDirectory);
}
}

View File

@@ -3,7 +3,6 @@ namespace Alma.Command.Unlink;
public class UnlinkCommand : ICommand
{
public string CommandString => "unlink";
public string[] CommandAliases => Array.Empty<string>();
public Task Run(List<string> parameters)
{
throw new NotImplementedException();

View File

@@ -1,20 +0,0 @@
namespace Alma.Models;
public enum ReadmeFiles
{
Markdown,
Text,
NoExtension
}
public static class ReadmeFileTypeExtensions
{
public static string GetFileName(this ReadmeFiles type)
=> type switch
{
ReadmeFiles.Markdown => "README.md",
ReadmeFiles.Text => "README.txt",
ReadmeFiles.NoExtension => "README",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}

View File

@@ -19,14 +19,7 @@ public class ConfigurationFileReader
{
foreach (var configurationFileReader in _configurationFileReaders)
{
if (await configurationFileReader.DeserializeAsync<T>(
fileNameWithoutExtension,
contextGenerator,
extension) is { Result: { } } result
)
{
return result;
}
if (await configurationFileReader.DeserializeAsync<T>(fileNameWithoutExtension, contextGenerator, extension) is { Result: { } } result) return result;
}
return (null, null);

View File

@@ -1,70 +1,47 @@
using System.Diagnostics.CodeAnalysis;
using Alma.Command.Diag;
using Alma.Logging;
namespace Alma.Services;
public class FolderService : IFolderService
{
private readonly Dictionary<string, Func<string?>> _configHomeProviders;
private readonly Dictionary<string, Func<string?>> _appDataProviders;
public string? ConfigRoot { get; }
public string AppData { get; }
public string ApplicationSubfolderName => "alma";
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(FolderService))]
public FolderService()
{
_configHomeProviders = ConfigHomeProviders();
_appDataProviders = AppDataProviders();
ConfigRoot = GetConfigHomePath();
AppData = GetAppDataPath();
if (!Directory.Exists(AppData)) Directory.CreateDirectory(AppData);
}
private static Dictionary<string, Func<string?>> ConfigHomeProviders()
{
return new Dictionary<string, Func<string?>>
{
{"ALMA_CONFIG", () => Environment.GetEnvironmentVariable("ALMA_CONFIG")},
{"XDG_CONFIG_HOME", () => Environment.GetEnvironmentVariable("XDG_CONFIG_HOME")},
{"UserProfile", GetPreferredConfigurationFolder},
{"ALMA_CONFIG_FALLBACK", () => Environment.GetEnvironmentVariable("ALMA_CONFIG_FALLBACK")},
};
}
private static Dictionary<string, Func<string?>> AppDataProviders()
{
return new Dictionary<string, Func<string?>>
{
{"ALMA_APP_DATA", () => Environment.GetEnvironmentVariable("ALMA_APP_DATA")},
{"LocalApplicationData", () => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)},
{"ALMA_APP_DATA_FALLBACK", () => Environment.GetEnvironmentVariable("ALMA_APP_DATA_FALLBACK")},
};
}
string IFolderService.GetPreferredConfigurationFolder()
=> GetPreferredConfigurationFolder();
public static string GetPreferredConfigurationFolder()
public string GetPreferredConfigurationFolder()
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
private string? GetConfigHomePath()
{
var configHome = EnumerateProviders(_configHomeProviders.Values);
var configHomeProviders = new List<Func<string?>>
{
() => Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"),
() => GetPreferredConfigurationFolder()
};
var configHome = EnumerateProviders(configHomeProviders);
return configHome == null ? null : Path.Combine(configHome, ApplicationSubfolderName);
}
private string GetAppDataPath()
{
var appData = EnumerateProviders(_appDataProviders.Values) ?? Environment.CurrentDirectory;
var appDataProviders = new List<Func<string?>>
{
() => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
};
var appData = EnumerateProviders(appDataProviders) ?? Environment.CurrentDirectory;
return Path.Combine(appData, ApplicationSubfolderName);
}
private static string? EnumerateProviders(IEnumerable<Func<string?>> providers)
private static string? EnumerateProviders(List<Func<string?>> providers)
{
string? result = null;
@@ -76,26 +53,4 @@ public class FolderService : IFolderService
return result;
}
[DiagnosticHelper("config-home-providers")]
public static void ConfigHomeProviderDiag(ILogger logger)
{
var configHomeProviders = ConfigHomeProviders();
logger.LogInformation($"There are {configHomeProviders.Count} config home providers:");
foreach (var configHome in configHomeProviders)
{
logger.LogInformation($"{configHome.Key} => {configHome.Value() ?? "<null>"}");
}
}
[DiagnosticHelper("app-data-providers")]
public static void AppDataProviderDiag(ILogger logger)
{
var appDataProviders = AppDataProviders();
logger.LogInformation($"There are {appDataProviders.Count} app data providers:");
foreach (var appData in appDataProviders)
{
logger.LogInformation($"{appData.Key} => {appData.Value() ?? "<null>"}");
}
}
}

View File

@@ -17,12 +17,6 @@ public class JsonConfigurationFileReader : IConfigurationFileReader
if (!File.Exists(fileName)) return (null, null);
await using FileStream openStream = File.OpenRead(fileName);
var result =
(T?)await JsonSerializer.DeserializeAsync(
openStream,
typeof(T),
contextGenerator(new JsonSerializerOptions(DefaultOptions))
);
return (result, fileName);
return ((T?)await JsonSerializer.DeserializeAsync(openStream, typeof(T), contextGenerator(new JsonSerializerOptions(DefaultOptions))), fileName);
}
}

View File

@@ -21,19 +21,11 @@ public class ModuleConfigurationResolver : IModuleConfigurationResolver
if (moduleConfigRoot is null) return (null, null);
var validModuleConfigurations = await moduleConfigRoot
.ToAsyncEnumerable()
.WhereAwait(async m => await _osInformation.IsOnPlatformAsync(m.Key))
.ToListAsync();
var validModuleConfigurations = moduleConfigRoot.Where(m => _osInformation.IsOnPlatform(m.Key));
//TODO: priority order
var orderedValidModuleConfigurations = new Dictionary<string, ModuleConfiguration>(validModuleConfigurations);
if (orderedValidModuleConfigurations.Count == 0)
{
return (ModuleConfiguration.Empty(), moduleConfigFileName);
}
var mergedModuleConfig = orderedValidModuleConfigurations
.Select(m => m.Value)
.Aggregate((a, b) => a.Merge(b));

View File

@@ -6,52 +6,19 @@ public class OsInformation : IOsInformation
{
private const string OsIdentifierDefault = "default";
private const string OsIdentifierWin = "windows";
private const string OsIdentifierMac = "macos";
private const string OsIdentifierFreeBsd = "freebsd";
private const string OsIdentifierLinux = "linux";
private const string LinuxOsRelease = "/etc/os-release";
public async Task<string> GetOsIdentifierAsync()
public string GetOsIdentifier()
{
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;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return OsIdentifierWin;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return 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";
var architecture = RuntimeInformation.ProcessArchitecture.ToString().ToLower();
return baseOsIdentifier + "-" + architecture;
return "unknown";
}
public async Task<bool> IsOnPlatformAsync(string platform)
public bool IsOnPlatform(string platform)
{
return platform == OsIdentifierDefault
|| (await GetOsIdentifierAsync()).StartsWith(platform, StringComparison.InvariantCultureIgnoreCase);
if (platform == OsIdentifierDefault) return true;
return platform == GetOsIdentifier();
}
}

View File

@@ -1,47 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Alma.Command.Diag;
using Alma.Data;
using Alma.Logging;
namespace Alma.Services;
public class PathHelperService : IPathHelperService
{
private static readonly List<SpecialPathResolver> _specialPathResolvers = new()
{
new("~", () => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), true),
new("%DOCUMENTS%", () => Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)),
};
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PathHelperService))]
public string ResolvePath(string path, string? currentDirectory = null)
{
var skipCombiningCurrentDirectory = false;
foreach (var specialPathResolver in _specialPathResolvers)
{
if (path.Contains(specialPathResolver.PathName))
{
var parts = path.Split(specialPathResolver.PathName);
path = string.Join(specialPathResolver.Resolver(), parts);
skipCombiningCurrentDirectory = (specialPathResolver.SkipCombiningCurrentDirectory ?? false) || skipCombiningCurrentDirectory;
}
}
path = path.Replace('/', Path.DirectorySeparatorChar);
return currentDirectory is null || skipCombiningCurrentDirectory
? path
: Path.Combine(currentDirectory, path);
}
[DiagnosticHelper("special-path-resolver")]
public static void SpecialPathResolverDiag(ILogger logger)
{
logger.LogInformation($"There are {_specialPathResolvers.Count} special path resolvers:");
foreach (var specialPathResolver in _specialPathResolvers)
{
logger.LogInformation($"{specialPathResolver.PathName} => {specialPathResolver.Resolver()}");
}
}
}

View File

@@ -1,80 +0,0 @@
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)
{
Process? process;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
process = CreateLinuxShell(command);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
process = CreateWindowsShell(command);
}
else
{
_logger.LogError("Platform not supported");
throw new NotSupportedException();
}
if (!process.Start()) return;
var reader = process.StandardOutput;
while (!reader.EndOfStream)
{
var content = await reader.ReadLineAsync();
if (content is not null)
{
_logger.LogInformation(content);
}
}
await process.WaitForExitAsync();
}
private Process CreateLinuxShell(string command)
{
var processStartInfo = new ProcessStartInfo
{
FileName = "sh",
ArgumentList = {"-c", command},
RedirectStandardOutput = true,
RedirectStandardInput = true,
CreateNoWindow = true,
UseShellExecute = false
};
return new Process {StartInfo = processStartInfo};
}
private Process CreateWindowsShell(string command)
{
var processStartInfo = new ProcessStartInfo
{
//TODO: customizable shell
FileName = "pwsh",
ArgumentList = {"-c", command},
RedirectStandardOutput = true,
RedirectStandardInput = true,
CreateNoWindow = true,
UseShellExecute = false
};
return new Process {StartInfo = processStartInfo};
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@@ -1,17 +1,10 @@
namespace Alma.Logging;
public interface ILogger
public interface ILogger<T>
{
LogLevel DefaultLogLevel { get; }
void LogInformation(string logMessage);
void LogDebug(string logMessage);
void LogTrace(string logMessage);
void Log(string logMessage, LogLevel logLevel);
void LogError(string logMessage);
void LogCritical(string logMessage);
}
public interface ILogger<T> : ILogger
{
}

View File

@@ -3,6 +3,5 @@
public interface ILoggerFactory
{
ILogger<T> CreateLogger<T>();
ILogger CreateLogger(Type t);
LogLevel DefaultLogLevel { get; }
}

View File

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

View File

@@ -1,11 +1,10 @@
namespace Alma.Logging;
public class Logger : ILogger
public class Logger<T> : ILogger<T>
{
public LogLevel DefaultLogLevel { get; }
public Logger(LogLevel defaultLogLevel, string topicName)
public Logger(LogLevel defaultLogLevel)
{
DefaultLogLevel = defaultLogLevel;
}
@@ -23,22 +22,4 @@ public class Logger : ILogger
Console.WriteLine(s);
}
}
public void LogError(string logMessage)
{
Log(logMessage, LogLevel.Error);
}
public void LogCritical(string logMessage)
{
Log(logMessage, LogLevel.Critical);
}
}
public class Logger<T> : Logger, ILogger<T>
{
public Logger(LogLevel defaultLogLevel) : base(defaultLogLevel, typeof(T).Name)
{
}
}

View File

@@ -13,9 +13,4 @@ public class LoggerFactory : ILoggerFactory
{
return new Logger<T>(DefaultLogLevel);
}
public ILogger CreateLogger(Type t)
{
return new Logger(DefaultLogLevel, t.Name);
}
}

View File

@@ -6,16 +6,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jab" Version="0.10.2" />
<PackageReference Include="Jab" Version="0.8.4" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<VersionPrefix>0.0.4</VersionPrefix>
<VersionSuffix>development</VersionSuffix>
</PropertyGroup>
</Project>

View File

@@ -1,9 +1,5 @@
using Alma.Command;
using Alma.Command.Configure;
using Alma.Command.Diag;
using Alma.Command.Help;
using Alma.Command.Info;
using Alma.Command.Install;
using Alma.Command.Link;
using Alma.Command.List;
using Alma.Command.Unlink;
@@ -16,18 +12,46 @@ namespace Alma;
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)
{
InitLogging();
var logger = AlmaLoggerFactory.CreateLogger(typeof(Program));
var workdir = GetWorkdir(logger);
if (workdir != null)
{
Environment.CurrentDirectory = workdir;
}
var services = new AlmaServiceProvider();
var repositoryConfiguration = services.GetService<IRepositoryConfiguration>();
@@ -37,39 +61,14 @@ public static class Program
await application.Run(args);
}
private static string? GetWorkdir(ILogger logger)
private static ILoggerFactory InitLogging()
{
var workdirProviders = new Dictionary<string, Func<string?>>
{
{"ALMA_WORKDIR", () => Environment.GetEnvironmentVariable("ALMA_WORKDIR")},
{"WORKDIR", () => Environment.GetEnvironmentVariable("WORKDIR")},
};
var loggerFactory = new LoggerFactory();
foreach (var workdirProvider in workdirProviders)
{
var workdir = workdirProvider.Value();
if (workdir != null)
{
if (Directory.Exists(workdir))
{
return workdir;
}
else
{
logger.LogInformation($"{workdirProvider.Key} is set to {workdir} but this directory does not exist.");
}
}
}
return null;
return AlmaLoggerFactory = loggerFactory;
}
private static void InitLogging()
{
AlmaLoggerFactory = new LoggerFactory();
}
public static ILoggerFactory AlmaLoggerFactory { get; private set; } = null!;
public static ILoggerFactory AlmaLoggerFactory { get; private set; }
}
[ServiceProvider]
@@ -80,18 +79,12 @@ public static class Program
[Singleton(typeof(IOsInformation), typeof(OsInformation))]
[Singleton(typeof(ICommand), typeof(LinkCommand))]
[Singleton(typeof(ICommand), typeof(UnlinkCommand))]
[Singleton(typeof(ICommand), typeof(InfoCommand))]
[Singleton(typeof(ICommand), typeof(ModuleInfoCommand))]
[Singleton(typeof(ICommand), typeof(ListCommand))]
[Singleton(typeof(ICommand), typeof(InstallCommand))]
[Singleton(typeof(ICommand), typeof(HelpCommand))]
[Singleton(typeof(ICommand), typeof(ConfigureCommand))]
[Singleton(typeof(ICommand), typeof(DiagCommand))]
[Singleton(typeof(ICommand), typeof(ReadMeCommand))]
//Dependency cycle
//[Singleton(typeof(ICommand), typeof(HelpCommand))]
[Singleton(typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver))]
[Singleton(typeof(IMetadataHandler), typeof(MetadataHandler))]
[Singleton(typeof(IShellService), typeof(ShellService))]
[Singleton(typeof(IVersionService), typeof(VersionService))]
[Singleton(typeof(IPathHelperService), typeof(PathHelperService))]
[Singleton(typeof(Application))]
[Transient(typeof(ILogger<>), Factory = nameof(CustomLoggerFactory))]
internal partial class AlmaServiceProvider

View File

@@ -1,15 +0,0 @@
using System.Reflection;
using Alma.Services;
namespace Alma;
public class VersionService : IVersionService
{
public string GetVersion()
{
return
typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion
?? typeof(Program).Assembly.GetName().Version?.ToString()
?? "unknown";
}
}