feat: v0.3.0d — add PR checks, iostreams, aliases, and broad enhancements
Add PR checks command, iostreams/text packages for colored table output, top-level run/workflow aliases matching gh CLI structure. Enhance actions, issues, PRs, releases, repos, labels, milestones, and wiki commands with improved flags, JSON output, and error handling.
This commit is contained in:
parent
7c0dcc8696
commit
113505de95
29 changed files with 3131 additions and 542 deletions
147
cmd/json.go
147
cmd/json.go
|
|
@ -2,11 +2,154 @@ package cmd
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/itchyny/gojq"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// addJSONFlags adds --json, --json-fields, and --jq flags to a command.
|
||||
// --json is an optional-value string flag:
|
||||
// - --json (no value) → output all fields as JSON
|
||||
// - --json title,state → output only those fields (gh-compatible)
|
||||
//
|
||||
// --json-fields is kept as a backwards-compatible alias.
|
||||
func addJSONFlags(cmd *cobra.Command, jsonDesc string) {
|
||||
f := cmd.Flags()
|
||||
f.String("json", "", jsonDesc)
|
||||
f.Lookup("json").NoOptDefVal = " " // space sentinel: flag present with no value
|
||||
f.String("json-fields", "", "Comma-separated list of JSON fields to include")
|
||||
f.String("jq", "", "Filter JSON output using a jq expression")
|
||||
}
|
||||
|
||||
// wantJSON returns true if the user requested JSON output via --json, --json-fields, or --jq.
|
||||
func wantJSON(cmd *cobra.Command) bool {
|
||||
if j, _ := cmd.Flags().GetString("json"); j != "" {
|
||||
return true
|
||||
}
|
||||
if jq, _ := cmd.Flags().GetString("jq"); jq != "" {
|
||||
return true
|
||||
}
|
||||
if f, _ := cmd.Flags().GetString("json-fields"); f != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// outputJSON writes a value as JSON, respecting --json, --json-fields, and --jq flags.
|
||||
func outputJSON(cmd *cobra.Command, value any) error {
|
||||
jsonVal, _ := cmd.Flags().GetString("json")
|
||||
jsonFields, _ := cmd.Flags().GetString("json-fields")
|
||||
jqExpr, _ := cmd.Flags().GetString("jq")
|
||||
|
||||
fields := ""
|
||||
jsonVal = strings.TrimSpace(jsonVal)
|
||||
if jsonVal != "" {
|
||||
fields = jsonVal
|
||||
} else if jsonFields != "" {
|
||||
fields = jsonFields
|
||||
}
|
||||
|
||||
return writeJSONFiltered(value, fields, jqExpr)
|
||||
}
|
||||
|
||||
// writeJSON writes a value as pretty-printed JSON to ios.Out.
|
||||
func writeJSON(value any) error {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc := json.NewEncoder(ios.Out)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(value)
|
||||
}
|
||||
|
||||
// writeJSONFiltered writes a value as JSON, optionally selecting specific fields
|
||||
// and/or applying a jq expression. If fields is empty and jqExpr is empty, it
|
||||
// writes the full value.
|
||||
func writeJSONFiltered(value any, fields string, jqExpr string) error {
|
||||
// If no filtering, just write the full JSON.
|
||||
if fields == "" && jqExpr == "" {
|
||||
return writeJSON(value)
|
||||
}
|
||||
|
||||
// Convert value to a generic interface via JSON round-trip so we can
|
||||
// manipulate it with maps/slices.
|
||||
raw, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling JSON: %w", err)
|
||||
}
|
||||
|
||||
var data any
|
||||
if err := json.Unmarshal(raw, &data); err != nil {
|
||||
return fmt.Errorf("unmarshaling JSON: %w", err)
|
||||
}
|
||||
|
||||
// Apply field selection if specified.
|
||||
if fields != "" {
|
||||
fieldList := strings.Split(fields, ",")
|
||||
for i, f := range fieldList {
|
||||
fieldList[i] = strings.TrimSpace(f)
|
||||
}
|
||||
data = selectFields(data, fieldList)
|
||||
}
|
||||
|
||||
// Apply jq expression if specified.
|
||||
if jqExpr != "" {
|
||||
return applyJQ(data, jqExpr)
|
||||
}
|
||||
|
||||
return writeJSON(data)
|
||||
}
|
||||
|
||||
// selectFields filters a JSON value to only include the specified fields.
|
||||
// Works on both single objects and arrays of objects.
|
||||
func selectFields(data any, fields []string) any {
|
||||
switch v := data.(type) {
|
||||
case []any:
|
||||
result := make([]any, len(v))
|
||||
for i, item := range v {
|
||||
result[i] = selectFields(item, fields)
|
||||
}
|
||||
return result
|
||||
case map[string]any:
|
||||
result := make(map[string]any)
|
||||
for _, field := range fields {
|
||||
if val, ok := v[field]; ok {
|
||||
result[field] = val
|
||||
}
|
||||
}
|
||||
return result
|
||||
default:
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
// applyJQ applies a jq expression to data and writes each output value.
|
||||
func applyJQ(data any, expr string) error {
|
||||
query, err := gojq.Parse(expr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid jq expression: %w", err)
|
||||
}
|
||||
|
||||
iter := query.Run(data)
|
||||
enc := json.NewEncoder(ios.Out)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
for {
|
||||
v, ok := iter.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if err, isErr := v.(error); isErr {
|
||||
return fmt.Errorf("jq error: %w", err)
|
||||
}
|
||||
// For string values, print raw (no JSON encoding) to match jq behavior.
|
||||
if s, ok := v.(string); ok {
|
||||
fmt.Fprintln(ios.Out, s)
|
||||
} else {
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue