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}