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}