aboutsummaryrefslogtreecommitdiffstats
path: root/commands/execenv/env.go
blob: e693807e6ddecc44737c4e9cb874242f9741700d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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())
}