env.go

  1package execenv
  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 RootCommandName = "git-bug"
 18
 19const gitBugNamespace = "git-bug"
 20
 21// Env is the environment of a command
 22type Env struct {
 23	Repo    repository.ClockedRepo
 24	Backend *cache.RepoCache
 25	Out     Out
 26	Err     Out
 27}
 28
 29func NewEnv() *Env {
 30	return &Env{
 31		Repo: nil,
 32		Out:  out{Writer: os.Stdout},
 33		Err:  out{Writer: os.Stderr},
 34	}
 35}
 36
 37type Out interface {
 38	io.Writer
 39	Printf(format string, a ...interface{})
 40	Print(a ...interface{})
 41	Println(a ...interface{})
 42
 43	// String returns what have been written in the output before, as a string.
 44	// This only works in test scenario.
 45	String() string
 46	// Bytes returns what have been written in the output before, as []byte.
 47	// This only works in test scenario.
 48	Bytes() []byte
 49	// Reset clear what has been recorded as written in the output before.
 50	// This only works in test scenario.
 51	Reset()
 52}
 53
 54type out struct {
 55	io.Writer
 56}
 57
 58func (o out) Printf(format string, a ...interface{}) {
 59	_, _ = fmt.Fprintf(o, format, a...)
 60}
 61
 62func (o out) Print(a ...interface{}) {
 63	_, _ = fmt.Fprint(o, a...)
 64}
 65
 66func (o out) Println(a ...interface{}) {
 67	_, _ = fmt.Fprintln(o, a...)
 68}
 69
 70func (o out) String() string {
 71	panic("only work with a test env")
 72}
 73
 74func (o out) Bytes() []byte {
 75	panic("only work with a test env")
 76}
 77
 78func (o out) Reset() {
 79	panic("only work with a test env")
 80}
 81
 82// LoadRepo is a pre-run function that load the repository for use in a command
 83func LoadRepo(env *Env) func(*cobra.Command, []string) error {
 84	return func(cmd *cobra.Command, args []string) error {
 85		cwd, err := os.Getwd()
 86		if err != nil {
 87			return fmt.Errorf("unable to get the current working directory: %q", err)
 88		}
 89
 90		env.Repo, err = repository.OpenGoGitRepo(cwd, gitBugNamespace, []repository.ClockLoader{bug.ClockLoader})
 91		if err == repository.ErrNotARepo {
 92			return fmt.Errorf("%s must be run from within a git Repo", RootCommandName)
 93		}
 94
 95		if err != nil {
 96			return err
 97		}
 98
 99		return nil
100	}
101}
102
103// LoadRepoEnsureUser is the same as LoadRepo, but also ensure that the user has configured
104// an identity. Use this pre-run function when an error after using the configured user won't
105// do.
106func LoadRepoEnsureUser(env *Env) func(*cobra.Command, []string) error {
107	return func(cmd *cobra.Command, args []string) error {
108		err := LoadRepo(env)(cmd, args)
109		if err != nil {
110			return err
111		}
112
113		_, err = identity.GetUserIdentity(env.Repo)
114		if err != nil {
115			return err
116		}
117
118		return nil
119	}
120}
121
122// LoadBackend is a pre-run function that load the repository and the Backend for use in a command
123// When using this function you also need to use CloseBackend as a post-run
124func LoadBackend(env *Env) func(*cobra.Command, []string) error {
125	return func(cmd *cobra.Command, args []string) error {
126		err := LoadRepo(env)(cmd, args)
127		if err != nil {
128			return err
129		}
130
131		var events chan cache.BuildEvent
132		env.Backend, events, err = cache.NewRepoCache(env.Repo)
133		if err != nil {
134			return err
135		}
136
137		for event := range events {
138			if event.Err != nil {
139				env.Err.Printf("Cache building error [%s]: %v\n", event.Typename, event.Err)
140				continue
141			}
142			switch event.Event {
143			case cache.BuildEventCacheIsBuilt:
144				env.Err.Println("Building cache... ")
145			case cache.BuildEventStarted:
146				env.Err.Printf("[%s] started\n", event.Typename)
147			case cache.BuildEventFinished:
148				env.Err.Printf("[%s] done\n", event.Typename)
149			}
150		}
151
152		cleaner := func(env *Env) interrupt.CleanerFunc {
153			return func() error {
154				if env.Backend != nil {
155					err := env.Backend.Close()
156					env.Backend = nil
157					return err
158				}
159				return nil
160			}
161		}
162
163		// Cleanup properly on interrupt
164		interrupt.RegisterCleaner(cleaner(env))
165		return nil
166	}
167}
168
169// LoadBackendEnsureUser is the same as LoadBackend, but also ensure that the user has configured
170// an identity. Use this pre-run function when an error after using the configured user won't
171// do.
172func LoadBackendEnsureUser(env *Env) func(*cobra.Command, []string) error {
173	return func(cmd *cobra.Command, args []string) error {
174		err := LoadBackend(env)(cmd, args)
175		if err != nil {
176			return err
177		}
178
179		_, err = identity.GetUserIdentity(env.Repo)
180		if err != nil {
181			return err
182		}
183
184		return nil
185	}
186}
187
188// CloseBackend is a wrapper for a RunE function that will close the Backend properly
189// if it has been opened.
190// This wrapper style is necessary because a Cobra PostE function does not run if RunE return an error.
191func CloseBackend(env *Env, runE func(cmd *cobra.Command, args []string) error) func(*cobra.Command, []string) error {
192	return func(cmd *cobra.Command, args []string) error {
193		errRun := runE(cmd, args)
194
195		if env.Backend == nil {
196			return nil
197		}
198		err := env.Backend.Close()
199		env.Backend = nil
200
201		// prioritize the RunE error
202		if errRun != nil {
203			return errRun
204		}
205		return err
206	}
207}