1package execenv
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "os"
8
9 "github.com/mattn/go-isatty"
10
11 "github.com/MichaelMure/git-bug/cache"
12 "github.com/MichaelMure/git-bug/repository"
13)
14
15const RootCommandName = "git-bug"
16
17const gitBugNamespace = "git-bug"
18
19// Env is the environment of a command
20type Env struct {
21 Repo repository.ClockedRepo
22 Backend *cache.RepoCache
23 In In
24 Out Out
25 Err Out
26}
27
28func NewEnv() *Env {
29 return &Env{
30 Repo: nil,
31 In: in{Reader: os.Stdin},
32 Out: out{Writer: os.Stdout},
33 Err: out{Writer: os.Stderr},
34 }
35}
36
37type In interface {
38 io.Reader
39
40 // IsTerminal tells if the input is a user terminal (rather than a buffer,
41 // a pipe ...), which tells if we can use interactive features.
42 IsTerminal() bool
43
44 // ForceIsTerminal allow to force the returned value of IsTerminal
45 // This only works in test scenario.
46 ForceIsTerminal(value bool)
47}
48
49type Out interface {
50 io.Writer
51
52 Printf(format string, a ...interface{})
53 Print(a ...interface{})
54 Println(a ...interface{})
55 PrintJSON(v interface{}) error
56
57 // IsTerminal tells if the output is a user terminal (rather than a buffer,
58 // a pipe ...), which tells if we can use colors and other interactive features.
59 IsTerminal() bool
60
61 // Raw return the underlying io.Writer, or itself if not.
62 // This is useful if something need to access the raw file descriptor.
63 Raw() io.Writer
64
65 // String returns what have been written in the output before, as a string.
66 // This only works in test scenario.
67 String() string
68 // Bytes returns what have been written in the output before, as []byte.
69 // This only works in test scenario.
70 Bytes() []byte
71 // Reset clear what has been recorded as written in the output before.
72 // This only works in test scenario.
73 Reset()
74 // ForceIsTerminal allow to force the returned value of IsTerminal
75 // This only works in test scenario.
76 ForceIsTerminal(value bool)
77}
78
79type in struct {
80 io.Reader
81}
82
83func (i in) IsTerminal() bool {
84 if f, ok := i.Reader.(*os.File); ok {
85 return isTerminal(f)
86 }
87 return false
88}
89
90func (i in) ForceIsTerminal(_ bool) {
91 panic("only work with a test env")
92}
93
94type out struct {
95 io.Writer
96}
97
98func (o out) Printf(format string, a ...interface{}) {
99 _, _ = fmt.Fprintf(o, format, a...)
100}
101
102func (o out) Print(a ...interface{}) {
103 _, _ = fmt.Fprint(o, a...)
104}
105
106func (o out) Println(a ...interface{}) {
107 _, _ = fmt.Fprintln(o, a...)
108}
109
110func (o out) PrintJSON(v interface{}) error {
111 raw, err := json.MarshalIndent(v, "", " ")
112 if err != nil {
113 return err
114 }
115 o.Println(string(raw))
116 return nil
117}
118
119func (o out) IsTerminal() bool {
120 if f, ok := o.Writer.(*os.File); ok {
121 return isTerminal(f)
122 }
123 return false
124}
125
126func (o out) Raw() io.Writer {
127 return o.Writer
128}
129
130func (o out) String() string {
131 panic("only work with a test env")
132}
133
134func (o out) Bytes() []byte {
135 panic("only work with a test env")
136}
137
138func (o out) Reset() {
139 panic("only work with a test env")
140}
141
142func (o out) ForceIsTerminal(_ bool) {
143 panic("only work with a test env")
144}
145
146func isTerminal(file *os.File) bool {
147 return isatty.IsTerminal(file.Fd()) || isatty.IsCygwinTerminal(file.Fd())
148}