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