github.com/neilotoole/jsoncolor@v0.6.0/cmd/jc/main.go (about)

     1  // Package main contains a trivial CLI that accepts JSON input either
     2  // via stdin or via "-i path/to/input.json", and outputs JSON
     3  // to stdout, or if "-o path/to/output.json" is set, outputs to that file.
     4  // If -c (colorized) is true, output to stdout will be colorized if possible
     5  // (but never colorized for file output).
     6  //
     7  // Examples:
     8  //
     9  //  $ cat example.json | jc
    10  //  $ cat example.json | jc -c false
    11  
    12  package main
    13  
    14  import (
    15  	"errors"
    16  	"flag"
    17  	"fmt"
    18  	"io"
    19  	"io/ioutil"
    20  	"os"
    21  	"path/filepath"
    22  
    23  	"github.com/mattn/go-colorable"
    24  	json "github.com/neilotoole/jsoncolor"
    25  )
    26  
    27  var (
    28  	flagPretty     = flag.Bool("p", true, "output pretty JSON")
    29  	flagColorize   = flag.Bool("c", true, "output colorized JSON")
    30  	flagInputFile  = flag.String("i", "", "path to input JSON file")
    31  	flagOutputFile = flag.String("o", "", "path to output JSON file")
    32  )
    33  
    34  func printUsage() {
    35  	const msg = `
    36  jc (jsoncolor) is a trivial CLI to demonstrate the neilotoole/jsoncolor package.
    37  It accepts JSON input, and outputs colorized, prettified JSON.
    38  
    39  Example Usage:
    40  
    41    # Pipe a JSON file, using defaults (colorized and prettified); print to stdout
    42    $ cat testdata/sakila_actor.json | jc
    43  
    44    # Read input from a JSON file, print to stdout, DO colorize but DO NOT prettify
    45    $ jc -c -p=false -i ./testdata/sakila_actor.json 
    46  
    47    # Pipe a JSON input file to jc, outputting to a specified file; and DO NOT prettify
    48    $ cat ./testdata/sakila_actor.json | jc -p=false -o /tmp/out.json
    49  `
    50  	fmt.Fprintf(os.Stderr, msg)
    51  }
    52  
    53  func main() {
    54  	flag.Parse()
    55  	if err := doMain(); err != nil {
    56  		fmt.Fprintf(os.Stderr, "error: %v\n", err)
    57  		printUsage()
    58  		os.Exit(1)
    59  	}
    60  }
    61  
    62  func doMain() error {
    63  	var (
    64  		input []byte
    65  		err   error
    66  	)
    67  
    68  	if flagInputFile != nil && *flagInputFile != "" {
    69  		// Read from file
    70  		var f *os.File
    71  		if f, err = os.Open(*flagInputFile); err != nil {
    72  			return err
    73  		}
    74  		defer f.Close()
    75  
    76  		if input, err = ioutil.ReadAll(f); err != nil {
    77  			return err
    78  		}
    79  	} else {
    80  		// Probably read from stdin...
    81  		var fi os.FileInfo
    82  		if fi, err = os.Stdin.Stat(); err != nil {
    83  			return err
    84  		}
    85  
    86  		if (fi.Mode() & os.ModeCharDevice) == 0 {
    87  			// Read from stdin
    88  			if input, err = ioutil.ReadAll(os.Stdin); err != nil {
    89  				return err
    90  			}
    91  		} else {
    92  			return errors.New("invalid args")
    93  		}
    94  	}
    95  
    96  	jsn := new(interface{}) // generic interface{} that will hold the parsed JSON
    97  	if err = json.Unmarshal(input, jsn); err != nil {
    98  		return fmt.Errorf("invalid input JSON: %w", err)
    99  	}
   100  
   101  	var out io.Writer
   102  	if flagOutputFile != nil && *flagOutputFile != "" {
   103  		// Output file is specified via -o flag
   104  		var fpath string
   105  		if fpath, err = filepath.Abs(*flagOutputFile); err != nil {
   106  			return fmt.Errorf("failed to get absolute path for -o %q: %w", *flagOutputFile, err)
   107  		}
   108  
   109  		// Ensure the parent dir exists
   110  		if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
   111  			return fmt.Errorf("failed to make parent dir for -o %q: %w", *flagOutputFile, err)
   112  		}
   113  
   114  		var f *os.File
   115  		if f, err = os.Create(fpath); err != nil {
   116  			return fmt.Errorf("failed to open output file specified by -o %q: %w", *flagOutputFile, err)
   117  		}
   118  		defer f.Close()
   119  		out = f
   120  	} else {
   121  		// Output file NOT specified via -o flag, use stdout.
   122  		out = os.Stdout
   123  	}
   124  
   125  	var enc *json.Encoder
   126  
   127  	if flagColorize != nil && *flagColorize == true && json.IsColorTerminal(out) {
   128  		out = colorable.NewColorable(out.(*os.File)) // colorable is needed for Windows
   129  		enc = json.NewEncoder(out)
   130  		clrs := json.DefaultColors()
   131  		enc.SetColors(clrs)
   132  	} else {
   133  		// We are NOT doing color output: either flag not set, or we
   134  		// could be outputting to a file etc.
   135  		// Therefore DO NOT call enc.SetColors.
   136  		enc = json.NewEncoder(out)
   137  	}
   138  
   139  	if flagPretty != nil && *flagPretty == true {
   140  		// Pretty-print, i.e. set indent
   141  		enc.SetIndent("", "  ")
   142  	}
   143  
   144  	if err = enc.Encode(jsn); err != nil {
   145  		return err
   146  	}
   147  
   148  	return nil
   149  }