From fb5059613c3503dc2f7a1f71dd8d18deb7991513 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Wed, 17 Jun 2026 22:09:07 +1200 Subject: [PATCH] Convert the :navigation cluster to command types Lift the five directory-navigation handlers into discrete command types with their names preserved (GetNextDirectory, PopDirectory, PushWithCurrentRootDirectory, PushWithParentRootDirectory, PushWithChosenRootDirectory) so the companion shell functions keep working. Shared per-session state moves into an internal NavigationSession helper; PushWithChosenRootDirectory takes IConsolePrompts via the constructor. Replaces the five DelegateCommand adapters; deletes Program.Navigation.cs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Navigation/GetNextDirectoryCommand.cs | 28 +++++ .../Commands/Navigation/NavigationSession.cs | 49 +++++++++ .../Navigation/PopDirectoryCommand.cs | 26 +++++ .../PushWithChosenRootDirectoryCommand.cs | 34 ++++++ .../PushWithCurrentRootDirectoryCommand.cs | 16 +++ .../PushWithParentRootDirectoryCommand.cs | 19 ++++ src/Fallout.Cli/Program.Navigation.cs | 101 ------------------ src/Fallout.Cli/Program.cs | 13 ++- 8 files changed, 180 insertions(+), 106 deletions(-) create mode 100644 src/Fallout.Cli/Commands/Navigation/GetNextDirectoryCommand.cs create mode 100644 src/Fallout.Cli/Commands/Navigation/NavigationSession.cs create mode 100644 src/Fallout.Cli/Commands/Navigation/PopDirectoryCommand.cs create mode 100644 src/Fallout.Cli/Commands/Navigation/PushWithChosenRootDirectoryCommand.cs create mode 100644 src/Fallout.Cli/Commands/Navigation/PushWithCurrentRootDirectoryCommand.cs create mode 100644 src/Fallout.Cli/Commands/Navigation/PushWithParentRootDirectoryCommand.cs delete mode 100644 src/Fallout.Cli/Program.Navigation.cs diff --git a/src/Fallout.Cli/Commands/Navigation/GetNextDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/GetNextDirectoryCommand.cs new file mode 100644 index 000000000..1ee9ace2e --- /dev/null +++ b/src/Fallout.Cli/Commands/Navigation/GetNextDirectoryCommand.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using Fallout.Common; +using Fallout.Common.IO; + +namespace Fallout.Cli.Commands.Navigation; + +/// fallout :GetNextDirectory: prints (and consumes) the queued next directory. +public sealed class GetNextDirectoryCommand : IFalloutCommand +{ + public string Name => "GetNextDirectory"; + + public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) + { + var content = NavigationSession.SessionFile.Existing()?.ReadAllLines(); + if (content == null || string.IsNullOrWhiteSpace(content[0])) + { + Console.WriteLine(EnvironmentInfo.WorkingDirectory); + return 1; + } + + var nextDirectory = content[0]; + content[0] = string.Empty; + NavigationSession.SessionFile.WriteAllLines(content); + Console.WriteLine(nextDirectory); + return 0; + } +} diff --git a/src/Fallout.Cli/Commands/Navigation/NavigationSession.cs b/src/Fallout.Cli/Commands/Navigation/NavigationSession.cs new file mode 100644 index 000000000..395740663 --- /dev/null +++ b/src/Fallout.Cli/Commands/Navigation/NavigationSession.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Fallout.Common; +using Fallout.Common.IO; +using Fallout.Common.Utilities; +using static Fallout.Common.Constants; + +namespace Fallout.Cli.Commands.Navigation; + +/// +/// Shared per-terminal-session state for the directory-navigation commands. The companion shell +/// functions invoke the commands by name (preserved on conversion): +/// +/// function nuke- { nuke :PopDirectory; cd $(nuke :GetNextDirectory) } +/// function nuke/ { nuke :PushWithChosenRootDirectory; cd $(nuke :GetNextDirectory) } +/// function nuke. { nuke :PushWithCurrentRootDirectory; cd $(nuke :GetNextDirectory) } +/// function nuke.. { nuke :PushWithParentRootDirectory; cd $(nuke :GetNextDirectory) } +/// +/// +internal static class NavigationSession +{ + public static string SessionId + => EnvironmentInfo.Platform switch + { + PlatformFamily.OSX => EnvironmentInfo.GetVariable("TERM_SESSION_ID").NotNull()[7..], + PlatformFamily.Windows => EnvironmentInfo.GetVariable("WT_SESSION").NotNull(), + _ => throw new NotSupportedException($"{EnvironmentInfo.Platform} has no session id selector.") + }; + + public static AbsolutePath SessionFile => GlobalTemporaryDirectory / $"nuke-{SessionId}.dat"; + + public static int PushAndSetNext(Func directoryProvider) + { + try + { + var content = SessionFile.Existing()?.ReadAllLines().ToList() ?? new List { null }; + content[0] = directoryProvider.Invoke(); + content.Insert(index: 1, EnvironmentInfo.WorkingDirectory); + SessionFile.WriteAllLines(content); + return 0; + } + catch (Exception exception) + { + Console.Error.WriteLine(exception.Message); + return 1; + } + } +} diff --git a/src/Fallout.Cli/Commands/Navigation/PopDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/PopDirectoryCommand.cs new file mode 100644 index 000000000..5095cfee8 --- /dev/null +++ b/src/Fallout.Cli/Commands/Navigation/PopDirectoryCommand.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using Fallout.Common.IO; + +namespace Fallout.Cli.Commands.Navigation; + +/// fallout :PopDirectory: pops the previous directory back to the front of the queue. +public sealed class PopDirectoryCommand : IFalloutCommand +{ + public string Name => "PopDirectory"; + + public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) + { + var content = NavigationSession.SessionFile.Existing()?.ReadAllLines().ToList(); + if (content == null || content.Count <= 1) + { + Console.Error.WriteLine("No previous directory"); + return 1; + } + + content[0] = content[1]; + content.RemoveAt(1); + NavigationSession.SessionFile.WriteAllLines(content); + return 0; + } +} diff --git a/src/Fallout.Cli/Commands/Navigation/PushWithChosenRootDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/PushWithChosenRootDirectoryCommand.cs new file mode 100644 index 000000000..94428b646 --- /dev/null +++ b/src/Fallout.Cli/Commands/Navigation/PushWithChosenRootDirectoryCommand.cs @@ -0,0 +1,34 @@ +using System.Linq; +using Fallout.Cli.Prompts; +using Fallout.Common; +using Fallout.Common.IO; +using static Fallout.Common.Constants; + +namespace Fallout.Cli.Commands.Navigation; + +/// +/// fallout :PushWithChosenRootDirectory: prompts for a discovered root directory and queues it. +/// +public sealed class PushWithChosenRootDirectoryCommand : IFalloutCommand +{ + private readonly IConsolePrompts _prompts; + + public PushWithChosenRootDirectoryCommand(IConsolePrompts prompts) => _prompts = prompts; + + public string Name => "PushWithChosenRootDirectory"; + + public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) + { + return NavigationSession.PushAndSetNext(() => + { + var directories = EnvironmentInfo.WorkingDirectory.GlobDirectories($"**/{FalloutDirectoryName}") + .Concat(EnvironmentInfo.WorkingDirectory.GlobFiles($"**/{FalloutDirectoryName}")) + .Where(x => !x.Equals(EnvironmentInfo.WorkingDirectory)) + .Select(x => x.Parent) + .Select(x => (x, EnvironmentInfo.WorkingDirectory.GetRelativePathTo(x).ToString())) + .OrderBy(x => x.Item2).ToArray(); + + return _prompts.PromptForChoice("Where to go next?", directories); + }); + } +} diff --git a/src/Fallout.Cli/Commands/Navigation/PushWithCurrentRootDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/PushWithCurrentRootDirectoryCommand.cs new file mode 100644 index 000000000..4c2557212 --- /dev/null +++ b/src/Fallout.Cli/Commands/Navigation/PushWithCurrentRootDirectoryCommand.cs @@ -0,0 +1,16 @@ +using Fallout.Common; +using Fallout.Common.IO; +using Fallout.Common.Utilities; + +namespace Fallout.Cli.Commands.Navigation; + +/// fallout :PushWithCurrentRootDirectory: queues the current root directory. +public sealed class PushWithCurrentRootDirectoryCommand : IFalloutCommand +{ + public string Name => "PushWithCurrentRootDirectory"; + + public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) + { + return NavigationSession.PushAndSetNext(() => rootDirectory.NotNull("No root directory")); + } +} diff --git a/src/Fallout.Cli/Commands/Navigation/PushWithParentRootDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/PushWithParentRootDirectoryCommand.cs new file mode 100644 index 000000000..ad5165a48 --- /dev/null +++ b/src/Fallout.Cli/Commands/Navigation/PushWithParentRootDirectoryCommand.cs @@ -0,0 +1,19 @@ +using System.IO; +using Fallout.Common; +using Fallout.Common.IO; +using Fallout.Common.Utilities; +using static Fallout.Common.Constants; + +namespace Fallout.Cli.Commands.Navigation; + +/// fallout :PushWithParentRootDirectory: queues the parent repository's root directory. +public sealed class PushWithParentRootDirectoryCommand : IFalloutCommand +{ + public string Name => "PushWithParentRootDirectory"; + + public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) + { + return NavigationSession.PushAndSetNext(() => TryGetRootDirectoryFrom(Path.GetDirectoryName(rootDirectory.NotNull("No root directory"))) + .NotNull("No parent root directory")); + } +} diff --git a/src/Fallout.Cli/Program.Navigation.cs b/src/Fallout.Cli/Program.Navigation.cs deleted file mode 100644 index 71f8e53a5..000000000 --- a/src/Fallout.Cli/Program.Navigation.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Fallout.Common; -using Fallout.Common.IO; -using static Fallout.Common.Constants; - -namespace Fallout.Cli; - -partial class Program -{ - // function nuke- { nuke :PopDirectory; cd $(nuke :GetNextDirectory) } - // function nuke/ { nuke :PushWithChosenRootDirectory; cd $(nuke :GetNextDirectory) } - // function nuke. { nuke :PushWithCurrentRootDirectory; cd $(nuke :GetNextDirectory) } - // function nuke.. { nuke :PushWithParentRootDirectory; cd $(nuke :GetNextDirectory) } - - private static string SessionId - => EnvironmentInfo.Platform switch - { - PlatformFamily.OSX => EnvironmentInfo.GetVariable("TERM_SESSION_ID").NotNull()[7..], - PlatformFamily.Windows => EnvironmentInfo.GetVariable("WT_SESSION").NotNull(), - _ => throw new NotSupportedException($"{EnvironmentInfo.Platform} has no session id selector.") - }; - - private static AbsolutePath SessionFile => GlobalTemporaryDirectory / $"nuke-{SessionId}.dat"; - - private static int GetNextDirectory(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) - { - var content = SessionFile.Existing()?.ReadAllLines(); - if (content == null || string.IsNullOrWhiteSpace(content[0])) - { - Console.WriteLine(EnvironmentInfo.WorkingDirectory); - return 1; - } - - var nextDirectory = content[0]; - content[0] = string.Empty; - SessionFile.WriteAllLines(content); - Console.WriteLine(nextDirectory); - return 0; - } - - private static int PopDirectory(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) - { - var content = SessionFile.Existing()?.ReadAllLines().ToList(); - if (content == null || content.Count <= 1) - { - Console.Error.WriteLine("No previous directory"); - return 1; - } - - content[0] = content[1]; - content.RemoveAt(1); - SessionFile.WriteAllLines(content); - return 0; - } - - private static int PushWithCurrentRootDirectory(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) - { - return PushAndSetNext(() => rootDirectory.NotNull("No root directory")); - } - - private static int PushWithParentRootDirectory(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) - { - return PushAndSetNext(() => TryGetRootDirectoryFrom(Path.GetDirectoryName(rootDirectory.NotNull("No root directory"))) - .NotNull("No parent root directory")); - } - - private static int PushWithChosenRootDirectory(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) - { - return PushAndSetNext(() => - { - var directories = EnvironmentInfo.WorkingDirectory.GlobDirectories($"**/{FalloutDirectoryName}") - .Concat(EnvironmentInfo.WorkingDirectory.GlobFiles($"**/{FalloutDirectoryName}")) - .Where(x => !x.Equals(EnvironmentInfo.WorkingDirectory)) - .Select(x => x.Parent) - .Select(x => (x, EnvironmentInfo.WorkingDirectory.GetRelativePathTo(x).ToString())) - .OrderBy(x => x.Item2).ToArray(); - - return PromptForChoice("Where to go next?", directories); - }); - } - - private static int PushAndSetNext(Func directoryProvider) - { - try - { - var content = SessionFile.Existing()?.ReadAllLines().ToList() ?? new List { null }; - content[0] = directoryProvider.Invoke(); - content.Insert(index: 1, EnvironmentInfo.WorkingDirectory); - SessionFile.WriteAllLines(content); - return 0; - } - catch (Exception exception) - { - Console.Error.WriteLine(exception.Message); - return 1; - } - } -} diff --git a/src/Fallout.Cli/Program.cs b/src/Fallout.Cli/Program.cs index 7548a71d2..b5f165af2 100644 --- a/src/Fallout.Cli/Program.cs +++ b/src/Fallout.Cli/Program.cs @@ -71,11 +71,14 @@ private static void RegisterCommands(IServiceCollection services) // Legacy handlers still living on Program, adapted until they are extracted into command // types. Each conversion deletes one line here plus its Program.X.cs partial. - services.AddSingleton(new DelegateCommand("GetNextDirectory", GetNextDirectory)); - services.AddSingleton(new DelegateCommand("PopDirectory", PopDirectory)); - services.AddSingleton(new DelegateCommand("PushWithCurrentRootDirectory", PushWithCurrentRootDirectory)); - services.AddSingleton(new DelegateCommand("PushWithParentRootDirectory", PushWithParentRootDirectory)); - services.AddSingleton(new DelegateCommand("PushWithChosenRootDirectory", PushWithChosenRootDirectory)); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // Legacy handlers still living on Program, adapted until they are extracted into command + // types. Each conversion deletes one line here plus its Program.X.cs partial. } internal static void PrintInfo()