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}