Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5892210f9d | |||
| 4b40aa64b9 | |||
| 2d4fc2c345 | |||
| 6d39621196 | |||
| 20a49c4f94 | |||
| 7351bf4fd1 | |||
| 89f1c37cf6 | |||
| f1b2495fb6 | |||
| 8b9702b935 | |||
| 9027f28065 | |||
| 4f0e7262ce | |||
| 986696b7d0 | |||
| 11598c220e | |||
| 954d26f4eb | |||
| d7043e1107 | |||
| 43c03c4f97 | |||
| 5f495c8390 | |||
| 82f5a990f3 | |||
| 1f4427644c |
3
.devcontainer/alpine-container/devcontainer.json
Normal file
3
.devcontainer/alpine-container/devcontainer.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"image": "mcr.microsoft.com/dotnet/sdk:8.0-alpine"
|
||||
}
|
||||
3
.devcontainer/debian-container/devcontainer.json
Normal file
3
.devcontainer/debian-container/devcontainer.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"image": "mcr.microsoft.com/dotnet/sdk:8.0"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"image": "mcr.microsoft.com/dotnet/sdk:7.0-alpine"
|
||||
}
|
||||
2
.editorconfig
Normal file
2
.editorconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
[*.cs]
|
||||
|
||||
15
.github/workflows/github-actions.yml
vendored
15
.github/workflows/github-actions.yml
vendored
@@ -21,12 +21,17 @@ jobs:
|
||||
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: '7.0.x'
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Patch version
|
||||
run: ${{ matrix.target.version_script }}
|
||||
continue-on-error: true
|
||||
@@ -53,6 +58,11 @@ jobs:
|
||||
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
|
||||
@@ -62,6 +72,9 @@ jobs:
|
||||
|
||||
- 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
|
||||
|
||||
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -10,8 +10,8 @@
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/src/Alma/bin/Debug/net7.0/Alma.dll",
|
||||
"args": [],
|
||||
"program": "${workspaceFolder}/src/Alma/bin/Debug/net8.0/Alma.dll",
|
||||
"args": ["link", "dotconfig", "git", "-d"],
|
||||
"cwd": "${workspaceFolder}/src/Alma",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
"console": "internalConsole",
|
||||
|
||||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
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"]
|
||||
20
README.md
20
README.md
@@ -9,4 +9,24 @@ Alma (aka Advanced Link Manager Application) is another dotfiles management tool
|
||||
```
|
||||
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 ...
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Alma</RootNamespace>
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace Alma.Command;
|
||||
|
||||
public interface ICommand
|
||||
{
|
||||
public string CommandString { get; }
|
||||
public Task Run(List<string> parameters);
|
||||
string CommandString { get; }
|
||||
string[] CommandAliases { get; }
|
||||
Task Run(List<string> parameters);
|
||||
}
|
||||
@@ -4,6 +4,8 @@ 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; }
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LiteDB" Version="5.0.19" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Alma</RootNamespace>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Alma.Command;
|
||||
using Alma.Command.Help;
|
||||
using Alma.Logging;
|
||||
|
||||
namespace Alma;
|
||||
@@ -25,7 +24,7 @@ public class Application
|
||||
|
||||
var commandString = args[0];
|
||||
|
||||
var command = _commands.FirstOrDefault(c => c.CommandString == commandString);
|
||||
var command = _commands.FirstOrDefault(c => c.CommandString == commandString || c.CommandAliases.Contains(commandString));
|
||||
|
||||
if (command is null)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Alma.Command.Install;
|
||||
using Alma.Configuration.Repository;
|
||||
using Alma.Data;
|
||||
using Alma.Logging;
|
||||
using Alma.Services;
|
||||
|
||||
@@ -11,6 +10,7 @@ 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,
|
||||
|
||||
131
src/Alma.App/Command/Diag/DiagCommand.cs
Normal file
131
src/Alma.App/Command/Diag/DiagCommand.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
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;
|
||||
}
|
||||
12
src/Alma.App/Command/Diag/DiagnosticHelper.cs
Normal file
12
src/Alma.App/Command/Diag/DiagnosticHelper.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Alma.Command.Diag;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class DiagnosticHelper : Attribute
|
||||
{
|
||||
public string Command { get; }
|
||||
|
||||
public DiagnosticHelper(string command)
|
||||
{
|
||||
Command = command;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ public class HelpCommand : ICommand
|
||||
|
||||
public string CommandString => "help";
|
||||
|
||||
public string[] CommandAliases { get; } = ["--help", "-h"];
|
||||
|
||||
public HelpCommand(
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<HelpCommand> logger
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Alma.Configuration.Module;
|
||||
using Alma.Configuration.Repository;
|
||||
using Alma.Data;
|
||||
using Alma.Helper;
|
||||
using Alma.Logging;
|
||||
using Alma.Services;
|
||||
|
||||
@@ -10,6 +9,7 @@ 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;
|
||||
@@ -66,7 +66,7 @@ public class InfoCommand : RepositoryModuleCommandBase
|
||||
}
|
||||
}
|
||||
|
||||
async Task ProcessGeneralInfoAsync()
|
||||
private async Task ProcessGeneralInfoAsync()
|
||||
{
|
||||
_logger.LogInformation("Alma " + _versionService.GetVersion());
|
||||
_logger.LogInformation("");
|
||||
@@ -87,7 +87,7 @@ public class InfoCommand : RepositoryModuleCommandBase
|
||||
_logger.LogInformation($"Platform is '{await _osInformation.GetOsIdentifierAsync()}'");
|
||||
_logger.LogInformation("");
|
||||
|
||||
if (_repositoryConfiguration.Configuration.Repositories is {Count: > 0} repositories)
|
||||
if (_repositoryConfiguration.Configuration.Repositories is { Count: > 0 } repositories)
|
||||
{
|
||||
_logger.LogInformation("Repositories:");
|
||||
foreach (var repository in repositories)
|
||||
@@ -107,7 +107,7 @@ public class InfoCommand : RepositoryModuleCommandBase
|
||||
}
|
||||
}
|
||||
|
||||
async Task ProcessRepoInfoAsync(string repoName)
|
||||
private async Task ProcessRepoInfoAsync(string repoName)
|
||||
{
|
||||
var (repoSourceDirectory, _) = GetRepositorySourceAndTargetDirectory(repoName);
|
||||
|
||||
@@ -138,7 +138,7 @@ public class InfoCommand : RepositoryModuleCommandBase
|
||||
|
||||
if (moduleConfig is not null)
|
||||
{
|
||||
var moduleName = currentDirectory.FullName[(repoRoot.FullName.Length + 1)..].Replace(Path.DirectorySeparatorChar, '/');
|
||||
var moduleName = currentDirectory.FullName[(repoRoot.FullName.TrimEnd(Path.DirectorySeparatorChar).Length + 1)..].Replace(Path.DirectorySeparatorChar, '/');
|
||||
modulesFound = modulesFound.Append(new(moduleName, moduleConfig));
|
||||
}
|
||||
|
||||
@@ -213,4 +213,4 @@ public class InfoCommand : RepositoryModuleCommandBase
|
||||
_logger.LogInformation("Has no links.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using Alma.Configuration.Repository;
|
||||
using Alma.Data;
|
||||
using Alma.Logging;
|
||||
using Alma.Services;
|
||||
|
||||
@@ -11,6 +9,7 @@ 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,
|
||||
|
||||
@@ -2,8 +2,8 @@ using System.Runtime.InteropServices;
|
||||
using Alma.Configuration.Module;
|
||||
using Alma.Configuration.Repository;
|
||||
using Alma.Data;
|
||||
using Alma.Helper;
|
||||
using Alma.Logging;
|
||||
using Alma.Models;
|
||||
using Alma.Services;
|
||||
|
||||
namespace Alma.Command.Link;
|
||||
@@ -17,6 +17,7 @@ public class LinkCommand : RepositoryModuleCommandBase
|
||||
private readonly ILogger<LinkCommand> _logger;
|
||||
|
||||
public override string CommandString => "link";
|
||||
public override string[] CommandAliases => Array.Empty<string>();
|
||||
|
||||
public LinkCommand(
|
||||
IRepositoryConfiguration repositoryConfiguration,
|
||||
@@ -35,6 +36,24 @@ public class LinkCommand : RepositoryModuleCommandBase
|
||||
|
||||
public override 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)
|
||||
{
|
||||
@@ -82,14 +101,46 @@ public class LinkCommand : RepositoryModuleCommandBase
|
||||
moduleDir,
|
||||
currentTargetDirectory,
|
||||
moduleConfiguration)).ToList();
|
||||
|
||||
// Exclude
|
||||
if (moduleConfigurationFile is not null) itemsToLink.RemoveAll(i => i.SourcePath == moduleConfigurationFileFullPath);
|
||||
|
||||
var successfulLinks = CreateLinks(itemsToLink);
|
||||
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;
|
||||
|
||||
await _metadataHandler.SaveLinkedItemsAsync(successfulLinks, moduleDir, currentTargetDirectory);
|
||||
}
|
||||
|
||||
private List<ItemToLink> CreateLinks(List<ItemToLink> itemsToLink)
|
||||
private List<ItemToLink> CreateLinks(List<ItemToLink> itemsToLink, bool dryRun)
|
||||
{
|
||||
var successfulLinks = new List<ItemToLink>();
|
||||
|
||||
@@ -108,18 +159,21 @@ public class LinkCommand : RepositoryModuleCommandBase
|
||||
|
||||
_logger.LogInformation($"Linking: '{itemToLink.SourcePath}' '{itemToLink.TargetPath}'");
|
||||
|
||||
if (sourceFileExists)
|
||||
if (!dryRun)
|
||||
{
|
||||
File.CreateSymbolicLink(itemToLink.TargetPath, itemToLink.SourcePath);
|
||||
}
|
||||
else if (sourceDirectoryExists)
|
||||
{
|
||||
Directory.CreateSymbolicLink(itemToLink.TargetPath, itemToLink.SourcePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Source not exists: " + itemToLink.SourcePath);
|
||||
continue;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
successfulLinks.Add(itemToLink);
|
||||
@@ -147,7 +201,14 @@ public class LinkCommand : RepositoryModuleCommandBase
|
||||
var filesToLink = new List<ItemToLink>();
|
||||
foreach (var file in currentDirectory.GetFiles())
|
||||
{
|
||||
filesToLink.Add(new ItemToLink(Path.Combine(currentDirectory.FullName, file.Name), Path.Combine(currentTargetDirectory.FullName, file.Name)));
|
||||
if (moduleConfiguration?.Links?.ContainsKey(file.Name) ?? false)
|
||||
{
|
||||
filesToLink.Add(new ItemToLink(file.FullName, _pathHelperService.ResolvePath(moduleConfiguration.Links[file.Name], targetDirectory.FullName)));
|
||||
}
|
||||
else
|
||||
{
|
||||
filesToLink.Add(new ItemToLink(file.FullName, Path.Combine(currentTargetDirectory.FullName, file.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
var subDirLinksToAdd = Enumerable.Empty<ItemToLink>();
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ListCommand : ICommand
|
||||
private readonly ILogger<ListCommand> _logger;
|
||||
|
||||
public string CommandString => "ls";
|
||||
public string[] CommandAliases => Array.Empty<string>();
|
||||
|
||||
public ListCommand(
|
||||
IRepositoryConfiguration repositoryConfiguration,
|
||||
|
||||
92
src/Alma.App/Command/ReadMe/ReadMeCommand.cs
Normal file
92
src/Alma.App/Command/ReadMe/ReadMeCommand.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ public abstract class RepositoryModuleCommandBase : ICommand
|
||||
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(
|
||||
@@ -39,6 +41,8 @@ public abstract class RepositoryModuleCommandBase : ICommand
|
||||
string? repositoryName = null;
|
||||
string? moduleName = null;
|
||||
|
||||
parameters = parameters.Where(p => !p.StartsWith("-")).ToList();
|
||||
|
||||
if (parameters.Count == 1)
|
||||
{
|
||||
if (singleParamIsRepo)
|
||||
|
||||
@@ -3,6 +3,7 @@ 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();
|
||||
|
||||
20
src/Alma.App/Models/ReadmeFiles.cs
Normal file
20
src/Alma.App/Models/ReadmeFiles.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
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)
|
||||
};
|
||||
}
|
||||
@@ -19,7 +19,14 @@ 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);
|
||||
|
||||
@@ -1,47 +1,70 @@
|
||||
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);
|
||||
}
|
||||
|
||||
public string GetPreferredConfigurationFolder()
|
||||
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()
|
||||
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
|
||||
|
||||
private string? GetConfigHomePath()
|
||||
{
|
||||
var configHomeProviders = new List<Func<string?>>
|
||||
{
|
||||
() => Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"),
|
||||
() => GetPreferredConfigurationFolder()
|
||||
};
|
||||
|
||||
var configHome = EnumerateProviders(configHomeProviders);
|
||||
var configHome = EnumerateProviders(_configHomeProviders.Values);
|
||||
return configHome == null ? null : Path.Combine(configHome, ApplicationSubfolderName);
|
||||
}
|
||||
|
||||
private string GetAppDataPath()
|
||||
{
|
||||
var appDataProviders = new List<Func<string?>>
|
||||
{
|
||||
() => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
|
||||
};
|
||||
|
||||
var appData = EnumerateProviders(appDataProviders) ?? Environment.CurrentDirectory;
|
||||
var appData = EnumerateProviders(_appDataProviders.Values) ?? Environment.CurrentDirectory;
|
||||
return Path.Combine(appData, ApplicationSubfolderName);
|
||||
}
|
||||
|
||||
private static string? EnumerateProviders(List<Func<string?>> providers)
|
||||
private static string? EnumerateProviders(IEnumerable<Func<string?>> providers)
|
||||
{
|
||||
string? result = null;
|
||||
|
||||
@@ -53,4 +76,26 @@ 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>"}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,12 @@ public class JsonConfigurationFileReader : IConfigurationFileReader
|
||||
if (!File.Exists(fileName)) return (null, null);
|
||||
|
||||
await using FileStream openStream = File.OpenRead(fileName);
|
||||
return ((T?)await JsonSerializer.DeserializeAsync(openStream, typeof(T), contextGenerator(new JsonSerializerOptions(DefaultOptions))), fileName);
|
||||
var result =
|
||||
(T?)await JsonSerializer.DeserializeAsync(
|
||||
openStream,
|
||||
typeof(T),
|
||||
contextGenerator(new JsonSerializerOptions(DefaultOptions))
|
||||
);
|
||||
return (result, fileName);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using Alma.Data;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Alma.Command.Diag;
|
||||
using Alma.Data;
|
||||
using Alma.Logging;
|
||||
|
||||
namespace Alma.Services;
|
||||
|
||||
@@ -10,6 +13,7 @@ public class PathHelperService : IPathHelperService
|
||||
new("%DOCUMENTS%", () => Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)),
|
||||
};
|
||||
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PathHelperService))]
|
||||
public string ResolvePath(string path, string? currentDirectory = null)
|
||||
{
|
||||
var skipCombiningCurrentDirectory = false;
|
||||
@@ -30,4 +34,14 @@ public class PathHelperService : IPathHelperService
|
||||
? 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()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Alma.Logging;
|
||||
|
||||
public interface ILogger<T>
|
||||
public interface ILogger
|
||||
{
|
||||
LogLevel DefaultLogLevel { get; }
|
||||
void LogInformation(string logMessage);
|
||||
@@ -9,4 +9,9 @@ public interface ILogger<T>
|
||||
void Log(string logMessage, LogLevel logLevel);
|
||||
void LogError(string logMessage);
|
||||
void LogCritical(string logMessage);
|
||||
}
|
||||
|
||||
public interface ILogger<T> : ILogger
|
||||
{
|
||||
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
public interface ILoggerFactory
|
||||
{
|
||||
ILogger<T> CreateLogger<T>();
|
||||
ILogger CreateLogger(Type t);
|
||||
LogLevel DefaultLogLevel { get; }
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
namespace Alma.Logging;
|
||||
|
||||
public class Logger<T> : ILogger<T>
|
||||
|
||||
public class Logger : ILogger
|
||||
{
|
||||
public LogLevel DefaultLogLevel { get; }
|
||||
|
||||
public Logger(LogLevel defaultLogLevel)
|
||||
public Logger(LogLevel defaultLogLevel, string topicName)
|
||||
{
|
||||
DefaultLogLevel = defaultLogLevel;
|
||||
}
|
||||
@@ -32,4 +33,12 @@ public class Logger<T> : ILogger<T>
|
||||
{
|
||||
Log(logMessage, LogLevel.Critical);
|
||||
}
|
||||
}
|
||||
|
||||
public class Logger<T> : Logger, ILogger<T>
|
||||
{
|
||||
public Logger(LogLevel defaultLogLevel) : base(defaultLogLevel, typeof(T).Name)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,9 @@ public class LoggerFactory : ILoggerFactory
|
||||
{
|
||||
return new Logger<T>(DefaultLogLevel);
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(Type t)
|
||||
{
|
||||
return new Logger(DefaultLogLevel, t.Name);
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,15 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jab" Version="0.8.4" />
|
||||
<PackageReference Include="Jab" Version="0.10.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<VersionPrefix>0.0.0</VersionPrefix>
|
||||
<VersionPrefix>0.0.5</VersionPrefix>
|
||||
<VersionSuffix>development</VersionSuffix>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Alma.Command;
|
||||
using Alma.Command.Configure;
|
||||
using Alma.Command.Diag;
|
||||
using Alma.Command.Help;
|
||||
using Alma.Command.Info;
|
||||
using Alma.Command.Install;
|
||||
@@ -19,6 +20,14 @@ public static class Program
|
||||
{
|
||||
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>();
|
||||
@@ -28,6 +37,33 @@ public static class Program
|
||||
await application.Run(args);
|
||||
}
|
||||
|
||||
private static string? GetWorkdir(ILogger logger)
|
||||
{
|
||||
var workdirProviders = new Dictionary<string, Func<string?>>
|
||||
{
|
||||
{"ALMA_WORKDIR", () => Environment.GetEnvironmentVariable("ALMA_WORKDIR")},
|
||||
{"WORKDIR", () => Environment.GetEnvironmentVariable("WORKDIR")},
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static void InitLogging()
|
||||
{
|
||||
AlmaLoggerFactory = new LoggerFactory();
|
||||
@@ -49,6 +85,8 @@ public static class Program
|
||||
[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))]
|
||||
[Singleton(typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver))]
|
||||
[Singleton(typeof(IMetadataHandler), typeof(MetadataHandler))]
|
||||
[Singleton(typeof(IShellService), typeof(ShellService))]
|
||||
|
||||
Reference in New Issue
Block a user