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