package execenv
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/mattn/go-isatty"
"golang.org/x/term"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/repository"
)
const RootCommandName = "git-bug"
const gitBugNamespace = "git-bug"
// Env is the environment of a command
type Env struct {
Repo repository.ClockedRepo
Backend *cache.RepoCache
In In
Out Out
Err Out
}
func NewEnv() *Env {
return &Env{
Repo: nil,
In: in{Reader: os.Stdin},
Out: out{Writer: os.Stdout},
Err: out{Writer: os.Stderr},
}
}
type In interface {
io.Reader
// IsTerminal tells if the input is a user terminal (rather than a buffer,
// a pipe ...), which tells if we can use interactive features.
IsTerminal() bool
// ForceIsTerminal allow to force the returned value of IsTerminal
// This only works in test scenario.
ForceIsTerminal(value bool)
}
type Out interface {
io.Writer
Printf(format string, a ...interface{})
Print(a ...interface{})
Println(a ...interface{})
PrintJSON(v interface{}) error
// IsTerminal tells if the output is a user terminal (rather than a buffer,
// a pipe ...), which tells if we can use colors and other interactive features.
IsTerminal() bool
// Width return the width of the attached terminal, or a good enough value.
Width() int
// Raw return the underlying io.Writer, or itself if not.
// This is useful if something need to access the raw file descriptor.
Raw() io.Writer
// String returns what have been written in the output before, as a string.
// This only works in test scenario.
String() string
// Bytes returns what have been written in the output before, as []byte.
// This only works in test scenario.
Bytes() []byte
// Reset clear what has been recorded as written in the output before.
// This only works in test scenario.
Reset()
// ForceIsTerminal allow to force the returned value of IsTerminal
// This only works in test scenario.
ForceIsTerminal(value bool)
}
type in struct {
io.Reader
}
func (i in) IsTerminal() bool {
if f, ok := i.Reader.(*os.File); ok {
return isTerminal(f)
}
return false
}
func (i in) ForceIsTerminal(_ bool) {
panic("only work with a test env")
}
type out struct {
io.Writer
}
func (o out) Printf(format string, a ...interface{}) {
_, _ = fmt.Fprintf(o, format, a...)
}
func (o out) Print(a ...interface{}) {
_, _ = fmt.Fprint(o, a...)
}
func (o out) Println(a ...interface{}) {
_, _ = fmt.Fprintln(o, a...)
}
func (o out) PrintJSON(v interface{}) error {
raw, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
o.Println(string(raw))
return nil
}
func (o out) IsTerminal() bool {
if f, ok := o.Writer.(*os.File); ok {
return isTerminal(f)
}
return false
}
func (o out) Width() int {
if f, ok := o.Raw().(*os.File); ok {
width, _, err := term.GetSize(int(f.Fd()))
if err == nil {
return width
}
}
return 80
}
func (o out) Raw() io.Writer {
return o.Writer
}
func (o out) String() string {
panic("only work with a test env")
}
func (o out) Bytes() []byte {
panic("only work with a test env")
}
func (o out) Reset() {
panic("only work with a test env")
}
func (o out) ForceIsTerminal(_ bool) {
panic("only work with a test env")
}
func isTerminal(file *os.File) bool {
return isatty.IsTerminal(file.Fd()) || isatty.IsCygwinTerminal(file.Fd())
}