1package commands
  2
  3import (
  4	"fmt"
  5	"io"
  6	"os"
  7
  8	"github.com/spf13/cobra"
  9
 10	"github.com/MichaelMure/git-bug/bug"
 11	"github.com/MichaelMure/git-bug/cache"
 12	"github.com/MichaelMure/git-bug/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
 51func getCWD(cmd *cobra.Command, args []string) (string, error) {
 52	cwd, ok := cmd.Context().Value("cwd").(string)
 53	if cwd != "" && ok {
 54		return cwd, nil
 55	}
 56
 57	cwd, err := os.Getwd()
 58	if err != nil {
 59		return "", fmt.Errorf("unable to get the current working directory: %q", err)
 60	}
 61
 62	return cwd, nil
 63}
 64
 65// loadRepo is a pre-run function that load the repository for use in a command
 66func loadRepo(env *Env) func(*cobra.Command, []string) error {
 67	return func(cmd *cobra.Command, args []string) error {
 68		cwd, err := getCWD(cmd, args)
 69		if err != nil {
 70			return err
 71		}
 72
 73		env.repo, err = repository.OpenGoGitRepo(cwd, GitBugNamespace, []repository.ClockLoader{bug.ClockLoader})
 74		if err == repository.ErrNotARepo {
 75			return fmt.Errorf("%s must be run from within a git repo", rootCommandName)
 76		}
 77
 78		if err != nil {
 79			return err
 80		}
 81
 82		return nil
 83	}
 84}
 85
 86// loadRepoEnsureUser is the same as loadRepo, but also ensure that the user has configured
 87// an identity. Use this pre-run function when an error after using the configured user won't
 88// do.
 89func loadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error {
 90	return func(cmd *cobra.Command, args []string) error {
 91		err := loadRepo(env)(cmd, args)
 92		if err != nil {
 93			return err
 94		}
 95
 96		_, err = identity.GetUserIdentity(env.repo)
 97		if err != nil {
 98			return err
 99		}
100
101		return nil
102	}
103}
104
105// loadBackend is a pre-run function that load the repository and the backend for use in a command
106// When using this function you also need to use closeBackend as a post-run
107func loadBackend(env *Env) func(*cobra.Command, []string) error {
108	return func(cmd *cobra.Command, args []string) error {
109		err := loadRepo(env)(cmd, args)
110		if err != nil {
111			return err
112		}
113
114		env.backend, err = cache.NewRepoCache(env.repo)
115		if err != nil {
116			return err
117		}
118
119		cleaner := func(env *Env) interrupt.CleanerFunc {
120			return func() error {
121				if env.backend != nil {
122					err := env.backend.Close()
123					env.backend = nil
124					return err
125				}
126				return nil
127			}
128		}
129
130		// Cleanup properly on interrupt
131		interrupt.RegisterCleaner(cleaner(env))
132		return nil
133	}
134}
135
136// loadBackendEnsureUser is the same as loadBackend, but also ensure that the user has configured
137// an identity. Use this pre-run function when an error after using the configured user won't
138// do.
139func loadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error {
140	return func(cmd *cobra.Command, args []string) error {
141		err := loadBackend(env)(cmd, args)
142		if err != nil {
143			return err
144		}
145
146		_, err = identity.GetUserIdentity(env.repo)
147		if err != nil {
148			return err
149		}
150
151		return nil
152	}
153}
154
155// closeBackend is a wrapper for a RunE function that will close the backend properly
156// if it has been opened.
157// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
158func closeBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
159	return func(cmd *cobra.Command, args []string) error {
160		env.err = out{Writer: cmd.ErrOrStderr()}
161		env.out = out{Writer: cmd.OutOrStdout()}
162
163		errRun := runE(cmd, args)
164
165		if env.backend == nil {
166			return nil
167		}
168		err := env.backend.Close()
169		env.backend = nil
170
171		// prioritize the RunE error
172		if errRun != nil {
173			return errRun
174		}
175		return err
176	}
177}