diff options
Diffstat (limited to 'commands/execenv')
-rw-r--r-- | commands/execenv/env.go | 191 | ||||
-rw-r--r-- | commands/execenv/env_testing.go | 48 |
2 files changed, 239 insertions, 0 deletions
diff --git a/commands/execenv/env.go b/commands/execenv/env.go new file mode 100644 index 00000000..a63f835a --- /dev/null +++ b/commands/execenv/env.go @@ -0,0 +1,191 @@ +package execenv + +import ( + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + + "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/entities/bug" + "github.com/MichaelMure/git-bug/entities/identity" + "github.com/MichaelMure/git-bug/repository" + "github.com/MichaelMure/git-bug/util/interrupt" +) + +const RootCommandName = "git-bug" + +const gitBugNamespace = "git-bug" + +// Env is the environment of a command +type Env struct { + Repo repository.ClockedRepo + Backend *cache.RepoCache + Out Out + Err Out +} + +func NewEnv() *Env { + return &Env{ + Repo: nil, + Out: out{Writer: os.Stdout}, + Err: out{Writer: os.Stderr}, + } +} + +type Out interface { + io.Writer + Printf(format string, a ...interface{}) + Print(a ...interface{}) + Println(a ...interface{}) + + // String returns what have been written in the output before, as a string. + // This only works in test scenario. + String() string + // Bytes returns what have been written in the output before, as []byte. + // This only works in test scenario. + Bytes() []byte + // Reset clear what has been recorded as written in the output before. + // This only works in test scenario. + Reset() +} + +type out struct { + io.Writer +} + +func (o out) Printf(format string, a ...interface{}) { + _, _ = fmt.Fprintf(o, format, a...) +} + +func (o out) Print(a ...interface{}) { + _, _ = fmt.Fprint(o, a...) +} + +func (o out) Println(a ...interface{}) { + _, _ = fmt.Fprintln(o, a...) +} + +func (o out) String() string { + panic("only work with a test env") +} + +func (o out) Bytes() []byte { + panic("only work with a test env") +} + +func (o out) Reset() { + panic("only work with a test env") +} + +// LoadRepo is a pre-run function that load the repository for use in a command +func LoadRepo(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to get the current working directory: %q", err) + } + + env.Repo, err = repository.OpenGoGitRepo(cwd, gitBugNamespace, []repository.ClockLoader{bug.ClockLoader}) + if err == repository.ErrNotARepo { + return fmt.Errorf("%s must be run from within a git Repo", RootCommandName) + } + + if err != nil { + return err + } + + return nil + } +} + +// LoadRepoEnsureUser is the same as LoadRepo, but also ensure that the user has configured +// an identity. Use this pre-run function when an error after using the configured user won't +// do. +func LoadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + err := LoadRepo(env)(cmd, args) + if err != nil { + return err + } + + _, err = identity.GetUserIdentity(env.Repo) + if err != nil { + return err + } + + return nil + } +} + +// LoadBackend is a pre-run function that load the repository and the Backend for use in a command +// When using this function you also need to use CloseBackend as a post-run +func LoadBackend(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + err := LoadRepo(env)(cmd, args) + if err != nil { + return err + } + + env.Backend, err = cache.NewRepoCache(env.Repo) + if err != nil { + return err + } + + cleaner := func(env *Env) interrupt.CleanerFunc { + return func() error { + if env.Backend != nil { + err := env.Backend.Close() + env.Backend = nil + return err + } + return nil + } + } + + // Cleanup properly on interrupt + interrupt.RegisterCleaner(cleaner(env)) + return nil + } +} + +// LoadBackendEnsureUser is the same as LoadBackend, but also ensure that the user has configured +// an identity. Use this pre-run function when an error after using the configured user won't +// do. +func LoadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + err := LoadBackend(env)(cmd, args) + if err != nil { + return err + } + + _, err = identity.GetUserIdentity(env.Repo) + if err != nil { + return err + } + + return nil + } +} + +// CloseBackend is a wrapper for a RunE function that will close the Backend properly +// if it has been opened. +// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error. +func CloseBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + errRun := runE(cmd, args) + + if env.Backend == nil { + return nil + } + err := env.Backend.Close() + env.Backend = nil + + // prioritize the RunE error + if errRun != nil { + return errRun + } + return err + } +} diff --git a/commands/execenv/env_testing.go b/commands/execenv/env_testing.go new file mode 100644 index 00000000..7d9fbd60 --- /dev/null +++ b/commands/execenv/env_testing.go @@ -0,0 +1,48 @@ +package execenv + +import ( + "bytes" + "fmt" + "testing" + + "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/repository" + "github.com/stretchr/testify/require" +) + +type TestOut struct { + *bytes.Buffer +} + +func (te *TestOut) Printf(format string, a ...interface{}) { + _, _ = fmt.Fprintf(te.Buffer, format, a...) +} + +func (te *TestOut) Print(a ...interface{}) { + _, _ = fmt.Fprint(te.Buffer, a...) +} + +func (te *TestOut) Println(a ...interface{}) { + _, _ = fmt.Fprintln(te.Buffer, a...) +} + +func NewTestEnv(t *testing.T) *Env { + t.Helper() + + repo := repository.CreateGoGitTestRepo(t, false) + + buf := new(bytes.Buffer) + + backend, err := cache.NewRepoCache(repo) + require.NoError(t, err) + t.Cleanup(func() { + backend.Close() + }) + + return &Env{ + Repo: repo, + Backend: backend, + Out: &TestOut{buf}, + Err: &TestOut{buf}, + } +} |