@@ -26,27 +26,28 @@ package main
2626
2727import (
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
3941const (
40- exitSuccess = iota
42+ exitSuccess exitCode = iota
4143 exitFailure
4244)
4345
4446var (
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
5152Tcols reads text from a file and writes the colorized text to the standard
5253output.
8990 %s %s
9091Rgb24:
9192 %s %s
92- `
93- return fmt .Sprintf (
94- s ,
93+ ` ,
9594 `[1mbold[0m` ,
9695 `[2mfaint[0m` ,
9796 `[3mitalic[0m` ,
@@ -140,38 +139,81 @@ Rgb24:
140139 `[38;2;178;12;240mrgb24=fg:178:12:240[0m` ,
141140 `[48;2;57;124;12mrgb24=bg:57:124:12[0m` ,
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