From e7e8bb0c084296cc3e29eacb59d92f27c1dfa44d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Strie=C3=9Fnig?= Date: Fri, 23 May 2025 10:42:32 +0200 Subject: [PATCH 1/2] feat: Add Finalize function to commands This adds a "Finalize" function to the command, which is executed at the end of every run, even if the run panics --- command.go | 7 +++++++ command_test.go | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/command.go b/command.go index 4794e5ebe..191ec9300 100644 --- a/command.go +++ b/command.go @@ -138,6 +138,8 @@ type Command struct { RunE func(cmd *Command, args []string) error // PostRun: run after the Run command. PostRun func(cmd *Command, args []string) + // Finalize: run at the end of the Run command, even if it panics. + Finalize func(cmd *Command, args []string) // PostRunE: PostRun but returns an error. PostRunE func(cmd *Command, args []string) error // PersistentPostRun: children of this command will inherit and execute after PostRun. @@ -961,6 +963,11 @@ func (c *Command) execute(a []string) (err error) { defer c.postRun() argWoFlags := c.Flags().Args() + + if c.Finalize != nil { + defer c.Finalize(c, argWoFlags) + } + if c.DisableFlagParsing { argWoFlags = a } diff --git a/command_test.go b/command_test.go index 156df9eb6..cca04dea5 100644 --- a/command_test.go +++ b/command_test.go @@ -2952,3 +2952,25 @@ func TestHelpFuncExecuted(t *testing.T) { checkStringContains(t, output, helpText) } + +func TestFinalizeCalledOnPanic(t *testing.T) { + finalizeCalls := 0 + defer func() { + if recover() == nil { + t.Error("The code should have panicked due to panicking run") + } + if finalizeCalls != 1 { + t.Errorf("finalize() called %d times, want 1", finalizeCalls) + } + }() + rootCmd := &Command{ + Use: "root", + Run: func(cmd *Command, args []string) { + panic("should panic") + }, + Finalize: func(cmd *Command, args []string) { + finalizeCalls++ + }, + } + executeCommand(rootCmd) +} From e6c2f2e479fc2d63a5385b841ed69358c9d89397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Strie=C3=9Fnig?= Date: Fri, 23 May 2025 11:51:24 +0200 Subject: [PATCH 2/2] fix: Call finalize of parent and child commands --- command.go | 11 +++++++---- command_test.go | 24 ++++++++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/command.go b/command.go index 191ec9300..a08cf342e 100644 --- a/command.go +++ b/command.go @@ -964,10 +964,6 @@ func (c *Command) execute(a []string) (err error) { argWoFlags := c.Flags().Args() - if c.Finalize != nil { - defer c.Finalize(c, argWoFlags) - } - if c.DisableFlagParsing { argWoFlags = a } @@ -988,6 +984,13 @@ func (c *Command) execute(a []string) (err error) { parents = append(parents, p) } } + defer func() { + for _, p := range parents { + if p.Finalize != nil { + p.Finalize(c, argWoFlags) + } + } + }() for _, p := range parents { if p.PersistentPreRunE != nil { if err := p.PersistentPreRunE(c, argWoFlags); err != nil { diff --git a/command_test.go b/command_test.go index cca04dea5..082cac949 100644 --- a/command_test.go +++ b/command_test.go @@ -2953,24 +2953,36 @@ func TestHelpFuncExecuted(t *testing.T) { checkStringContains(t, output, helpText) } -func TestFinalizeCalledOnPanic(t *testing.T) { - finalizeCalls := 0 +func TestFinalizeOfChildAndParentCalledOnPanic(t *testing.T) { + parentFinalizeCalls := 0 + childFinalizeCalls := 0 defer func() { if recover() == nil { t.Error("The code should have panicked due to panicking run") } - if finalizeCalls != 1 { - t.Errorf("finalize() called %d times, want 1", finalizeCalls) + if parentFinalizeCalls != 1 { + t.Errorf("finalize() of parent command called %d times, want 1", parentFinalizeCalls) + } + if childFinalizeCalls != 1 { + t.Errorf("finalize() of child command called %d times, want 1", childFinalizeCalls) } }() rootCmd := &Command{ Use: "root", + Run: emptyRun, + Finalize: func(cmd *Command, args []string) { + parentFinalizeCalls++ + }, + } + subCmd := &Command{ + Use: "sub", Run: func(cmd *Command, args []string) { panic("should panic") }, Finalize: func(cmd *Command, args []string) { - finalizeCalls++ + childFinalizeCalls++ }, } - executeCommand(rootCmd) + rootCmd.AddCommand(subCmd) + executeCommand(rootCmd, "sub") }