From f4ab8aee22ee4a58864196b3543601f5778696e4 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Wed, 5 Mar 2025 08:11:08 +1100 Subject: [PATCH 1/2] Add json output --- src/Stack/Commands/Stack/ListStacksCommand.cs | 4 +- .../Commands/Stack/StackStatusCommand.cs | 86 ++++++++++++++++++- .../Commands/CommandSettingsBase.cs | 18 +++- .../Commands/CommandWithOutput.cs | 28 +++++- 4 files changed, 128 insertions(+), 8 deletions(-) diff --git a/src/Stack/Commands/Stack/ListStacksCommand.cs b/src/Stack/Commands/Stack/ListStacksCommand.cs index 41fc6456..a838d146 100644 --- a/src/Stack/Commands/Stack/ListStacksCommand.cs +++ b/src/Stack/Commands/Stack/ListStacksCommand.cs @@ -7,7 +7,7 @@ namespace Stack.Commands; -public class ListStacksCommandSettings : CommandSettingsBase; +public class ListStacksCommandSettings : CommandWithOutputSettingsBase; public class ListStacksCommand : CommandWithOutput { @@ -20,7 +20,7 @@ protected override async Task Execute(ListStacksComma return await handler.Handle(new ListStacksCommandInputs()); } - protected override void WriteOutput(ListStacksCommandSettings settings, ListStacksCommandResponse response) + protected override void WriteDefaultOutput(ListStacksCommandResponse response) { if (response.Stacks.Count == 0) { diff --git a/src/Stack/Commands/Stack/StackStatusCommand.cs b/src/Stack/Commands/Stack/StackStatusCommand.cs index e0f5959a..4d81cbb4 100644 --- a/src/Stack/Commands/Stack/StackStatusCommand.cs +++ b/src/Stack/Commands/Stack/StackStatusCommand.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Text.Json; using Spectre.Console; using Spectre.Console.Cli; using Stack.Commands; @@ -9,7 +10,7 @@ namespace Stack.Commands; -public class StackStatusCommandSettings : CommandSettingsBase +public class StackStatusCommandSettings : CommandWithOutputSettingsBase { [Description("The name of the stack to show the status of.")] [CommandOption("-s|--stack")] @@ -38,7 +39,7 @@ protected override async Task Execute(StackStatusCom return await handler.Handle(new StackStatusCommandInputs(settings.Stack, settings.All, settings.Full)); } - protected override void WriteOutput(StackStatusCommandSettings settings, StackStatusCommandResponse response) + protected override void WriteDefaultOutput(StackStatusCommandResponse response) { StackHelpers.OutputStackStatus(response.Statuses, StdOutLogger); @@ -48,8 +49,89 @@ protected override void WriteOutput(StackStatusCommandSettings settings, StackSt StackHelpers.OutputBranchAndStackActions(stack, status, StdOutLogger); } } + + protected override void WriteJsonOutput(StackStatusCommandResponse response, JsonSerializerOptions options) + { + var stackDetails = new List(); + + foreach (var (stack, status) in response.Statuses) + { + status.Branches.TryGetValue(stack.SourceBranch, out var sourceBranchStatus); + + if (sourceBranchStatus is not null) + { + var sourceBranch = new Branch( + stack.SourceBranch, + sourceBranchStatus.Status.ExistsLocally, + sourceBranchStatus.Status.Tip, + sourceBranchStatus.Status.HasRemoteTrackingBranch ? + new RemoteTrackingBranchStatus( + $"origin/{stack.SourceBranch}", + sourceBranchStatus.Status.ExistsInRemote, + sourceBranchStatus.Status.AheadOfRemote, + sourceBranchStatus.Status.BehindRemote) : null); + + var branches = new List(); + var parentBranch = sourceBranch; + + foreach (var branch in stack.Branches) + { + status.Branches.TryGetValue(branch, out var branchStatus); + + if (branchStatus is not null) + { + var pullRequest = branchStatus.PullRequest; + var remoteTrackingBranch = branchStatus.Status.HasRemoteTrackingBranch ? + new RemoteTrackingBranchStatus( + $"origin/{branch}", + branchStatus.Status.ExistsInRemote, + branchStatus.Status.AheadOfRemote, + branchStatus.Status.BehindRemote) : null; + + var parentBranchStatus = new ParentBranchStatus(parentBranch, branchStatus.Status.AheadOfParent, branchStatus.Status.BehindParent); + + var branchDetail = new BranchDetail( + branch, + branchStatus.Status.ExistsLocally, + branchStatus.Status.Tip, + remoteTrackingBranch, + pullRequest, + parentBranchStatus); + + branches.Add(branchDetail); + + if (branchStatus.IsActive) + { + parentBranch = branchDetail; + } + } + } + + stackDetails.Add(new StackDetail(stack.Name, sourceBranch, [.. branches])); + } + } + + var json = JsonSerializer.Serialize(stackDetails, options); + StdOut.WriteLine(json); + } } +record StackDetail(string Name, Branch SourceBranch, BranchDetail[] Branches); + +record RemoteTrackingBranchStatus(string Name, bool Exists, int Ahead, int Behind); + +record Branch(string Name, bool Exists, Commit? Tip, RemoteTrackingBranchStatus? RemoteTrackingBranch); + +record BranchDetail( + string Name, + bool Exists, + Commit? Tip, + RemoteTrackingBranchStatus? RemoteTrackingBranch, + GitHubPullRequest? PullRequest, + ParentBranchStatus? Parent) : Branch(Name, Exists, Tip, RemoteTrackingBranch); + +record ParentBranchStatus(Branch Branch, int Ahead, int Behind); + public record StackStatusCommandInputs(string? Stack, bool All, bool Full); public record StackStatusCommandResponse(Dictionary Statuses); diff --git a/src/Stack/Infrastructure/Commands/CommandSettingsBase.cs b/src/Stack/Infrastructure/Commands/CommandSettingsBase.cs index c2da8d3e..763eda87 100644 --- a/src/Stack/Infrastructure/Commands/CommandSettingsBase.cs +++ b/src/Stack/Infrastructure/Commands/CommandSettingsBase.cs @@ -14,7 +14,21 @@ public class CommandSettingsBase : CommandSettings [Description("The path to the directory containing the git repository. Defaults to the current directory.")] [CommandOption("--working-dir")] public string? WorkingDirectory { get; init; } +} - public virtual GitClientSettings GetGitClientSettings() => new(Verbose, WorkingDirectory); - public virtual GitHubClientSettings GetGitHubClientSettings() => new(Verbose, WorkingDirectory); +public class CommandWithOutputSettingsBase : CommandSettingsBase +{ + [Description("Output the result as JSON.")] + [CommandOption("--json")] + [DefaultValue(false)] + public bool Json { get; init; } } + +public static class CommandSettingsBaseExtensions +{ + public static GitClientSettings GetGitClientSettings(this CommandSettingsBase settings) => + new(settings.Verbose, settings.WorkingDirectory); + + public static GitHubClientSettings GetGitHubClientSettings(this CommandSettingsBase settings) => + new(settings.Verbose, settings.WorkingDirectory); +} \ No newline at end of file diff --git a/src/Stack/Infrastructure/Commands/CommandWithOutput.cs b/src/Stack/Infrastructure/Commands/CommandWithOutput.cs index eaaf2b25..3ec920e6 100644 --- a/src/Stack/Infrastructure/Commands/CommandWithOutput.cs +++ b/src/Stack/Infrastructure/Commands/CommandWithOutput.cs @@ -1,12 +1,18 @@ +using System.Text.Json; using Spectre.Console; using Spectre.Console.Cli; namespace Stack.Commands; public abstract class CommandWithOutput : Command - where TSettings : CommandSettingsBase + where TSettings : CommandWithOutputSettingsBase where TResponse : notnull { + readonly JsonSerializerOptions jsonSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + public override async Task ExecuteAsync(CommandContext context, TSettings settings) { var response = await Execute(settings); @@ -17,6 +23,24 @@ public override async Task ExecuteAsync(CommandContext context, TSettings s protected override abstract Task Execute(TSettings settings); - protected abstract void WriteOutput(TSettings settings, TResponse response); + protected abstract void WriteDefaultOutput(TResponse response); + + protected virtual void WriteJsonOutput(TResponse response, JsonSerializerOptions options) + { + var json = JsonSerializer.Serialize(response, jsonSerializerOptions); + StdOut.WriteLine(json); + } + + private void WriteOutput(TSettings settings, TResponse response) + { + if (settings.Json) + { + WriteJsonOutput(response, jsonSerializerOptions); + } + else + { + WriteDefaultOutput(response); + } + } } From 10adeca0884785438bb74ef6b4551535c6f7f550 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Mon, 10 Mar 2025 09:03:10 +1100 Subject: [PATCH 2/2] Update readme --- README.md | 2 ++ src/Stack/Infrastructure/Commands/CommandSettingsBase.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d301e71..689b5a3e 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ OPTIONS: -v, --version Prints version information --verbose Show verbose output --working-dir The path to the directory containing the git repository. Defaults to the current directory + --json Output results as JSON ``` #### `stack status` @@ -212,6 +213,7 @@ OPTIONS: -s, --stack The name of the stack to show the status of --all Show status of all stacks --full Show full status including pull requests + --json Output results as JSON ``` #### `stack delete` diff --git a/src/Stack/Infrastructure/Commands/CommandSettingsBase.cs b/src/Stack/Infrastructure/Commands/CommandSettingsBase.cs index 763eda87..01f6f76e 100644 --- a/src/Stack/Infrastructure/Commands/CommandSettingsBase.cs +++ b/src/Stack/Infrastructure/Commands/CommandSettingsBase.cs @@ -18,7 +18,7 @@ public class CommandSettingsBase : CommandSettings public class CommandWithOutputSettingsBase : CommandSettingsBase { - [Description("Output the result as JSON.")] + [Description("Output results as JSON.")] [CommandOption("--json")] [DefaultValue(false)] public bool Json { get; init; }