...

Source file src/github.com/cli/go-gh/v2/pkg/jq/jq.go

Documentation: github.com/cli/go-gh/v2/pkg/jq

     1  // Package jq facilitates processing of JSON strings using jq expressions.
     2  package jq
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"math"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/cli/go-gh/v2/pkg/jsonpretty"
    16  	"github.com/itchyny/gojq"
    17  )
    18  
    19  // Evaluate a jq expression against an input and write it to an output.
    20  // Any top-level scalar values produced by the jq expression are written out
    21  // directly, as raw values and not as JSON scalars, similar to how jq --raw
    22  // works.
    23  func Evaluate(input io.Reader, output io.Writer, expr string) error {
    24  	return EvaluateFormatted(input, output, expr, "", false)
    25  }
    26  
    27  // Evaluate a jq expression against an input and write it to an output,
    28  // optionally with indentation and colorization.  Any top-level scalar values
    29  // produced by the jq expression are written out directly, as raw values and not
    30  // as JSON scalars, similar to how jq --raw works.
    31  func EvaluateFormatted(input io.Reader, output io.Writer, expr string, indent string, colorize bool) error {
    32  	query, err := gojq.Parse(expr)
    33  	if err != nil {
    34  		var e *gojq.ParseError
    35  		if errors.As(err, &e) {
    36  			str, line, column := getLineColumn(expr, e.Offset-len(e.Token))
    37  			return fmt.Errorf(
    38  				"failed to parse jq expression (line %d, column %d)\n    %s\n    %*c  %w",
    39  				line, column, str, column, '^', err,
    40  			)
    41  		}
    42  		return err
    43  	}
    44  
    45  	code, err := gojq.Compile(
    46  		query,
    47  		gojq.WithEnvironLoader(func() []string {
    48  			return os.Environ()
    49  		}))
    50  	if err != nil {
    51  		return err
    52  	}
    53  
    54  	jsonData, err := io.ReadAll(input)
    55  	if err != nil {
    56  		return err
    57  	}
    58  
    59  	var responseData interface{}
    60  	err = json.Unmarshal(jsonData, &responseData)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	enc := prettyEncoder{
    66  		w:        output,
    67  		indent:   indent,
    68  		colorize: colorize,
    69  	}
    70  
    71  	iter := code.Run(responseData)
    72  	for {
    73  		v, ok := iter.Next()
    74  		if !ok {
    75  			break
    76  		}
    77  		if err, isErr := v.(error); isErr {
    78  			var e *gojq.HaltError
    79  			if errors.As(err, &e) && e.Value() == nil {
    80  				break
    81  			}
    82  			return err
    83  		}
    84  		if text, e := jsonScalarToString(v); e == nil {
    85  			_, err := fmt.Fprintln(output, text)
    86  			if err != nil {
    87  				return err
    88  			}
    89  		} else {
    90  			if err = enc.Encode(v); err != nil {
    91  				return err
    92  			}
    93  		}
    94  	}
    95  
    96  	return nil
    97  }
    98  
    99  func jsonScalarToString(input interface{}) (string, error) {
   100  	switch tt := input.(type) {
   101  	case string:
   102  		return tt, nil
   103  	case float64:
   104  		if math.Trunc(tt) == tt {
   105  			return strconv.FormatFloat(tt, 'f', 0, 64), nil
   106  		} else {
   107  			return strconv.FormatFloat(tt, 'f', 2, 64), nil
   108  		}
   109  	case nil:
   110  		return "", nil
   111  	case bool:
   112  		return fmt.Sprintf("%v", tt), nil
   113  	default:
   114  		return "", fmt.Errorf("cannot convert type to string: %v", tt)
   115  	}
   116  }
   117  
   118  type prettyEncoder struct {
   119  	w        io.Writer
   120  	indent   string
   121  	colorize bool
   122  }
   123  
   124  func (p prettyEncoder) Encode(v any) error {
   125  	var b []byte
   126  	var err error
   127  	if p.indent == "" {
   128  		b, err = json.Marshal(v)
   129  	} else {
   130  		b, err = json.MarshalIndent(v, "", p.indent)
   131  	}
   132  	if err != nil {
   133  		return err
   134  	}
   135  	if !p.colorize {
   136  		if _, err := p.w.Write(b); err != nil {
   137  			return err
   138  		}
   139  		if _, err := p.w.Write([]byte{'\n'}); err != nil {
   140  			return err
   141  		}
   142  		return nil
   143  	}
   144  	return jsonpretty.Format(p.w, bytes.NewReader(b), p.indent, true)
   145  }
   146  
   147  func getLineColumn(expr string, offset int) (string, int, int) {
   148  	for line := 1; ; line++ {
   149  		index := strings.Index(expr, "\n")
   150  		if index < 0 {
   151  			return expr, line, offset + 1
   152  		}
   153  		if index >= offset {
   154  			return expr[:index], line, offset + 1
   155  		}
   156  		expr = expr[index+1:]
   157  		offset -= index + 1
   158  	}
   159  }
   160  

View as plain text