Skip to content

Commit e68093d

Browse files
committed
Merge branch 'develop'
2 parents 0c549ff + e8f4f04 commit e68093d

5 files changed

Lines changed: 425 additions & 143 deletions

File tree

cmd/tcols/main.go

Lines changed: 135 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,28 @@ package main
2626

2727
import (
2828
"bufio"
29+
"errors"
2930
"flag"
3031
"fmt"
3132
"io"
3233
"io/ioutil"
3334
"os"
3435
"strings"
36+
"sync"
3537

3638
"github.com/mdm-code/termcols"
3739
)
3840

3941
const (
40-
exitSuccess = iota
42+
exitSuccess exitCode = iota
4143
exitFailure
4244
)
4345

4446
var (
45-
styles []string
46-
)
47-
48-
func usage() string {
49-
s := `tcols - add color to text on the terminal
47+
styles []string
48+
errParsing error = errors.New("failed to parse CLI arguments")
49+
errPiping error = errors.New("cannot read/write on nil interfaces")
50+
usage = fmt.Sprintf(`tcols - add color to text on the terminal
5051
5152
Tcols reads text from a file and writes the colorized text to the standard
5253
output.
@@ -89,9 +90,7 @@ Rgb8:
8990
%s %s
9091
Rgb24:
9192
%s %s
92-
`
93-
return fmt.Sprintf(
94-
s,
93+
`,
9594
`bold`,
9695
`faint`,
9796
`italic`,
@@ -140,38 +139,81 @@ Rgb24:
140139
`rgb24=fg:178:12:240`,
141140
`rgb24=bg:57:124:12`,
142141
)
142+
)
143+
144+
type (
145+
exitCode = int
146+
openFn = func([]string, func(string) (*os.File, error)) ([]io.Reader, func(), error)
147+
exitFunc func(exitCode)
148+
149+
failer struct {
150+
w io.Writer
151+
fn exitFunc
152+
code exitCode
153+
mu sync.Locker
154+
}
155+
156+
concurrentWriter struct {
157+
w *bufio.Writer
158+
sync.Mutex
159+
}
160+
)
161+
162+
func (f *failer) fail(e error) (exitFunc, exitCode) {
163+
f.mu.Lock()
164+
fmt.Fprintf(f.w, e.Error())
165+
f.mu.Unlock()
166+
return f.fn, f.code
167+
}
168+
169+
func (cw *concurrentWriter) Write(p []byte) (n int, err error) {
170+
cw.Lock()
171+
n, err = cw.w.Write(p)
172+
cw.Unlock()
173+
return
143174
}
144175

145-
func args() {
146-
for _, flagName := range []string{"s", "style"} {
147-
flag.Func(
148-
flagName,
176+
func (cw *concurrentWriter) Flush() error {
177+
return cw.w.Flush()
178+
}
179+
180+
func newFailer(w io.Writer, fn exitFunc, code exitCode) failer {
181+
return failer{w, fn, code, &sync.Mutex{}}
182+
}
183+
184+
func newConcurrentWriter(w io.Writer) *concurrentWriter {
185+
return &concurrentWriter{w: bufio.NewWriter(w)}
186+
}
187+
188+
func parse(args []string, open openFn) ([]io.Reader, func(), error) {
189+
if len(args) == 0 {
190+
return []io.Reader{}, func() {}, errParsing
191+
}
192+
fs := flag.NewFlagSet("tcols", flag.ExitOnError)
193+
for _, fName := range []string{"s", "styles"} {
194+
fs.Func(
195+
fName,
149196
"list of styles and colors to apply to text",
150197
func(v string) error {
151-
styles = strings.Fields(v)
198+
styles = append(styles, strings.Fields(v)...)
152199
return nil
153200
},
154201
)
155202
}
156-
flag.Usage = func() { fmt.Print(usage()) }
157-
flag.Parse()
158-
}
159-
160-
func readText(bb *[]byte, rr ...io.Reader) error {
161-
if len(rr) == 0 {
162-
return nil
203+
fs.Usage = func() { fmt.Printf(usage) }
204+
err := fs.Parse(args)
205+
if err != nil {
206+
return []io.Reader{}, func() {}, err
163207
}
164-
for _, r := range rr {
165-
b, err := ioutil.ReadAll(r)
166-
if err != nil {
167-
return err
168-
}
169-
*bb = append(*bb, b...)
208+
if len(fs.Args()) > 0 {
209+
return open(fs.Args(), os.Open)
170210
}
171-
return nil
211+
return []io.Reader{os.Stdin}, func() {}, nil
172212
}
173213

174-
func argsFiles() ([]io.Reader, func(), error) {
214+
// Open opens files to have their contents read. The function f serves as the
215+
// main callable responsible for opening files.
216+
func open(fnames []string, f func(string) (*os.File, error)) ([]io.Reader, func(), error) {
175217
var files []io.Reader
176218
closer := func() {
177219
for _, f := range files {
@@ -181,8 +223,8 @@ func argsFiles() ([]io.Reader, func(), error) {
181223
}
182224
}
183225
}
184-
for _, fname := range flag.Args() {
185-
f, err := os.Open(fname)
226+
for _, fname := range fnames {
227+
f, err := f(fname)
186228
if err != nil {
187229
return files, closer, err
188230
}
@@ -191,42 +233,75 @@ func argsFiles() ([]io.Reader, func(), error) {
191233
return files, closer, nil
192234
}
193235

194-
func fail(w io.Writer, e error) {
195-
fmt.Fprintf(w, e.Error())
196-
os.Exit(exitFailure)
197-
}
198-
199-
func main() {
200-
args()
201-
text := make([]byte, 0, 64)
202-
if len(flag.Args()) > 0 {
203-
files, closer, err := argsFiles()
204-
defer closer()
205-
if err != nil {
206-
fail(os.Stderr, err)
207-
}
208-
err = readText(&text, files...)
209-
if err != nil {
210-
fail(os.Stderr, err)
211-
}
212-
} else {
213-
err := readText(&text, os.Stdin)
214-
if err != nil {
215-
fail(os.Stderr, err)
216-
}
236+
func pipe(r io.Reader, w io.Writer, styles []string) error {
237+
if r == nil && w == nil {
238+
return errPiping
239+
}
240+
text, err := ioutil.ReadAll(r)
241+
if err != nil {
242+
return errPiping
217243
}
218-
out := bufio.NewWriter(os.Stdout)
219244
colors, err := termcols.MapColors(styles)
220245
if err != nil {
221-
fail(os.Stderr, err)
246+
return err
222247
}
223-
output := termcols.Colorize(string(text), colors...)
224-
_, err = out.WriteString(output)
248+
colored := termcols.Colorize(string(text), colors...)
249+
_, err = io.WriteString(w, colored)
225250
if err != nil {
226-
fail(os.Stderr, err)
251+
return errPiping
252+
}
253+
return nil
254+
}
255+
256+
func run(args []string, fn openFn) error {
257+
files, closer, err := parse(args, fn)
258+
defer closer()
259+
if err != nil {
260+
return err
261+
}
262+
263+
out := newConcurrentWriter(os.Stdout)
264+
265+
var wg sync.WaitGroup
266+
wg.Add(len(files))
267+
268+
done := make(chan struct{})
269+
fail := make(chan error)
270+
271+
for _, f := range files {
272+
go func(r io.Reader) {
273+
defer wg.Done()
274+
err := pipe(r, out, styles)
275+
if err != nil {
276+
fail <- err
277+
}
278+
}(f)
279+
}
280+
281+
go func() {
282+
wg.Wait()
283+
close(done)
284+
}()
285+
286+
select {
287+
case <-done:
288+
break
289+
case err := <-fail:
290+
return err
227291
}
292+
228293
if err := out.Flush(); err != nil {
229-
fail(os.Stderr, err)
294+
return err
295+
}
296+
return nil
297+
}
298+
299+
func main() {
300+
f := newFailer(os.Stderr, os.Exit, exitFailure)
301+
err := run(os.Args[1:], open)
302+
if err != nil {
303+
exit, code := f.fail(err)
304+
exit(code)
230305
}
231306
os.Exit(exitSuccess)
232307
}

0 commit comments

Comments
 (0)