Skip to content

Commit

Permalink
Merge pull request #140 from danielgtaylor/shorthandv2
Browse files Browse the repository at this point in the history
feat: switch to shorthand v2
  • Loading branch information
danielgtaylor committed Nov 9, 2022
2 parents f733d40 + 829559e commit 8829b97
Show file tree
Hide file tree
Showing 15 changed files with 672 additions and 191 deletions.
4 changes: 2 additions & 2 deletions cli/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"strings"

jmespath "github.com/danielgtaylor/go-jmespath-plus"
"github.com/danielgtaylor/shorthand"
"github.com/danielgtaylor/shorthand/v2"
"github.com/google/shlex"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
Expand Down Expand Up @@ -99,7 +99,7 @@ export EDITOR="vim"`)
var modified interface{} = data

if len(args) > 0 {
modified, err = shorthand.ParseAndBuild(req.URL.Path, strings.Join(args, " "), modified.(map[string]interface{}))
modified, err = shorthand.Unmarshal(strings.Join(args, " "), shorthand.ParseOptions{EnableFileInput: true, EnableObjectDetection: true}, modified)
panicOnErr(err)
}

Expand Down
12 changes: 7 additions & 5 deletions cli/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"github.com/alecthomas/chroma/quick"
"github.com/alecthomas/chroma/styles"
"github.com/charmbracelet/glamour/ansi"
jmespath "github.com/danielgtaylor/go-jmespath-plus"
"github.com/danielgtaylor/shorthand/v2"
"github.com/ghodss/yaml"
"github.com/spf13/viper"
"golang.org/x/term"
Expand Down Expand Up @@ -421,10 +421,12 @@ func (f *DefaultFormatter) Format(resp Response) error {
}

if filter != "" {
// JMESPath can't support maps with arbitrary key types, so we convert
// to map[string]interface{} before filtering.
data = makeJSONSafe(data, true)
result, err := jmespath.Search(filter, data)
opts := shorthand.GetOptions{}
if enableVerbose {
opts.DebugLogger = LogDebug
}

result, _, err := shorthand.GetPath(filter, data, opts)

if err != nil {
return err
Expand Down
7 changes: 5 additions & 2 deletions cli/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"os"
"strings"

"github.com/danielgtaylor/shorthand"
"github.com/danielgtaylor/shorthand/v2"
yaml "gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -36,7 +36,10 @@ func GetBody(mediaType string, args []string) (string, error) {
}
}

input, err := shorthand.GetInput(args)
input, _, err := shorthand.GetInput(args, shorthand.ParseOptions{
EnableFileInput: true,
EnableObjectDetection: true,
})
if err != nil {
return "", err
}
Expand Down
6 changes: 4 additions & 2 deletions cli/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,13 @@ var SchemaLexer = lexers.Register(chroma.MustNewLazyLexer(
Type: chroma.ByGroups(chroma.Text, chroma.Keyword),
},
{
Pattern: `([^:]+)(:)([^ )]+)`,
Pattern: `([^: )]+)(:)([^ )]+)`,
Type: chroma.ByGroups(chroma.String, chroma.Text, chroma.Text),
},
{
Pattern: `[^\n]*`, Type: chroma.Text, Mutator: chroma.Pop(1),
Pattern: `[^\n]*`,
Type: chroma.Text,
Mutator: chroma.Pop(1),
},
},
"row": {
Expand Down
26 changes: 19 additions & 7 deletions cli/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"time"

"github.com/danielgtaylor/shorthand/v2"
"github.com/spf13/viper"
)

Expand Down Expand Up @@ -257,26 +258,34 @@ type Response struct {
}

// Map returns a map representing this response matching the encoded JSON.
func (r Response) Map() map[string]interface{} {
links := map[string][]map[string]interface{}{}
func (r Response) Map() map[string]any {
links := map[string]any{}

for rel, list := range r.Links {
if _, ok := links[rel]; !ok {
links[rel] = []map[string]interface{}{}
lrel := links[rel]
if lrel == nil {
lrel = []any{}
}

for _, l := range list {
links[rel] = append(links[rel], map[string]interface{}{
lrel = append(lrel.([]any), map[string]any{
"rel": l.Rel,
"uri": l.URI,
})
}

links[rel] = lrel
}

headers := map[string]any{}
for k, v := range r.Headers {
headers[k] = v
}

return map[string]interface{}{
return map[string]any{
"proto": r.Proto,
"status": r.Status,
"headers": r.Headers,
"headers": headers,
"links": links,
"body": r.Body,
}
Expand Down Expand Up @@ -429,6 +438,9 @@ func MakeRequestAndFormat(req *http.Request) {
}

if err := Formatter.Format(parsed); err != nil {
if e, ok := err.(shorthand.Error); ok {
panic(e.Pretty())
}
panic(err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ restish post api.rest.sh \
platform.about.mission: Make APIs simple and intuitive, \
platform.about.homepage: httpie.io, \
platform.about.stars: 54000, \
platform.apps: Terminal, Desktop, Web, Mobile
platform.apps: [Terminal, Desktop, Web, Mobile]
```

## Getting Header Values
Expand All @@ -143,5 +143,5 @@ https --headers api.rest.sh | grep Content-Length | cut -d':' -d' ' -f2
Restish Example:

```bash
restish api.rest.sh -f 'headers."Content-Length"' -r
restish api.rest.sh -f 'headers.Content-Length' -r
```
22 changes: 12 additions & 10 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ You can install in one of three ways: Homebrew tap, GitHub release, or via `go g
If you have [Homebrew](https://brew.sh/) then install via the official tap:

```bash
# Add the tap
$ brew tap danielgtaylor/restish

# Install the executable
$ brew install restish
# Install directly through the Homebrew tap
$ brew install danielgtaylor/restish/restish
```

If you don't have Homebrew, you can grab a [release](https://github.com/danielgtaylor/restish/releases) for your platform and manually copy the executable to the right location (e.g. `/usr/local/bin/restish`), otherwise if you have Go installed:
Expand All @@ -30,7 +27,7 @@ You can confirm the installation worked by trying to run Restish:
$ restish --version
```

?> If using `zsh` as your shell (the default on macOS), you should set `alias restish="noglob restish"` in your `~/.zshrc` to prevent it from trying to handle `?` in URLs and `[]` in shorthand input!
?> If using `zsh` as your shell (the default on macOS), you should set `alias restish="noglob restish"` in your `~/.zshrc` to prevent it from trying to handle `?` in URLs and `[]` in shorthand input. Alternatively you can use quotes around your inputs.

## Basic Usage

Expand Down Expand Up @@ -97,7 +94,7 @@ $ restish post api.rest.sh <input.json
$ restish post api.rest.sh name: Kari, tags[]: admin
```

Read more about [CLI Shorthand](/shorthand.md). Headers and query params can also be set via environment variables, for example:
Read more about [CLI Shorthand](/shorthand.md). Headers and query params can also be set via environment variables by prefixing with `RSH_`, for example:

```bash
# Set via env vars
Expand All @@ -121,11 +118,11 @@ $ restish edit -i api.rest.sh/types

To use interactive mode you must have the `VISUAL` or `EDITOR` environment variable set to an editor, for example `export VISUAL="code --wait"` for VSCode.

Editing resources will make use of [conditional requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests) if any relevant headers are found on the `GET` response.
Editing resources will make use of [conditional requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests) if any relevant headers are found on the `GET` response. For example, if an `ETag` header is present in the `GET` response then an `If-Match` header will be send on the `PUT` to prevent performing the write operation if the resource was modified by someone else while you are editing.

### Output Filtering

By default, you will see the entire response as output. Restish includes built-in filtering using [JMESPath Plus](https://github.com/danielgtaylor/go-jmespath-plus#readme) which enables you to filter & project the response data. Using a filter automatically enables JSON output mode. Here are some basic examples:
By default, you will see the entire response as output. Restish includes built-in filtering using [Shorthand queries]() which enable you to filter & project the response data. Using a filter automatically enables JSON output mode and only prints the result of the filter expression. Here are some basic examples:

```bash
# Get social media profiles from a JSON Resume:
Expand All @@ -146,7 +143,7 @@ $ restish api.rest.sh/example -f body.basics.profiles
]

# Advanced filtering example:
$ restish api.rest.sh/example -f 'body.volunteer[?organization==`"Restish"`]|[0].{name, startDate, summary}'
$ restish api.rest.sh/example -f 'body.volunteer[organization.lower == restish]|[0].{organization, startDate, summary}'
{
"organization": "Restish",
"startDate": "2018-09-29T00:00:00Z",
Expand Down Expand Up @@ -180,6 +177,9 @@ Each profile can have a number of preset headers or query params, a type of auth
Getting started registering an API is easy and uses an interactive prompt to set up profiles, auth, etc. At a minimum you must provide a short nickname and a base URL:

```bash
# How to register a new API
$ restish api configure $SHORT_NAME $URL

# Register a new API called `example`
$ restish api configure example https://api.rest.sh
```
Expand Down Expand Up @@ -270,6 +270,8 @@ $ restish completion zsh --help
$ restish completion powershell --help
```

If using Homebrew, you may need one additional step to [include the Homebrew completions path](https://docs.brew.sh/Shell-Completion) for your shell.

Once set up, you can use the `tab` key to discover API commands. For example:

```bash
Expand Down
9 changes: 6 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@
}

.token.variable,
.token.binary {
.token.binary,
.token.function {
color: #ffd7d7;
}

Expand Down Expand Up @@ -261,14 +262,16 @@
},
},
string: {
pattern: /("(?:\\.|[^\\"\r\n])*"(?!\s*:))|('(?:\\.|[^\\'\r\n])*'(?!\s*:))/,
// pattern: /("(?:\\.|[^\\"\r\n])*"(?!\s*:))|('(?:\\.|[^\\'\r\n])*'(?!\s*:))/,
pattern: /("(?:\\.|[^\\"\r\n])*"(?!\s*:))/,
greedy: true,
},
keypress: /<\S+>/,
property: /[A-Za-z0-9.\[\]-]+(?=:[a-z0-9. _-])/,
property: /[A-Za-z0-9.-]+(?=[:[{][a-z0-9.^ _-\}[\]])/,
number: /\b[0-9]+(\.[0-9]+)?/,
boolean: /\b(?:true|false)\b/i,
null: /\bnull\b/i,
function: /contains|startsWith/,
keyword: /restish|<|[|]|\b(for|do|done)(?!\/)\b/,
};
</script>
Expand Down
8 changes: 4 additions & 4 deletions docs/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ $ restish -H Header1:val1 -H Header2:val2 api.rest.sh

## Request Body

A request body can be set in two ways for requests that support bodies (e.g. `POST` / `PUT` / `PATCH`):
A request body can be set in two ways (or a combination of both) for requests that support bodies (e.g. `POST` / `PUT` / `PATCH`):

1. Standard input
2. CLI shorthand
Expand All @@ -49,7 +49,7 @@ $ echo '{"name": "hello"}' | restish put api.rest.sh
The [CLI Shorthand](shorthand.md) is a convenient way of providing structured data on the commandline. It is a JSON-like syntax that enables you to easily create nested structured data. For example:

```bash
$ restish post api.rest.sh foo.bar[].baz: 1, .hello: world
$ restish post api.rest.sh 'foo.bar[]{baz: 1, hello: world}'
```

Will send the following request:
Expand All @@ -71,7 +71,7 @@ Host: api.rest.sh
}
```

The shorthand supports nested objects, arrays, automatic type coercion, context-aware backreferences, and loading data from files. See the [CLI Shorthand Syntax](shorthand.md) for more info.
The shorthand supports nested objects, arrays, automatic type coercion, and loading data from files. See the [CLI Shorthand Syntax](shorthand.md) for more info.

### Combined Body Input

Expand All @@ -85,4 +85,4 @@ $ restish post api.rest.sh <template.json id: test2, tags[]: group1

If you have a known small set of fields that need to change between calls, this makes it easy to do so without large complex commands.

?> Hint: want to replace an array? Use something like `value: null, value[]: item` to first empty the array, then start building it up again.
?> Hint: want to replace an array? Use something like `value: [item]` rather than appending.
21 changes: 7 additions & 14 deletions docs/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,28 +131,21 @@ $ restish -o json api.rest.sh/images

## Filtering & Projection

Restish includes JMESPath Plus, which includes all of [JMESPath](https://jmespath.org/) plus some [additional enhancements](https://github.com/danielgtaylor/go-jmespath-plus#readme). If you've ever used the [AWS CLI](https://aws.amazon.com/cli/), then you've likely used JMESPath. It's a language for filtering and projecting the response value that's useful for massaging the response data for scripts.
Restish includes basic response filtering functionality through the [Shorthand Query Syntax](shorthand.md#Querying). It's a language for filtering and projecting the response value that's useful for paring down and massaging the response data for scripts.

The response format described above is used as the input, so don't forget the `body` prefix when accessing body members!

```bash
# Print out request headers
$ restish api.rest.sh/images -f "headers"
$ restish api.rest.sh/images -f headers

# Filter results to just the names
$ restish api.rest.sh/images -f "body[].{name}"
$ restish api.rest.sh/images -f 'body[].{name}'

# Get all `url` fields recursively from a response that are from Github
$ restish api.rest.sh/example -f "..url|[?starts_with(@, 'https://github')]"

# Pivot data, e.g. group/sort company names by title
$ restish api.rest.sh/example -f "pivot(body.work, &position, &name)"
$ restish api.rest.sh/example -f '..url|[@ contains github]'
```

See the JMESPath documentation for more information and examples.

!> Warning: structured data from binary formats like CBOR may be converted to its JSON equivalent before applying JMESPath filters. For example, a byte slice and a date would both be treated as strings.

## Raw Mode

Raw mode, when enabled, will remove JSON formatting from the filtered output if the result matches one of the following:
Expand All @@ -164,15 +157,15 @@ For example:

```bash
# Normal mode
$ restish api.rest.sh/images -f body[0].self
$ restish api.rest.sh/images -f 'body[0].self'
"/images/jpeg"

# Raw mode strips the quotes
$ restish api.rest.sh/images -f body[0].self -r
$ restish api.rest.sh/images -f 'body[0].self' -r
/images/jpeg

# It also works with arrays
$ restish api.rest.sh/images -f body[].self -r
$ restish api.rest.sh/images -f 'body[].self' -r
/images/jpeg
/images/webp
/images/gif
Expand Down
Loading

0 comments on commit 8829b97

Please sign in to comment.