env.go

  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}