diff --git a/examples/run-example/run-example.go b/examples/run-example/run-example.go new file mode 100644 index 0000000..8a0d10a --- /dev/null +++ b/examples/run-example/run-example.go @@ -0,0 +1,50 @@ +package main + +import ( + "os" + + "github.com/roemer/gotaskr" + "github.com/roemer/gotaskr/goext" + "github.com/roemer/gotaskr/log" +) + +func init() { + gotaskr.Task("Run-In-Directory", func() error { + pwd, _ := os.Getwd() + log.Informationf("Path before: %s", pwd) + + err := goext.RunInDirectory("subdir", func() error { + pwd, _ = os.Getwd() + log.Informationf("Path inside: %s", pwd) + return nil + }) + + pwd, _ = os.Getwd() + log.Informationf("Path after : %s", pwd) + + return err + }) + + gotaskr.Task("Run-With-Variables", func() error { + log.Informationf("Variable before: %s", os.Getenv("TEST")) + + err := goext.RunWithEnvs(map[string]string{"TEST": "foo"}, func() error { + log.Informationf("Variable inside: %s", os.Getenv("TEST")) + return nil + }) + + log.Informationf("Variable after : %s", os.Getenv("TEST")) + + return err + }) + + gotaskr.Task("Run-With-Multiple-Options", func() error { + return goext.RunWithOptions(func() error { + return nil + }, goext.RunOptionInDirectory("subdir"), goext.RunOptionWithEnvs(map[string]string{"TEST": "foo"})) + }) +} + +func main() { + os.Exit(gotaskr.Execute()) +} diff --git a/examples/run-example/subdir/.gitkeep b/examples/run-example/subdir/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/goext/goext.go b/goext/goext.go index 47cccd9..ea8914e 100644 --- a/goext/goext.go +++ b/goext/goext.go @@ -3,8 +3,6 @@ package goext import ( "fmt" - "os" - "strconv" ) // Ternary adds the missing ternary operator. @@ -21,130 +19,6 @@ func Printfln(format string, a ...any) (n int, err error) { return fmt.Println(text) } -// RunWithEnv runs a given function with the given environment variables -// and resets them to the previous state afterwards. -func RunWithEnv(envVariables map[string]string, f func() error) error { - origEnvVariables := map[string]string{} - // Read and store original values - for name := range envVariables { - if originalValue, ok := os.LookupEnv(name); ok { - origEnvVariables[name] = originalValue - } - } - // Make sure to reset to the previous values - defer func() { - for name := range envVariables { - origValue, ok := origEnvVariables[name] - if ok { - _ = os.Setenv(name, origValue) - } else { - _ = os.Unsetenv(name) - } - } - }() - - // Change the values - for name, value := range envVariables { - // Set the new value - _ = os.Setenv(name, value) - } - - // Execute the function - err := f() - if err != nil { - return fmt.Errorf("inner method failed: %v", err) - } - return nil -} - -func RunWithEnv1P[P1 any](envVariables map[string]string, f func() (P1, error)) (P1, error) { - var p1 P1 - return p1, RunWithEnv(envVariables, func() error { - var err error - p1, err = f() - return err - }) -} - -func RunWithEnv2P[P1 any, P2 any](envVariables map[string]string, f func() (P1, P2, error)) (P1, P2, error) { - var p1 P1 - var p2 P2 - return p1, p2, RunWithEnv(envVariables, func() error { - var err error - p1, p2, err = f() - return err - }) -} - -func RunWithEnv3P[P1 any, P2 any, P3 any](envVariables map[string]string, f func() (P1, P2, P3, error)) (P1, P2, P3, error) { - var p1 P1 - var p2 P2 - var p3 P3 - return p1, p2, p3, RunWithEnv(envVariables, func() error { - var err error - p1, p2, p3, err = f() - return err - }) -} - -// RunInDirectory runs a given function inside the passed directory as working directory. -// It resets to the previous directory when finished (or an error occured). -func RunInDirectory(path string, f func() error) (err error) { - // Get the current directory - pwd, err := os.Getwd() - if err != nil { - return fmt.Errorf("cannot get current directory: %v", err) - } - // Make sure to reset to the previous folder - defer func() { - // Set the error only if the main error is not yet set - if tempErr := os.Chdir(pwd); tempErr != nil && err == nil { - err = fmt.Errorf("cannot change back to directory %s: %v", strconv.Quote(pwd), tempErr) - } - }() - // Change the path - err = os.Chdir(path) - if err != nil { - return fmt.Errorf("cannot change to directory %s: %v", strconv.Quote(path), err) - } - // Execute the function - err = f() - if err != nil { - return fmt.Errorf("inner method failed: %v", err) - } - return -} - -func RunInDirectory1P[P1 any](path string, f func() (P1, error)) (P1, error) { - var p1 P1 - return p1, RunInDirectory(path, func() error { - var err error - p1, err = f() - return err - }) -} - -func RunInDirectory2P[P1 any, P2 any](path string, f func() (P1, P2, error)) (P1, P2, error) { - var p1 P1 - var p2 P2 - return p1, p2, RunInDirectory(path, func() error { - var err error - p1, p2, err = f() - return err - }) -} - -func RunInDirectory3P[P1 any, P2 any, P3 any](path string, f func() (P1, P2, P3, error)) (P1, P2, P3, error) { - var p1 P1 - var p2 P2 - var p3 P3 - return p1, p2, p3, RunInDirectory(path, func() error { - var err error - p1, p2, p3, err = f() - return err - }) -} - // PanicOnError panics if the given error is set. func PanicOnError(err error) { if err != nil { diff --git a/goext/run.go b/goext/run.go new file mode 100644 index 0000000..b68848d --- /dev/null +++ b/goext/run.go @@ -0,0 +1,68 @@ +package goext + +import ( + "errors" + "fmt" +) + +// An option that can be used to run which is applied before the run and reverted after. +type RunOption interface { + // The method that is applied before the run. + apply() error + // The method that is applied after the run. + revert() error +} + +// Runs a given method with addiitional options. +func RunWithOptions(f func() error, options ...RunOption) (err error) { + // Apply the options + for _, option := range options { + err = errors.Join(err, option.apply()) + } + // Make sure to revert all options, in reverse order + defer func() { + for index := len(options) - 1; index >= 0; index-- { + option := options[index] + err = errors.Join(err, option.revert()) + } + }() + // Execute the function + methodErr := f() + if methodErr != nil { + err = errors.Join(err, fmt.Errorf("inner method failed: %v", methodErr)) + } + return +} + +// Runs a given method with returns one parameter with addiitional options. +func RunWithOptions1P[P1 any](f func() (P1, error), options ...RunOption) (P1, error) { + var p1 P1 + return p1, RunWithOptions(func() error { + var err error + p1, err = f() + return err + }, options...) +} + +// Runs a given method with returns two parameters with addiitional options. +func RunWithOptions2P[P1 any, P2 any](f func() (P1, P2, error), options ...RunOption) (P1, P2, error) { + var p1 P1 + var p2 P2 + return p1, p2, RunWithOptions(func() error { + var err error + p1, p2, err = f() + return err + }, options...) +} + +// Runs a given method with returns three parameters with addiitional options. +func RunWithOptions3P[P1 any, P2 any, P3 any](f func() (P1, P2, P3, error), options ...RunOption) (P1, P2, P3, error) { + var p1 P1 + var p2 P2 + var p3 P3 + return p1, p2, p3, RunWithOptions(func() error { + var err error + p1, p2, p3, err = f() + return err + }, options...) +} diff --git a/goext/run_options.go b/goext/run_options.go new file mode 100644 index 0000000..a8d6a98 --- /dev/null +++ b/goext/run_options.go @@ -0,0 +1,117 @@ +package goext + +import ( + "errors" + "fmt" + "os" + "strconv" +) + +////////////////////////////// +// Public Interface +////////////////////////////// + +// Option that allows changing the working directory during a run. +func RunOptionInDirectory(path string) RunOption { + return &runInDirectoryOption{path: path} +} + +// Option that allows setting/overriding environment variables during a run. +func RunOptionWithEnvs(envVariables map[string]string) RunOption { + return &runWithEnvsOption{envs: envVariables} +} + +func RunInDirectory(path string, f func() error) (err error) { + return RunWithOptions(f, RunOptionInDirectory(path)) +} +func RunInDirectory1P[P1 any](path string, f func() (P1, error)) (P1, error) { + return RunWithOptions1P(f, RunOptionInDirectory(path)) +} +func RunInDirectory2P[P1 any, P2 any](path string, f func() (P1, P2, error)) (P1, P2, error) { + return RunWithOptions2P(f, RunOptionInDirectory(path)) +} +func RunInDirectory3P[P1 any, P2 any, P3 any](path string, f func() (P1, P2, P3, error)) (P1, P2, P3, error) { + return RunWithOptions3P(f, RunOptionInDirectory(path)) +} + +func RunWithEnvs(envVariables map[string]string, f func() error) error { + return RunWithOptions(f, RunOptionWithEnvs(envVariables)) +} +func RunWithEnvs1P[P1 any](envVariables map[string]string, f func() (P1, error)) (P1, error) { + return RunWithOptions1P(f, RunOptionWithEnvs(envVariables)) +} +func RunWithEnvs2P[P1 any, P2 any](envVariables map[string]string, f func() (P1, P2, error)) (P1, P2, error) { + return RunWithOptions2P(f, RunOptionWithEnvs(envVariables)) +} +func RunWithEnvs3P[P1 any, P2 any, P3 any](envVariables map[string]string, f func() (P1, P2, P3, error)) (P1, P2, P3, error) { + return RunWithOptions3P(f, RunOptionWithEnvs(envVariables)) +} + +////////////////////////////// +// Run In Directory +////////////////////////////// + +// Option that allows changing the working directory during a run. +type runInDirectoryOption struct { + path string + origPath string +} + +func (r *runInDirectoryOption) apply() error { + // Get the current directory + pwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("cannot get current directory: %v", err) + } + r.origPath = pwd + // Change the path + err = os.Chdir(r.path) + if err != nil { + return fmt.Errorf("cannot change to directory %s: %v", strconv.Quote(r.path), err) + } + return nil +} + +func (r *runInDirectoryOption) revert() error { + // Reset to the previous folder + err := os.Chdir(r.origPath) + if err != nil { + return fmt.Errorf("cannot change back to directory %s: %v", strconv.Quote(r.origPath), err) + } + return nil +} + +////////////////////////////// +// Run With Envs +////////////////////////////// + +// Option that allows setting/overriding environment variables during a run. +type runWithEnvsOption struct { + envs map[string]string + origEnvs map[string]string +} + +func (r *runWithEnvsOption) apply() (err error) { + r.origEnvs = map[string]string{} + for name, value := range r.envs { + // Read and store original value + if originalValue, ok := os.LookupEnv(name); ok { + r.origEnvs[name] = originalValue + } + // Set the new value + err = errors.Join(err, os.Setenv(name, value)) + } + return +} + +func (r *runWithEnvsOption) revert() (err error) { + for name := range r.envs { + origValue, ok := r.origEnvs[name] + if ok { + err = errors.Join(err, os.Setenv(name, origValue)) + } else { + err = errors.Join(err, os.Unsetenv(name)) + } + } + return +} diff --git a/goext/goext_test.go b/goext/run_test.go similarity index 92% rename from goext/goext_test.go rename to goext/run_test.go index 85511a1..59b1122 100644 --- a/goext/goext_test.go +++ b/goext/run_test.go @@ -17,17 +17,17 @@ func TestRunWithEnv(t *testing.T) { assertEnvIsUnset(assert, "VAR_1") assertEnvIsUnset(assert, "VAR_2") - err := RunWithEnv(map[string]string{ - "VAR_1": "foo", - "VAR_2": "bar", - "PRE_VAR_2": "world", - }, func() error { + err := RunWithOptions(func() error { assertEnvEquals(assert, "PRE_VAR_1", "baz") assertEnvEquals(assert, "PRE_VAR_2", "world") assertEnvEquals(assert, "VAR_1", "foo") assertEnvEquals(assert, "VAR_2", "bar") return nil - }) + }, RunOptionWithEnvs(map[string]string{ + "VAR_1": "foo", + "VAR_2": "bar", + "PRE_VAR_2": "world", + })) assert.NoError(err) assertEnvEquals(assert, "PRE_VAR_1", "baz")