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