env.go

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