Skip to content

Commit

Permalink
Refactored run methods to support multiple options
Browse files Browse the repository at this point in the history
  • Loading branch information
Roemer committed Feb 15, 2024
1 parent a3ef674 commit a2b46e4
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 132 deletions.
50 changes: 50 additions & 0 deletions examples/run-example/run-example.go
Original file line number Diff line number Diff line change
@@ -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())
}
Empty file.
126 changes: 0 additions & 126 deletions goext/goext.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package goext

import (
"fmt"
"os"
"strconv"
)

// Ternary adds the missing ternary operator.
Expand All @@ -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 {
Expand Down
68 changes: 68 additions & 0 deletions goext/run.go
Original file line number Diff line number Diff line change
@@ -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...)
}
117 changes: 117 additions & 0 deletions goext/run_options.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit a2b46e4

Please sign in to comment.