env.go

  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}