Repository: bitfield/script
Pull request #106 (2022-04-06 17:13:52)

Add JQ filter


Patch:
diff --git a/README.md b/README.md
index a6974f2..f273c93 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ If you're already familiar with shell scripting and the Unix toolset, here is a
 | `grep -v`          | [`Reject()`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Reject) / [`RejectRegexp()`](https://pkg.go.dev/github.com/bitfield/script#Pipe.RejectRegexp) |
 | `head`             | [`First()`](https://pkg.go.dev/github.com/bitfield/script#Pipe.First) |
 | `find -type f`     | [`FindFiles`](https://pkg.go.dev/github.com/bitfield/script#FindFiles) |
+| `jq`     | [`JQ`](https://pkg.go.dev/github.com/bitfield/script#Pipe.JQ) |
 | `ls`               | [`ListFiles()`](https://pkg.go.dev/github.com/bitfield/script#ListFiles) |
 | `sed`              | [`Replace()`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Replace) / [`ReplaceRegexp()`](https://pkg.go.dev/github.com/bitfield/script#Pipe.ReplaceRegexp) |
 | `sha256sum`        | [`SHA256Sum()`](https://pkg.go.dev/github.com/bitfield/script#Pipe.SHA256Sum) / [`SHA256Sums()`](https://pkg.go.dev/github.com/bitfield/script#Pipe.SHA256Sums) |
@@ -97,6 +98,12 @@ What's that? You want to append that output to a file instead of printing it to
 script.Args().Concat().Match("Error").First(10).AppendFile("/var/log/errors.txt")
 ```
 
+If the data is JSON, we can do better than simple string-matching. We can use [JQ](https://stedolan.github.io/jq/) queries:
+
+```go
+script.File("commits.json").JQ(".[0] | {message: .commit.message, name: .commit.committer.name}").Stdout()
+```
+
 Suppose we want to execute some external program instead of doing the work ourselves. We can do that too:
 
 ```go
@@ -214,6 +221,7 @@ Filters are methods on an existing pipe that also return a pipe, allowing you to
 | [`First`](https://pkg.go.dev/github.com/bitfield/script#Pipe.First) | first N lines of input |
 | [`Freq`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Freq) | frequency count of unique input lines, most frequent first |
 | [`Join`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Join) | replace all newlines with spaces |
+| [`JQ`](https://pkg.go.dev/github.com/bitfield/script#Pipe.JQ) | result of `jq` query |
 | [`Last`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Last) | last N lines of input|
 | [`Match`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Match) | lines matching given string |
 | [`MatchRegexp`](https://pkg.go.dev/github.com/bitfield/script#Pipe.MatchRegexp) | lines matching given regexp |
@@ -242,6 +250,12 @@ Sinks are methods that return some data from a pipe, ending the pipeline and ext
 | [`Wait`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Wait) | | none  |
 | [`WriteFile`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WriteFile) | specified file, truncating if it exists | bytes written, error  |
 
+# What's new
+
+| Version | New |
+| ----------- | ------- |
+| v0.20.0 | [`JQ`](https://pkg.go.dev/github.com/bitfield/script#Pipe.JQ) |
+
 # Contributing
 
 See the [contributor's guide](CONTRIBUTING.md) for some helpful tips if you'd like to contribute to the `script` project.
diff --git a/go.mod b/go.mod
index 813e5aa..f9d555a 100644
--- a/go.mod
+++ b/go.mod
@@ -5,5 +5,6 @@ go 1.12
 require (
 	bitbucket.org/creachadair/shell v0.0.7
 	github.com/google/go-cmp v0.5.7
+	github.com/itchyny/gojq v0.12.7
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 )
diff --git a/go.sum b/go.sum
index 393c9f0..0754dbd 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,19 @@
 bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk=
 bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/itchyny/gojq v0.12.7 h1:hYPTpeWfrJ1OT+2j6cvBScbhl0TkdwGM4bc66onUSOQ=
+github.com/itchyny/gojq v0.12.7/go.mod h1:ZdvNHVlzPgUf8pgjnuDTmGfHA/21KoutQUJ3An/xNuw=
+github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
+github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/script.go b/script.go
index fe0c274..8703873 100644
--- a/script.go
+++ b/script.go
@@ -5,6 +5,7 @@ import (
 	"container/ring"
 	"crypto/sha256"
 	"encoding/hex"
+	"encoding/json"
 	"fmt"
 	"io"
 	"os"
@@ -18,6 +19,7 @@ import (
 	"text/template"
 
 	"bitbucket.org/creachadair/shell"
+	"github.com/itchyny/gojq"
 )
 
 // ReadAutoCloser represents a pipe source that will be automatically closed
@@ -603,6 +605,42 @@ func (p *Pipe) Join() *Pipe {
 	})
 }
 
+// JQ takes a query in the 'jq' language and applies it to the input (presumed
+// to be JSON), producing the result. An invalid query will set the appropriate
+// error on the pipe.
+//
+// The exact dialect of JQ supported is that provided by
+// github.com/itchyny/gojq, whose documentation explains the differences between
+// it and 'standard' JQ.
+func (p *Pipe) JQ(query string) *Pipe {
+	return p.Filter(func(r io.Reader, w io.Writer) error {
+		q, err := gojq.Parse(query)
+		if err != nil {
+			return err
+		}
+		var input interface{}
+		err = json.NewDecoder(r).Decode(&input)
+		if err != nil {
+			return err
+		}
+		iter := q.Run(input)
+		for {
+			v, ok := iter.Next()
+			if !ok {
+				return nil
+			}
+			if err, ok := v.(error); ok {
+				return err
+			}
+			result, err := gojq.Marshal(v)
+			if err != nil {
+				return err
+			}
+			fmt.Fprintln(w, string(result))
+		}
+	})
+}
+
 // Last produces only the last N lines of input, or the whole input if there are
 // less than N lines. If N is zero or negative, there is no output at all.
 func (p *Pipe) Last(n int) *Pipe {


Test patch:
diff --git a/script_test.go b/script_test.go
index 4e5c60b..f87b610 100644
--- a/script_test.go
+++ b/script_test.go
@@ -544,6 +544,86 @@ func TestJoinJoinsInputLinesIntoSpaceSeparatedString(t *testing.T) {
 	}
 }
 
+func TestJQWithDotQueryPrettyPrintsInput(t *testing.T) {
+	t.Parallel()
+	input := `{"timestamp": 1649264191, "iss_position": {"longitude": "52.8439", "latitude": "10.8107"}, "message": "success"}`
+	// Fields should be sorted by key, with whitespace removed
+	want := `{"iss_position":{"latitude":"10.8107","longitude":"52.8439"},"message":"success","timestamp":1649264191}` + "\n"
+	got, err := script.Echo(input).JQ(".").String()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want != got {
+		t.Error(want, got)
+		t.Error(cmp.Diff(want, got))
+	}
+}
+
+func TestJQWithFieldQueryProducesSelectedField(t *testing.T) {
+	t.Parallel()
+	input := `{"timestamp": 1649264191, "iss_position": {"longitude": "52.8439", "latitude": "10.8107"}, "message": "success"}`
+	want := `{"latitude":"10.8107","longitude":"52.8439"}` + "\n"
+	got, err := script.Echo(input).JQ(".iss_position").String()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want != got {
+		t.Error(want, got)
+		t.Error(cmp.Diff(want, got))
+	}
+}
+
+func TestJQWithArrayQueryProducesRequiredArray(t *testing.T) {
+	t.Parallel()
+	input := `{"timestamp": 1649264191, "iss_position": {"longitude": "52.8439", "latitude": "10.8107"}, "message": "success"}`
+	want := `["10.8107","52.8439"]` + "\n"
+	got, err := script.Echo(input).JQ("[.iss_position.latitude, .iss_position.longitude]").String()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want != got {
+		t.Error(want, got)
+		t.Error(cmp.Diff(want, got))
+	}
+}
+
+func TestJQWithArrayInputAndElementQueryProducesSelectedElement(t *testing.T) {
+	t.Parallel()
+	input := `[1, 2, 3]`
+	want := "1\n"
+	got, err := script.Echo(input).JQ(".[0]").String()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want != got {
+		t.Error(want, got)
+		t.Error(cmp.Diff(want, got))
+	}
+}
+
+func TestJQHandlesGithubJSONWithRealWorldExampleQuery(t *testing.T) {
+	t.Parallel()
+	want := `{"message":"restore sample log data (fixes #102)","name":"John Arundel"}` + "\n"
+	got, err := script.File("testdata/commits.json").
+		JQ(".[0] | {message: .commit.message, name: .commit.committer.name}").String()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want != got {
+		t.Error(want, got)
+		t.Error(cmp.Diff(want, got))
+	}
+}
+
+func TestJQErrorsWithInvalidQuery(t *testing.T) {
+	t.Parallel()
+	input := `[1, 2, 3]`
+	_, err := script.Echo(input).JQ(".foo & .bar").String()
+	if err == nil {
+		t.Error("want error from invalid JQ query, got nil")
+	}
+}
+
 func TestLastDropsAllButLastNLinesOfInput(t *testing.T) {
 	t.Parallel()
 	input := "a\nb\nc\n"
@@ -1596,6 +1676,15 @@ func ExamplePipe_Join() {
 	// hello world
 }
 
+func ExamplePipe_JQ() {
+	kernel := "Darwin"
+	arch := "x86_64"
+	query := fmt.Sprintf(".assets[] | select(.name | endswith(\"%s_%s.tar.gz\")).browser_download_url", kernel, arch)
+	script.File("testdata/releases.json").JQ(query).Stdout()
+	// Output:
+	// "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/blackjack_0.3.3_Darwin_x86_64.tar.gz"
+}
+
 func ExamplePipe_Last() {
 	script.Echo("a\nb\nc\n").Last(2).Stdout()
 	// Output:
diff --git a/testdata/commits.json b/testdata/commits.json
new file mode 100644
index 0000000..cb46945
--- /dev/null
+++ b/testdata/commits.json
@@ -0,0 +1,402 @@
+[
+  {
+    "sha": "0f734f4dfd5bcdb38d8309f9c0d8c515732c7bdf",
+    "node_id": "C_kwDOCt91vdoAKDBmNzM0ZjRkZmQ1YmNkYjM4ZDgzMDlmOWMwZDhjNTE1NzMyYzdiZGY",
+    "commit": {
+      "author": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-27T09:53:42Z"
+      },
+      "committer": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-27T09:53:42Z"
+      },
+      "message": "restore sample log data (fixes #102)",
+      "tree": {
+        "sha": "848dd685e8a12f5d02ccfa5696ad73fb56bbe4e1",
+        "url": "https://api.github.com/repos/bitfield/script/git/trees/848dd685e8a12f5d02ccfa5696ad73fb56bbe4e1"
+      },
+      "url": "https://api.github.com/repos/bitfield/script/git/commits/0f734f4dfd5bcdb38d8309f9c0d8c515732c7bdf",
+      "comment_count": 0,
+      "verification": {
+        "verified": false,
+        "reason": "unsigned",
+        "signature": null,
+        "payload": null
+      }
+    },
+    "url": "https://api.github.com/repos/bitfield/script/commits/0f734f4dfd5bcdb38d8309f9c0d8c515732c7bdf",
+    "html_url": "https://github.com/bitfield/script/commit/0f734f4dfd5bcdb38d8309f9c0d8c515732c7bdf",
+    "comments_url": "https://api.github.com/repos/bitfield/script/commits/0f734f4dfd5bcdb38d8309f9c0d8c515732c7bdf/comments",
+    "author": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "committer": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "parents": [
+      {
+        "sha": "85fa6426e7dcadb70cca582f028445ceafb1963b",
+        "url": "https://api.github.com/repos/bitfield/script/commits/85fa6426e7dcadb70cca582f028445ceafb1963b",
+        "html_url": "https://github.com/bitfield/script/commit/85fa6426e7dcadb70cca582f028445ceafb1963b"
+      }
+    ]
+  },
+  {
+    "sha": "85fa6426e7dcadb70cca582f028445ceafb1963b",
+    "node_id": "C_kwDOCt91vdoAKDg1ZmE2NDI2ZTdkY2FkYjcwY2NhNTgyZjAyODQ0NWNlYWZiMTk2M2I",
+    "commit": {
+      "author": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-23T09:36:44Z"
+      },
+      "committer": {
+        "name": "GitHub",
+        "email": "noreply@github.com",
+        "date": "2022-03-23T09:36:44Z"
+      },
+      "message": "Merge pull request #101 from bitfield/filter\n\nMake pipe filters concurrent\r\n\r\nCo-authored-by: Puchen Xu <korolxu@umich.edu>\r\nCo-authored-by: Christoph Petrausch <263448+hikhvar@users.noreply.github.com>",
+      "tree": {
+        "sha": "549df3edaccf5afc94b6089496b03cbb68677050",
+        "url": "https://api.github.com/repos/bitfield/script/git/trees/549df3edaccf5afc94b6089496b03cbb68677050"
+      },
+      "url": "https://api.github.com/repos/bitfield/script/git/commits/85fa6426e7dcadb70cca582f028445ceafb1963b",
+      "comment_count": 0,
+      "verification": {
+        "verified": true,
+        "reason": "valid",
+        "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJiOuosCRBK7hj4Ov3rIwAAJKIIAAiUbw1SJOa4OxKHKAb8YZFF\nxIqe9ezhC0vynjWenmqDjIQ5G3slSU5faCwmmaSfWf09HxBmohqiWWkfsS1Ifk6n\nIV6eSDFxuGpqP/Oyar+n+BjDL3QOl79nVbfTIhgGuQQinwyV97002QqCBDRxDD6X\nulTaR+bZxB6iBlJ/nKT7BZEAU7AG6UQeE7lyULDA7z2fzoUFA6J1yF8XU/4aLI3s\nV9KOBUUC3xPZ7JllExagKbM4CTIlpXHD4wFqG7BuSUWDTqmwaQ27Pi8a05M+2Qzy\nR1fm/LYH5OD/NkmDoLINZfPRIJmNyM4/xAxR/vPIHCiPTi74+V74zKoNfdk0I5Y=\n=uhmg\n-----END PGP SIGNATURE-----\n",
+        "payload": "tree 549df3edaccf5afc94b6089496b03cbb68677050\nparent 7955ae56a26545e9f54ee707d190398559ba2841\nparent 3418243ce0a037990ed3303b70b2f4511ec430ce\nauthor John Arundel <john@bitfieldconsulting.com> 1648028204 +0000\ncommitter GitHub <noreply@github.com> 1648028204 +0000\n\nMerge pull request #101 from bitfield/filter\n\nMake pipe filters concurrent\r\n\r\nCo-authored-by: Puchen Xu <korolxu@umich.edu>\r\nCo-authored-by: Christoph Petrausch <263448+hikhvar@users.noreply.github.com>"
+      }
+    },
+    "url": "https://api.github.com/repos/bitfield/script/commits/85fa6426e7dcadb70cca582f028445ceafb1963b",
+    "html_url": "https://github.com/bitfield/script/commit/85fa6426e7dcadb70cca582f028445ceafb1963b",
+    "comments_url": "https://api.github.com/repos/bitfield/script/commits/85fa6426e7dcadb70cca582f028445ceafb1963b/comments",
+    "author": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "committer": {
+      "login": "web-flow",
+      "id": 19864447,
+      "node_id": "MDQ6VXNlcjE5ODY0NDQ3",
+      "avatar_url": "https://avatars.githubusercontent.com/u/19864447?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/web-flow",
+      "html_url": "https://github.com/web-flow",
+      "followers_url": "https://api.github.com/users/web-flow/followers",
+      "following_url": "https://api.github.com/users/web-flow/following{/other_user}",
+      "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions",
+      "organizations_url": "https://api.github.com/users/web-flow/orgs",
+      "repos_url": "https://api.github.com/users/web-flow/repos",
+      "events_url": "https://api.github.com/users/web-flow/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/web-flow/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "parents": [
+      {
+        "sha": "7955ae56a26545e9f54ee707d190398559ba2841",
+        "url": "https://api.github.com/repos/bitfield/script/commits/7955ae56a26545e9f54ee707d190398559ba2841",
+        "html_url": "https://github.com/bitfield/script/commit/7955ae56a26545e9f54ee707d190398559ba2841"
+      },
+      {
+        "sha": "3418243ce0a037990ed3303b70b2f4511ec430ce",
+        "url": "https://api.github.com/repos/bitfield/script/commits/3418243ce0a037990ed3303b70b2f4511ec430ce",
+        "html_url": "https://github.com/bitfield/script/commit/3418243ce0a037990ed3303b70b2f4511ec430ce"
+      }
+    ]
+  },
+  {
+    "sha": "3418243ce0a037990ed3303b70b2f4511ec430ce",
+    "node_id": "C_kwDOCt91vdoAKDM0MTgyNDNjZTBhMDM3OTkwZWQzMzAzYjcwYjJmNDUxMWVjNDMwY2U",
+    "commit": {
+      "author": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-23T09:31:53Z"
+      },
+      "committer": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-23T09:31:53Z"
+      },
+      "message": "refactor",
+      "tree": {
+        "sha": "549df3edaccf5afc94b6089496b03cbb68677050",
+        "url": "https://api.github.com/repos/bitfield/script/git/trees/549df3edaccf5afc94b6089496b03cbb68677050"
+      },
+      "url": "https://api.github.com/repos/bitfield/script/git/commits/3418243ce0a037990ed3303b70b2f4511ec430ce",
+      "comment_count": 0,
+      "verification": {
+        "verified": false,
+        "reason": "unsigned",
+        "signature": null,
+        "payload": null
+      }
+    },
+    "url": "https://api.github.com/repos/bitfield/script/commits/3418243ce0a037990ed3303b70b2f4511ec430ce",
+    "html_url": "https://github.com/bitfield/script/commit/3418243ce0a037990ed3303b70b2f4511ec430ce",
+    "comments_url": "https://api.github.com/repos/bitfield/script/commits/3418243ce0a037990ed3303b70b2f4511ec430ce/comments",
+    "author": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "committer": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "parents": [
+      {
+        "sha": "caf0a29bf28531a1caf82c768a1348e9f8fbf990",
+        "url": "https://api.github.com/repos/bitfield/script/commits/caf0a29bf28531a1caf82c768a1348e9f8fbf990",
+        "html_url": "https://github.com/bitfield/script/commit/caf0a29bf28531a1caf82c768a1348e9f8fbf990"
+      }
+    ]
+  },
+  {
+    "sha": "caf0a29bf28531a1caf82c768a1348e9f8fbf990",
+    "node_id": "C_kwDOCt91vdoAKGNhZjBhMjliZjI4NTMxYTFjYWY4MmM3NjhhMTM0OGU5ZjhmYmY5OTA",
+    "commit": {
+      "author": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-22T18:48:25Z"
+      },
+      "committer": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-22T18:48:25Z"
+      },
+      "message": "Update README",
+      "tree": {
+        "sha": "99d2e875cac437fcc1aaacc649dd85785de234c9",
+        "url": "https://api.github.com/repos/bitfield/script/git/trees/99d2e875cac437fcc1aaacc649dd85785de234c9"
+      },
+      "url": "https://api.github.com/repos/bitfield/script/git/commits/caf0a29bf28531a1caf82c768a1348e9f8fbf990",
+      "comment_count": 0,
+      "verification": {
+        "verified": false,
+        "reason": "unsigned",
+        "signature": null,
+        "payload": null
+      }
+    },
+    "url": "https://api.github.com/repos/bitfield/script/commits/caf0a29bf28531a1caf82c768a1348e9f8fbf990",
+    "html_url": "https://github.com/bitfield/script/commit/caf0a29bf28531a1caf82c768a1348e9f8fbf990",
+    "comments_url": "https://api.github.com/repos/bitfield/script/commits/caf0a29bf28531a1caf82c768a1348e9f8fbf990/comments",
+    "author": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "committer": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "parents": [
+      {
+        "sha": "33444c3941ecca0b6749642c8150802a82214f01",
+        "url": "https://api.github.com/repos/bitfield/script/commits/33444c3941ecca0b6749642c8150802a82214f01",
+        "html_url": "https://github.com/bitfield/script/commit/33444c3941ecca0b6749642c8150802a82214f01"
+      }
+    ]
+  },
+  {
+    "sha": "33444c3941ecca0b6749642c8150802a82214f01",
+    "node_id": "C_kwDOCt91vdoAKDMzNDQ0YzM5NDFlY2NhMGI2NzQ5NjQyYzgxNTA4MDJhODIyMTRmMDE",
+    "commit": {
+      "author": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-22T17:53:28Z"
+      },
+      "committer": {
+        "name": "John Arundel",
+        "email": "john@bitfieldconsulting.com",
+        "date": "2022-03-22T17:53:28Z"
+      },
+      "message": "Migrate example programs to executable examples",
+      "tree": {
+        "sha": "97559517268ea225c557767cadcd3e18e333f1ed",
+        "url": "https://api.github.com/repos/bitfield/script/git/trees/97559517268ea225c557767cadcd3e18e333f1ed"
+      },
+      "url": "https://api.github.com/repos/bitfield/script/git/commits/33444c3941ecca0b6749642c8150802a82214f01",
+      "comment_count": 0,
+      "verification": {
+        "verified": false,
+        "reason": "unsigned",
+        "signature": null,
+        "payload": null
+      }
+    },
+    "url": "https://api.github.com/repos/bitfield/script/commits/33444c3941ecca0b6749642c8150802a82214f01",
+    "html_url": "https://github.com/bitfield/script/commit/33444c3941ecca0b6749642c8150802a82214f01",
+    "comments_url": "https://api.github.com/repos/bitfield/script/commits/33444c3941ecca0b6749642c8150802a82214f01/comments",
+    "author": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "committer": {
+      "login": "bitfield",
+      "id": 142262,
+      "node_id": "MDQ6VXNlcjE0MjI2Mg==",
+      "avatar_url": "https://avatars.githubusercontent.com/u/142262?v=4",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/bitfield",
+      "html_url": "https://github.com/bitfield",
+      "followers_url": "https://api.github.com/users/bitfield/followers",
+      "following_url": "https://api.github.com/users/bitfield/following{/other_user}",
+      "gists_url": "https://api.github.com/users/bitfield/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/bitfield/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/bitfield/subscriptions",
+      "organizations_url": "https://api.github.com/users/bitfield/orgs",
+      "repos_url": "https://api.github.com/users/bitfield/repos",
+      "events_url": "https://api.github.com/users/bitfield/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/bitfield/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "parents": [
+      {
+        "sha": "49b2e75023d8229bffd733eb0690a14f4a077904",
+        "url": "https://api.github.com/repos/bitfield/script/commits/49b2e75023d8229bffd733eb0690a14f4a077904",
+        "html_url": "https://github.com/bitfield/script/commit/49b2e75023d8229bffd733eb0690a14f4a077904"
+      }
+    ]
+  }
+]
diff --git a/testdata/releases.json b/testdata/releases.json
new file mode 100644
index 0000000..5e99c1f
--- /dev/null
+++ b/testdata/releases.json
@@ -0,0 +1,312 @@
+{
+  "url": "https://api.github.com/repos/mbarley333/blackjack/releases/51398991",
+  "assets_url": "https://api.github.com/repos/mbarley333/blackjack/releases/51398991/assets",
+  "upload_url": "https://uploads.github.com/repos/mbarley333/blackjack/releases/51398991/assets{?name,label}",
+  "html_url": "https://github.com/mbarley333/blackjack/releases/tag/v0.3.3",
+  "id": 51398991,
+  "author": {
+    "login": "mbarley333",
+    "id": 6832950,
+    "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+    "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/mbarley333",
+    "html_url": "https://github.com/mbarley333",
+    "followers_url": "https://api.github.com/users/mbarley333/followers",
+    "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+    "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+    "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+    "repos_url": "https://api.github.com/users/mbarley333/repos",
+    "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "node_id": "RE_kwDOF4c5ns4DEElP",
+  "tag_name": "v0.3.3",
+  "target_commitish": "main",
+  "name": "v0.3.3",
+  "draft": false,
+  "prerelease": false,
+  "created_at": "2021-10-14T23:29:24Z",
+  "published_at": "2021-10-14T23:30:16Z",
+  "assets": [
+    {
+      "url": "https://api.github.com/repos/mbarley333/blackjack/releases/assets/47018863",
+      "id": 47018863,
+      "node_id": "RA_kwDOF4c5ns4CzXNv",
+      "name": "blackjack_0.3.3_Darwin_arm64.tar.gz",
+      "label": "",
+      "uploader": {
+        "login": "mbarley333",
+        "id": 6832950,
+        "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/mbarley333",
+        "html_url": "https://github.com/mbarley333",
+        "followers_url": "https://api.github.com/users/mbarley333/followers",
+        "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+        "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+        "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+        "repos_url": "https://api.github.com/users/mbarley333/repos",
+        "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "content_type": "application/octet-stream",
+      "state": "uploaded",
+      "size": 741046,
+      "download_count": 2,
+      "created_at": "2021-10-14T23:30:17Z",
+      "updated_at": "2021-10-14T23:30:21Z",
+      "browser_download_url": "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/blackjack_0.3.3_Darwin_arm64.tar.gz"
+    },
+    {
+      "url": "https://api.github.com/repos/mbarley333/blackjack/releases/assets/47018868",
+      "id": 47018868,
+      "node_id": "RA_kwDOF4c5ns4CzXN0",
+      "name": "blackjack_0.3.3_Darwin_x86_64.tar.gz",
+      "label": "",
+      "uploader": {
+        "login": "mbarley333",
+        "id": 6832950,
+        "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/mbarley333",
+        "html_url": "https://github.com/mbarley333",
+        "followers_url": "https://api.github.com/users/mbarley333/followers",
+        "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+        "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+        "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+        "repos_url": "https://api.github.com/users/mbarley333/repos",
+        "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "content_type": "application/octet-stream",
+      "state": "uploaded",
+      "size": 739976,
+      "download_count": 1,
+      "created_at": "2021-10-14T23:30:18Z",
+      "updated_at": "2021-10-14T23:30:21Z",
+      "browser_download_url": "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/blackjack_0.3.3_Darwin_x86_64.tar.gz"
+    },
+    {
+      "url": "https://api.github.com/repos/mbarley333/blackjack/releases/assets/47018870",
+      "id": 47018870,
+      "node_id": "RA_kwDOF4c5ns4CzXN2",
+      "name": "blackjack_0.3.3_Linux_arm64.tar.gz",
+      "label": "",
+      "uploader": {
+        "login": "mbarley333",
+        "id": 6832950,
+        "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/mbarley333",
+        "html_url": "https://github.com/mbarley333",
+        "followers_url": "https://api.github.com/users/mbarley333/followers",
+        "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+        "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+        "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+        "repos_url": "https://api.github.com/users/mbarley333/repos",
+        "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "content_type": "application/octet-stream",
+      "state": "uploaded",
+      "size": 631211,
+      "download_count": 1,
+      "created_at": "2021-10-14T23:30:18Z",
+      "updated_at": "2021-10-14T23:30:21Z",
+      "browser_download_url": "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/blackjack_0.3.3_Linux_arm64.tar.gz"
+    },
+    {
+      "url": "https://api.github.com/repos/mbarley333/blackjack/releases/assets/47018871",
+      "id": 47018871,
+      "node_id": "RA_kwDOF4c5ns4CzXN3",
+      "name": "blackjack_0.3.3_Linux_i386.tar.gz",
+      "label": "",
+      "uploader": {
+        "login": "mbarley333",
+        "id": 6832950,
+        "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/mbarley333",
+        "html_url": "https://github.com/mbarley333",
+        "followers_url": "https://api.github.com/users/mbarley333/followers",
+        "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+        "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+        "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+        "repos_url": "https://api.github.com/users/mbarley333/repos",
+        "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "content_type": "application/octet-stream",
+      "state": "uploaded",
+      "size": 659421,
+      "download_count": 1,
+      "created_at": "2021-10-14T23:30:18Z",
+      "updated_at": "2021-10-14T23:30:21Z",
+      "browser_download_url": "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/blackjack_0.3.3_Linux_i386.tar.gz"
+    },
+    {
+      "url": "https://api.github.com/repos/mbarley333/blackjack/releases/assets/47018867",
+      "id": 47018867,
+      "node_id": "RA_kwDOF4c5ns4CzXNz",
+      "name": "blackjack_0.3.3_Linux_x86_64.tar.gz",
+      "label": "",
+      "uploader": {
+        "login": "mbarley333",
+        "id": 6832950,
+        "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/mbarley333",
+        "html_url": "https://github.com/mbarley333",
+        "followers_url": "https://api.github.com/users/mbarley333/followers",
+        "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+        "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+        "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+        "repos_url": "https://api.github.com/users/mbarley333/repos",
+        "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "content_type": "application/octet-stream",
+      "state": "uploaded",
+      "size": 678298,
+      "download_count": 1,
+      "created_at": "2021-10-14T23:30:18Z",
+      "updated_at": "2021-10-14T23:30:21Z",
+      "browser_download_url": "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/blackjack_0.3.3_Linux_x86_64.tar.gz"
+    },
+    {
+      "url": "https://api.github.com/repos/mbarley333/blackjack/releases/assets/47018866",
+      "id": 47018866,
+      "node_id": "RA_kwDOF4c5ns4CzXNy",
+      "name": "blackjack_0.3.3_Windows_i386.tar.gz",
+      "label": "",
+      "uploader": {
+        "login": "mbarley333",
+        "id": 6832950,
+        "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/mbarley333",
+        "html_url": "https://github.com/mbarley333",
+        "followers_url": "https://api.github.com/users/mbarley333/followers",
+        "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+        "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+        "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+        "repos_url": "https://api.github.com/users/mbarley333/repos",
+        "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "content_type": "application/octet-stream",
+      "state": "uploaded",
+      "size": 724109,
+      "download_count": 1,
+      "created_at": "2021-10-14T23:30:18Z",
+      "updated_at": "2021-10-14T23:30:21Z",
+      "browser_download_url": "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/blackjack_0.3.3_Windows_i386.tar.gz"
+    },
+    {
+      "url": "https://api.github.com/repos/mbarley333/blackjack/releases/assets/47018869",
+      "id": 47018869,
+      "node_id": "RA_kwDOF4c5ns4CzXN1",
+      "name": "blackjack_0.3.3_Windows_x86_64.tar.gz",
+      "label": "",
+      "uploader": {
+        "login": "mbarley333",
+        "id": 6832950,
+        "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/mbarley333",
+        "html_url": "https://github.com/mbarley333",
+        "followers_url": "https://api.github.com/users/mbarley333/followers",
+        "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+        "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+        "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+        "repos_url": "https://api.github.com/users/mbarley333/repos",
+        "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "content_type": "application/octet-stream",
+      "state": "uploaded",
+      "size": 730786,
+      "download_count": 1,
+      "created_at": "2021-10-14T23:30:18Z",
+      "updated_at": "2021-10-14T23:30:21Z",
+      "browser_download_url": "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/blackjack_0.3.3_Windows_x86_64.tar.gz"
+    },
+    {
+      "url": "https://api.github.com/repos/mbarley333/blackjack/releases/assets/47018864",
+      "id": 47018864,
+      "node_id": "RA_kwDOF4c5ns4CzXNw",
+      "name": "checksums.txt",
+      "label": "",
+      "uploader": {
+        "login": "mbarley333",
+        "id": 6832950,
+        "node_id": "MDQ6VXNlcjY4MzI5NTA=",
+        "avatar_url": "https://avatars.githubusercontent.com/u/6832950?v=4",
+        "gravatar_id": "",
+        "url": "https://api.github.com/users/mbarley333",
+        "html_url": "https://github.com/mbarley333",
+        "followers_url": "https://api.github.com/users/mbarley333/followers",
+        "following_url": "https://api.github.com/users/mbarley333/following{/other_user}",
+        "gists_url": "https://api.github.com/users/mbarley333/gists{/gist_id}",
+        "starred_url": "https://api.github.com/users/mbarley333/starred{/owner}{/repo}",
+        "subscriptions_url": "https://api.github.com/users/mbarley333/subscriptions",
+        "organizations_url": "https://api.github.com/users/mbarley333/orgs",
+        "repos_url": "https://api.github.com/users/mbarley333/repos",
+        "events_url": "https://api.github.com/users/mbarley333/events{/privacy}",
+        "received_events_url": "https://api.github.com/users/mbarley333/received_events",
+        "type": "User",
+        "site_admin": false
+      },
+      "content_type": "text/plain; charset=utf-8",
+      "state": "uploaded",
+      "size": 714,
+      "download_count": 1,
+      "created_at": "2021-10-14T23:30:17Z",
+      "updated_at": "2021-10-14T23:30:18Z",
+      "browser_download_url": "https://github.com/mbarley333/blackjack/releases/download/v0.3.3/checksums.txt"
+    }
+  ],
+  "tarball_url": "https://api.github.com/repos/mbarley333/blackjack/tarball/v0.3.3",
+  "zipball_url": "https://api.github.com/repos/mbarley333/blackjack/zipball/v0.3.3",
+  "body": "## Changelog\n\n1fe693b Merge pull request #56 from mbarley333/fix_gethint\nf7efabf remove print statements\n\n"
+}