1package commands
  2
  3import (
  4	"fmt"
  5	"io"
  6	"os"
  7
  8	"github.com/spf13/cobra"
  9
 10	"github.com/MichaelMure/git-bug/cache"
 11	"github.com/MichaelMure/git-bug/entities/bug"
 12	"github.com/MichaelMure/git-bug/entities/identity"
 13	"github.com/MichaelMure/git-bug/repository"
 14	"github.com/MichaelMure/git-bug/util/interrupt"
 15)
 16
 17const gitBugNamespace = "git-bug"
 18
 19// Env is the environment of a command
 20type Env struct {
 21	repo    repository.ClockedRepo
 22	backend *cache.RepoCache
 23	out     out
 24	err     out
 25}
 26
 27func newEnv() *Env {
 28	return &Env{
 29		repo: nil,
 30		out:  out{Writer: os.Stdout},
 31		err:  out{Writer: os.Stderr},
 32	}
 33}
 34
 35type out struct {
 36	io.Writer
 37}
 38
 39func (o out) Printf(format string, a ...interface{}) {
 40	_, _ = fmt.Fprintf(o, format, a...)
 41}
 42
 43func (o out) Print(a ...interface{}) {
 44	_, _ = fmt.Fprint(o, a...)
 45}
 46
 47func (o out) Println(a ...interface{}) {
 48	_, _ = fmt.Fprintln(o, a...)
 49}
 50
 51// loadRepo is a pre-run function that load the repository for use in a command
 52func loadRepo(env *Env) func(*cobra.Command, []string) error {
 53	return func(cmd *cobra.Command, args []string) error {
 54		cwd, err := os.Getwd()
 55		if err != nil {
 56			return fmt.Errorf("unable to get the current working directory: %q", err)
 57		}
 58
 59		env.repo, err = repository.OpenGoGitRepo(cwd, gitBugNamespace, []repository.ClockLoader{bug.ClockLoader})
 60		if err == repository.ErrNotARepo {
 61			return fmt.Errorf("%s must be run from within a git repo", rootCommandName)
 62		}
 63
 64		if err != nil {
 65			return err
 66		}
 67
 68		return nil
 69	}
 70}
 71
 72// loadRepoEnsureUser is the same as loadRepo, but also ensure that the user has configured
 73// an identity. Use this pre-run function when an error after using the configured user won't
 74// do.
 75func loadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error {
 76	return func(cmd *cobra.Command, args []string) error {
 77		err := loadRepo(env)(cmd, args)
 78		if err != nil {
 79			return err
 80		}
 81
 82		_, err = identity.GetUserIdentity(env.repo)
 83		if err != nil {
 84			return err
 85		}
 86
 87		return nil
 88	}
 89}
 90
 91// loadBackend is a pre-run function that load the repository and the backend for use in a command
 92// When using this function you also need to use closeBackend as a post-run
 93func loadBackend(env *Env) func(*cobra.Command, []string) error {
 94	return func(cmd *cobra.Command, args []string) error {
 95		err := loadRepo(env)(cmd, args)
 96		if err != nil {
 97			return err
 98		}
 99
100		env.backend, err = cache.NewRepoCache(env.repo)
101		if err != nil {
102			return err
103		}
104
105		cleaner := func(env *Env) interrupt.CleanerFunc {
106			return func() error {
107				if env.backend != nil {
108					err := env.backend.Close()
109					env.backend = nil
110					return err
111				}
112				return nil
113			}
114		}
115
116		// Cleanup properly on interrupt
117		interrupt.RegisterCleaner(cleaner(env))
118		return nil
119	}
120}
121
122// loadBackendEnsureUser is the same as loadBackend, but also ensure that the user has configured
123// an identity. Use this pre-run function when an error after using the configured user won't
124// do.
125func loadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error {
126	return func(cmd *cobra.Command, args []string) error {
127		err := loadBackend(env)(cmd, args)
128		if err != nil {
129			return err
130		}
131
132		_, err = identity.GetUserIdentity(env.repo)
133		if err != nil {
134			return err
135		}
136
137		return nil
138	}
139}
140
141// closeBackend is a wrapper for a RunE function that will close the backend properly
142// if it has been opened.
143// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
144func closeBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
145	return func(cmd *cobra.Command, args []string) error {
146		errRun := runE(cmd, args)
147
148		if env.backend == nil {
149			return nil
150		}
151		err := env.backend.Close()
152		env.backend = nil
153
154		// prioritize the RunE error
155		if errRun != nil {
156			return errRun
157		}
158		return err
159	}
160}