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