Skip to content

Commit

Permalink
Merge pull request #47 from Nodsoft/develop
Browse files Browse the repository at this point in the history
Tool: Add Watcher feature to Manifest and SSG
  • Loading branch information
SakuraIsayeki authored Jul 6, 2024
2 parents 9be9104 + 0ef4aa2 commit 431341f
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 110 deletions.
163 changes: 116 additions & 47 deletions Nodsoft.MoltenObsidian.Tool/Commands/Manifest/GenerateManifestCommand.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
using Nodsoft.MoltenObsidian.Vaults.FileSystem;
using Nodsoft.MoltenObsidian.Manifest;
using Nodsoft.MoltenObsidian.Vault;
using Spectre.Console;
using Spectre.Console.Cli;

Expand Down Expand Up @@ -36,7 +38,11 @@ public sealed class GenerateManifestSettings : CommandSettings

[CommandOption("--debug", IsHidden = true)]
public bool DebugMode { get; set; }

[CommandOption("--watch"), Description("Watches the vault for changes and regenerates the manifest.")]
public bool Watch { get; set; }


public override ValidationResult Validate()
{
if (VaultPathStr is "" || (VaultPath = new(VaultPathStr)) is { Exists: false })
Expand All @@ -49,18 +55,16 @@ public override ValidationResult Validate()
return ValidationResult.Error($"The output path '{OutputPath}' does not exist.");
}

if (!Force)
if (!Force && !VaultPath.GetDirectories().Any(static d => d.Name == ".obsidian"))
{
if (!VaultPath.GetDirectories().Any(static d => d.Name == ".obsidian"))
{
return ValidationResult.Error($"The vault path '{VaultPath}' does not appear to be a valid Obsidian vault.");
}
return ValidationResult.Error($"The vault path '{VaultPath}' does not appear to be a valid Obsidian vault.");
}

return ValidationResult.Success();
}
}


/// <summary>
/// Provides a command that generates a manifest for a Molten Obsidian vault.
/// </summary>
Expand All @@ -72,15 +76,15 @@ public override async Task<int> ExecuteAsync(CommandContext context, GenerateMan
if (settings.DebugMode)
{
// Inform the user that we're in debug mode.
AnsiConsole.MarkupLine(/*lang=md*/"[bold blue]Debug mode is enabled.[/]");
AnsiConsole.MarkupLine( /*lang=md*/"[bold blue]Debug mode is enabled.[/]");
}

// This is where the magic happens.
// We'll be using the Spectre.Console library to provide a nice CLI experience.
// Statuses at each step, and a nice summary at the end.

FileSystemVault vault = null!;

// First, load the Obsidian vault. This will validate the vault and load all the files.
AnsiConsole.Console.Status().Start("Loading vault...", _ =>
{
Expand All @@ -90,8 +94,6 @@ public override async Task<int> ExecuteAsync(CommandContext context, GenerateMan
AnsiConsole.Console.MarkupLine(/*lang=md*/$"[grey]Ignoring folders:[/] {string.Join("[grey], [/]", settings.IgnoredFolders ?? ["*None*"])}");
AnsiConsole.Console.MarkupLine(/*lang=md*/$"[grey]Ignoring files:[/] {string.Join("[grey], [/]", settings.IgnoreFiles ?? ["*None*"])}");
}
settings.IgnoredFolders ??= FileSystemVault.DefaultIgnoredFolders.ToArray();
settings.IgnoreFiles ??= FileSystemVault.DefaultIgnoredFiles.ToArray();
Expand All @@ -101,62 +103,129 @@ public override async Task<int> ExecuteAsync(CommandContext context, GenerateMan
});

AnsiConsole.MarkupLine(/*lang=md*/$"Loaded vault with [green]{vault.Files.Count}[/] files.");

// Next, generate the manifest.
RemoteVaultManifest manifest = null!;
await AnsiConsole.Console.Status().StartAsync("Generating manifest...", async _ =>
{
// Generate the manifest.
manifest = await VaultManifestGenerator.GenerateManifestAsync(vault);
});

AnsiConsole.MarkupLine(/*lang=md*/$"Generated manifest with [green]{manifest.Files.Count}[/] files.");

// Finally, write the manifest to disk, at the specified location (or the vault root if not specified).
FileInfo manifestFile = new(Path.Combine((settings.OutputPath ?? settings.VaultPath).FullName, RemoteVaultManifest.ManifestFileName));

// Check if the file already exists.
if (manifestFile.Exists)
await GenerateManifestAsync(vault, settings.VaultPath, settings.OutputPath, settings.DebugMode, file =>
{
if (settings.Force)
if (settings.Force || settings.Watch)
{
// Warn the user that the file will be overwritten.
AnsiConsole.MarkupLine(/*lang=md*/"[yellow]A manifest file already exists at the specified location, but [green]--force[/] was specified. Overwriting.[/]");
string forceFlagStr = settings switch
{
{ Force: true } => "--force",
{ Watch: true } => "--watch",
_ => throw new UnreachableException()
};
AnsiConsole.MarkupLine(/*lang=md*/$"[yellow]A manifest file already exists at the specified location, but [green]{forceFlagStr}[/] was specified. Overwriting.[/]");
}
else
{
// If it does, ask the user if they want to overwrite it.
bool overwrite = AnsiConsole.Prompt(new ConfirmationPrompt(/*lang=md*/"[yellow]The manifest file already exists. Overwrite?[/]"));
if (!overwrite)
{
// If they don't, abort.
AnsiConsole.MarkupLine("[red]Aborted.[/]");
return 1;
AnsiConsole.MarkupLine(/*lang=md*/"[red]Aborted.[/]");
return false;
}
// If they do, delete the file.
manifestFile.Delete();
file.Delete();
}
}
await AnsiConsole.Console.Status().StartAsync("Writing manifest...", async _ =>
return true;
});

if (settings.Watch)
{
// Write the new manifest to disk.
await using FileStream stream = manifestFile.Open(FileMode.OpenOrCreate, FileAccess.Write);
stream.SetLength(0);
#pragma warning disable CA1869
await JsonSerializer.SerializeAsync(stream, manifest, new JsonSerializerOptions
await AnsiConsole.Console.Status().StartAsync("Watching vault for changes...", async ctx =>
{
WriteIndented = settings.DebugMode, // If debug mode is enabled, write the manifest with indentation.
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // Use camelCase for property names.
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull // Don't write null values.
// Print a status message.
ctx.Spinner(Spinner.Known.Dots);
ctx.SpinnerStyle(Style.Parse("purple bold"));
vault.VaultUpdate += async (_, args) =>
{
try
{
if (args.Entity.Path is RemoteVaultManifest.ManifestFileName)
{
AnsiConsole.MarkupLine(/*lang=md*/"[grey]Manifest update detected. Ignoring...[/]");
return;
}
// Print a status message.
AnsiConsole.MarkupLine(/*lang=md*/$"[grey]Vault update detected (Entity name: [/]{args.Entity.Path}[grey], Change type: [/]{args.Type})");
AnsiConsole.MarkupLine(/*lang=md*/"[blue]Regenerating manifest...[/]");
// Regenerate the manifest.
await GenerateManifestAsync(vault, settings.VaultPath, settings.OutputPath, settings.DebugMode, _ => true);
}
catch (Exception e)
{
// Print an error message.
AnsiConsole.MarkupLine(/*lang=md*/"[red]An error occurred while regenerating the manifest:[/]");
AnsiConsole.WriteException(e, ExceptionFormats.ShortenEverything);
}
};
// Watch the vault for changes.
await Task.Delay(-1);
});
});
}

AnsiConsole.MarkupLine(/*lang=md*/$"Wrote manifest to [green link]{manifestFile.FullName}[/].");
return 0;
}
}

internal static async Task<RemoteVaultManifest> GenerateManifestAsync(
IVault vault,
DirectoryInfo vaultPath,
DirectoryInfo? outputPath,
bool debugMode,
Func<FileInfo, bool> promptOverwrite
) {
// Assert the vault as FileSystem-Based
if (vault is not FileSystemVault)
{
throw new InvalidOperationException("The vault must be a FileSystemVault to generate a manifest.");
}

// Next, generate the manifest.
AnsiConsole.Console.MarkupLine( /*lang=md*/"Generating manifest...");
RemoteVaultManifest manifest = await VaultManifestGenerator.GenerateManifestAsync(vault);

AnsiConsole.MarkupLine(/*lang=md*/$"Generated manifest with [green]{manifest.Files.Count}[/] files.");

// Write the manifest to disk, at the specified location (or the vault root if not specified).
FileInfo manifestFile = new(Path.Combine((outputPath ?? vaultPath).FullName, RemoteVaultManifest.ManifestFileName));

if (manifestFile.Exists && !promptOverwrite(manifestFile))
{
return manifest;
}

// Write the manifest to disk.
await WriteManifestFileAsync(manifestFile, manifest, debugMode);
return manifest;
}

private static async Task WriteManifestFileAsync(FileInfo file, RemoteVaultManifest manifest, bool debugMode)
{
AnsiConsole.MarkupLine(/*lang=md*/$"Writing manifest...");

// Write the new manifest to disk.
await using FileStream fs = file.Open(FileMode.OpenOrCreate, FileAccess.Write);
fs.SetLength(0);

#pragma warning disable CA1869
await JsonSerializer.SerializeAsync(fs, manifest, new JsonSerializerOptions
{
WriteIndented = debugMode, // If debug mode is enabled, write the manifest with indentation.
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // Use camelCase for property names.
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull // Don't write null values.
});

AnsiConsole.MarkupLine(/*lang=md*/$"Wrote manifest to [green link]{file.FullName}[/].");
}
}
Loading

0 comments on commit 431341f

Please sign in to comment.