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