Commit Diff


commit - cec1a3c7758f8fd6368f9066c7bb23580f2cd4d9
commit + 7bb538f44fe2e83c810af33436ca585d4be05b2c
blob - /dev/null
blob + 4c944815a4bb123f565845369d144f93ffccc233 (mode 644)
--- /dev/null
+++ man/gql.1
@@ -0,0 +1,43 @@
+.Dd
+.Dt GQL 1
+.Sh NAME
+.Nm gql
+.Nd execute GraphQL queries
+.Sh SYNOPSIS
+.Nm
+.Op Fl k Ar file
+.Op Fl r Ar file
+.Op Fl v Ar var=value
+.Ar url
+.Op Ar
+.Sh DESCRIPTION
+.Nm
+executes the queries read from the named files
+.Pq or from the standard input if none specified
+to the GraphQL API served at
+.Ar url .
+The response is written to the standard output.
+.Pp
+The following flags are understood:
+.Bl -tag -width indent
+.It Fl k Ar file
+Reads the API key from
+.Pa file
+to be be included in the Authorization field
+of the HTTP request as a Bearer token.
+.It Fl r Ar file
+Loads the JSON-encoded variables object
+from
+.Pa file .
+.It Fl v Ar var=value
+Sets the named variable
+.Ar var
+to
+.Ar value .
+Any variables loaded from a file via the
+.Fl r
+flag will be discarded.
+.El
+
+.Sh EXIT STATUS
+.Ex
blob - /dev/null
blob + 064e8bc2904e37848f24e9a3a708901ad80c1ff0 (mode 644)
--- /dev/null
+++ src/gql/gql.go
@@ -0,0 +1,107 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+)
+
+const usage string = "gql [-k file] [-r file] [-v var=value ] url [file]"
+
+// Firefox ESR (Extended Support Release).
+const firefox = "Mozilla/5.0 (X11; Intel Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
+
+type Query struct {
+	Query     string `json:"query"`
+	Variables any `json:"variables,omitempty"`
+}
+
+var tokenFlag = flag.String("k", "", "file containing api key")
+var varFlag = flag.String("v", "", "variable in key=value format")
+var rFlag = flag.String("r", "", "file containing variables in json format")
+
+var token string
+
+func init() {
+	flag.Parse()
+}
+
+func main() {
+	if len(flag.Args()) == 0 {
+		fmt.Println("usage:", usage)
+		os.Exit(2)
+	}
+
+	if *tokenFlag != "" {
+		b, err := os.ReadFile(*tokenFlag)
+		if err != nil {
+			log.Fatalf("read key file: %v", err)
+		}
+		token = string(bytes.TrimSpace(b))
+	}
+
+	var query Query
+	if *varFlag != "" {
+		k, v, ok := strings.Cut(*varFlag, "=")
+		if !ok {
+			log.Fatalf("missing = separator in variable spec %q", *varFlag)
+		}
+		query.Variables = map[string]string{k: v}
+	} else if *rFlag != "" {
+		f, err := os.Open(*rFlag)
+		if err != nil {
+			log.Fatalf("open variables file: %v", err)
+		}
+		defer f.Close()
+		if err := json.NewDecoder(f).Decode(&query.Variables); err != nil {
+			log.Fatalf("parse variables from %s: %v", *rFlag, err)
+		}
+	}
+
+	url := flag.Arg(0)
+
+	rd := os.Stdin
+	if len(flag.Args()) == 2 {
+		f, err := os.Open(flag.Arg(1))
+		if err != nil {
+			log.Fatal(err)
+		}
+		defer f.Close()
+		rd = f
+	}
+	b, err := io.ReadAll(rd)
+	if err != nil {
+		log.Fatalf("read query: %v", err)
+	}
+	query.Query = string(bytes.TrimSpace(b))
+
+	buf := &bytes.Buffer{}
+	if err := json.NewEncoder(buf).Encode(&query); err != nil {
+		log.Fatalf("encode graphql query: %v", err)
+	}
+	println(buf.String())
+	os.Exit(1)
+
+	req, err := http.NewRequest(http.MethodPost, url, buf)
+	if err != nil {
+		log.Fatal(err)
+	}
+	if token != "" {
+		req.Header.Set("Authorization", "Bearer "+token)
+	}
+	req.Header.Set("User-Agent", firefox)
+	req.Header.Set("Content-Type", "application/json")
+
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Fprintln(os.Stderr, resp.Status)
+	io.Copy(os.Stdout, resp.Body)
+}