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