Repository: danielgtaylor/restish
Pull request #162 (2023-01-13 22:11:51)

feat: bulk list supports shorthand query filters
This updates the `restish bulk list` operation to support shorthand query filters to display information from each matched file that is listed. This enables quickly looking up information across all resources, similar to how you could pass each filename to a tool like `jq` to query and print out some information for each file. The advantage here is that it is built-in using the same language as the rest of Restish.


Patch:
diff --git a/bulk/commands.go b/bulk/commands.go
index 23a65bd..ce55a2e 100644
--- a/bulk/commands.go
+++ b/bulk/commands.go
@@ -13,6 +13,7 @@ import (
 	"github.com/danielgtaylor/mexpr"
 	"github.com/danielgtaylor/restish/cli"
 	"github.com/danielgtaylor/restish/openapi"
+	"github.com/danielgtaylor/shorthand/v2"
 	"github.com/hexops/gotextdiff"
 	"github.com/hexops/gotextdiff/myers"
 	"github.com/hexops/gotextdiff/span"
@@ -347,7 +348,7 @@ func Init(cmd *cobra.Command) {
 
 	list := cobra.Command{
 		GroupID: "info",
-		Use:     "list [--match expr]",
+		Use:     "list [--match expr] [-f filter]",
 		Aliases: []string{"ls"},
 		Short:   "List checked out files",
 		Args:    cobra.NoArgs,
@@ -355,6 +356,23 @@ func Init(cmd *cobra.Command) {
 		Run: func(cmd *cobra.Command, args []string) {
 			match, _ := cmd.Flags().GetString("match")
 			for _, path := range collectFiles(mustLoadMeta(), args, match, false) {
+				if filter := viper.GetString("rsh-filter"); filter != "" {
+					var content any
+					b, err := afero.ReadFile(afs, path)
+					panicOnErr(err)
+					if err := json.Unmarshal(b, &content); err == nil {
+						if res, _, err := shorthand.GetPath(filter, content, shorthand.GetOptions{}); err == nil && !isFalsey(res) {
+							fmt.Fprintln(cli.Stdout, path)
+							b, _ := json.MarshalIndent(res, "", "  ")
+							if viper.GetBool("color") {
+								b, _ = cli.Highlight("readable", b)
+							}
+							fmt.Fprintln(cli.Stdout, string(b))
+							continue
+						}
+					}
+				}
+
 				fmt.Fprintln(cli.Stdout, path)
 			}
 		},
diff --git a/docs/bulk.md b/docs/bulk.md
index 2cc800d..d223e35 100644
--- a/docs/bulk.md
+++ b/docs/bulk.md
@@ -66,6 +66,17 @@ recent_ratings > 5
 ...............^^^^
 ```
 
+Additionally, you can use the `-f` flag to apply a [Shorthand Query](./shorthand.md#querying) filter to each matched file and print out the result, enabling a quick way to get specific values from a set of matched files:
+
+```bash
+# Get the most recent rating of each matched book
+$ rb list -m 'rating_average > 4.7' -f 'recent_ratings[0].rating'
+letters-from-an-astrophysicist.json
+4.9
+the-food-lab.json
+5
+```
+
 Next, let's make some changes!
 
 ```bash
@@ -120,11 +131,11 @@ Initialize a new bulk checkout. The response should be a list of resources which
 
 Alias: `i`
 
-| Param / Option      | Description & Example                                                                                                                                                          |
-| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `URL`               | The URL to list resources<br/>Example: `api.rest.sh/books`                                                                                                                     |
-| `--rsh-filter`/`-f` | Filter the response via [Shorthand Query](./shorthand.md#querying)<br/>Example: `-f 'body.{id, version: last_modified_dt}'`                                                    |
-| `--url-template`    | Template string to build URLs from list response items. If a filter is passed, it is processed _before_ rendering the URL template.<br/>Example: `--url-template='/items/{id}` |
+| Param / Option       | Description & Example                                                                                                                                                          |
+| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `URL`                | The URL to list resources<br/>Example: `api.rest.sh/books`                                                                                                                     |
+| `-f`, `--rsh-filter` | Filter the response via [Shorthand Query](./shorthand.md#querying)<br/>Example: `-f 'body.{id, version: last_modified_dt}'`                                                    |
+| `--url-template`     | Template string to build URLs from list response items. If a filter is passed, it is processed _before_ rendering the URL template.<br/>Example: `--url-template='/items/{id}` |
 
 #### Automatically Recognized Fields
 
@@ -168,16 +179,17 @@ $ rb init example.com/items \
 ### List
 
 ```bash
-restish bulk list [--match expr]
+restish bulk list [--match expr] [-f filter]
 ```
 
 List checked out resources, optionally with filtering via expressions.
 
 Alias: `ls`
 
-| Param / Option  | Description & Example                                                                                                       |
-| --------------- | --------------------------------------------------------------------------------------------------------------------------- |
-| `-m`, `--match` | Match resources using [mexpr](https://github.com/danielgtaylor/mexpr) expressions<br/>Example: `-m 'rating_average >= 4.8'` |
+| Param / Option       | Description & Example                                                                                                                 |
+| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
+| `-m`, `--match`      | Match resources using [mexpr](https://github.com/danielgtaylor/mexpr) expressions<br/>Example: `-m 'rating_average >= 4.8'`           |
+| `-f`, `--rsh-filter` | Filter each resource via [Shorthand Query](./shorthand.md#querying) and print the result<br/>Example: `-f 'recent_ratings[0].rating'` |
 
 ?> Match expressions show any resource whose expression result is "truthy" (meaning a non-zero scalar or non-empty map/slice). `false`, `0`, `""`, `[]`, and `{}` are considered "falsey".
 


Test patch:
diff --git a/bulk/commands_test.go b/bulk/commands_test.go
index 6e8958c..ddc33bf 100644
--- a/bulk/commands_test.go
+++ b/bulk/commands_test.go
@@ -160,6 +160,16 @@ func TestWorkflow(t *testing.T) {
 	require.Contains(t, out, "b/items/b1.json")
 	require.Contains(t, out, "c/items/c1.json")
 
+	// List with filter to show contents
+	// ---------------------------------
+	gock.Flush()
+	out, err = run("bulk", "list", "-m", "", "-f", "id")
+	require.NoError(t, err)
+	require.Contains(t, out, `"a1"`)
+	require.Contains(t, out, `"a2"`)
+	require.Contains(t, out, `"b1"`)
+	require.Contains(t, out, `"c1"`)
+
 	// Remote files changed
 	// --------------------
 	gock.Flush()