Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: add interactive mode #24

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package sdkclient

import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"strings"

"github.com/Songmu/prompter"
"github.com/mattn/go-shellwords"
)

func (c *CLI) runFilter(ctx context.Context, src io.Reader, title string) (string, error) {
command := c.FilterCommand
if command == "" {
return runInternalFilter(ctx, src, title)
}
var f *exec.Cmd
if strings.Contains(command, " ") {
f = exec.CommandContext(ctx, "sh", "-c", command)
} else {
f = exec.CommandContext(ctx, command)
}
f.Stderr = os.Stderr
p, _ := f.StdinPipe()
go func() {
io.Copy(p, src)
p.Close()
}()
b, err := f.Output()
if err != nil {
return "", fmt.Errorf("failed to execute filter command: %w", err)
}
return string(bytes.TrimRight(b, "\r\n")), nil
}

func runInternalFilter(ctx context.Context, src io.Reader, title string) (string, error) {
var items []string
s := bufio.NewScanner(src)
for s.Scan() {
fmt.Println(s.Text())
items = append(items, strings.Fields(s.Text())[0])
}

result := make(chan string)
go func() {
var input string
for {
input = prompter.Prompt("Enter "+title, "")
if input == "" {
select {
case <-ctx.Done():
return
default:
}
continue
}
var found []string
for _, item := range items {
item := item
if item == input {
found = []string{item}
break
} else if strings.HasPrefix(item, input) {
found = append(found, item)
}
}

switch len(found) {
case 0:
fmt.Printf("no such item %s\n", input)
case 1:
fmt.Printf("%s=%s\n", title, found[0])
result <- found[0]
return
default:
fmt.Printf("%s is ambiguous\n", input)
}
}
}()
select {
case <-ctx.Done():
return "", ErrAborted
case r := <-result:
return r, nil
}
}

func (c *CLI) editInput(ctx context.Context, helpURL string) error {
tmp, err := os.CreateTemp("", "awslim-*.jsonnet")
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
}
input := `// ` + helpURL + "\n" + c.Input + "\n"
if _, err := tmp.WriteString(input); err != nil {
return fmt.Errorf("failed to write input to temp file: %w", err)
}
tmp.Close()

editor := c.EditorCommand
if editor == "" {
editor = os.Getenv("EDITOR")
}
if editor == "" {
return fmt.Errorf("EDITOR is not set")
}
commands, err := shellwords.Parse(editor)
if err != nil {
return fmt.Errorf("failed to parse editor command: %w", err)
}
commands = append(commands, tmp.Name())
cmd := exec.CommandContext(ctx, commands[0], commands[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run editor: %w", err)
}
c.Input = tmp.Name()
return nil
}
11 changes: 7 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ module github.com/fujiwara/awslim
go 1.21.0

require (
github.com/Songmu/prompter v0.5.1
github.com/alecthomas/kong v0.9.0
github.com/aws/aws-sdk-go-v2 v1.27.2
github.com/aws/aws-sdk-go-v2 v1.28.0
github.com/aws/aws-sdk-go-v2/config v1.27.14
github.com/goccy/go-yaml v1.11.3
github.com/google/go-jsonnet v0.20.0
github.com/jmespath/go-jmespath v0.4.0
github.com/mattn/go-shellwords v1.0.12
)

require (
github.com/aws/aws-sdk-go-v2/credentials v1.17.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.8 // indirect
Expand All @@ -27,6 +29,7 @@ require (
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
Expand Down
22 changes: 14 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
github.com/Songmu/prompter v0.5.1 h1:IAsttKsOZWSDw7bV1mtGn9TAmLFAjXbp9I/eYmUUogo=
github.com/Songmu/prompter v0.5.1/go.mod h1:CS3jEPD6h9IaLaG6afrl1orTgII9+uDWuw95dr6xHSw=
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA=
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8=
github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2 v1.28.0 h1:ne6ftNhY0lUvlazMUQF15FF6NH80wKmPRFG7g2q6TCw=
github.com/aws/aws-sdk-go-v2 v1.28.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2/config v1.27.14 h1:QOg8Ud53rrmdjBHX080AaYUBhG2ER28kP/yjE7afF/0=
github.com/aws/aws-sdk-go-v2/config v1.27.14/go.mod h1:CLgU27opbIwnjwH++zQPvF4qsEIqviKL6l8b1AtRImc=
github.com/aws/aws-sdk-go-v2/credentials v1.17.14 h1:0y1IAEldTO2ZA3Lcq7u7y4Q2tUQlB3At2LZQijUHu3U=
github.com/aws/aws-sdk-go-v2/credentials v1.17.14/go.mod h1:En2zXCfDZJgtbp2UnzHDgKMz+mSRc4pA3Ka+jxoJvaA=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.2 h1:HTAQSEibYaSioHzjOQssUJnE8itwVP9SzmdR6lqC38g=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.2/go.mod h1:NjUtmUEIimOc5tPw//xqKNK/spUqCTSbxjwzCrnsj8U=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.10 h1:LZIUb8sQG2cb89QaVFtMSnER10gyKkqU1k3hP3g9das=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.10/go.mod h1:BRIqay//vnIOCZjoXWSLffL2uzbtxEmnSlfbvVh7Z/4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.10 h1:HY7CXLA0GiQUo3WYxOP7WYkLcwvRX4cLPf5joUcrQGk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.10/go.mod h1:kfRBSxRa+I+VyON7el3wLZdrO91oxUxEwdAaWgFqN90=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.12 h1:kO2J7WMroF/OTHN9WTcUtMjPhJ7ZoNxx0dwv6UCXQgY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.12/go.mod h1:mrNxrjYvXaSjZe5fkKaWgDnOQ6BExLn/7Ru9OpRsMPY=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.7 h1:sdPpNCoUijc0ntu024ZdjrXh3mB9rud5SjmE7djIfK4=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.7/go.mod h1:8RMeDMFTkkDQ5LvaaAykdkNVVR0eQxGWm8CD6uBvd1M=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.1 h1:/vljM1ZswUEIRHWVxEqDhLzOSGmDcstW2zeTt23Ipf0=
Expand Down Expand Up @@ -59,6 +61,8 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
Expand All @@ -71,6 +75,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
48 changes: 41 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
Expand All @@ -25,6 +26,8 @@ type ClientMethod func(context.Context, *clientMethodParam) (any, error)

var ErrDryRun = fmt.Errorf("dry-run mode")

var ErrAborted = errors.New("Aborted")

type CLI struct {
Service string `arg:"" help:"service name" default:""`
Method string `arg:"" help:"method name" default:""`
Expand All @@ -41,6 +44,10 @@ type CLI struct {
Strict bool `name:"strict" help:"strict input JSON unmarshaling" default:"true" negatable:"true"`
FollowNext string `short:"f" help:"OutputField=InputField format. follow the next token." default:""`

Interactive bool `short:"I" help:"interactive mode"`
FilterCommand string `short:"F" help:"filter command to select an item" env:"AWSLIM_FILTER"`
EditorCommand string `short:"E" help:"editor command to edit input JSON" env:"AWSLIM_EDITOR"`

DryRun bool `short:"n" help:"dry-run mode"`
Version bool `short:"v" help:"show version"`

Expand Down Expand Up @@ -83,8 +90,15 @@ func (c *CLI) CallMethod(ctx context.Context) error {
if !ok {
return fmt.Errorf("unknown function %s", key)
}
helpURL := fmt.Sprintf("https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/%s", key)

if c.Interactive {
if err := c.editInput(ctx, helpURL); err != nil {
return err
}
}
if c.Input == "help" {
fmt.Fprintf(c.w, "See https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/%s\n", key)
fmt.Fprintln(c.w, "See "+helpURL)
return nil
}

Expand Down Expand Up @@ -245,7 +259,7 @@ func (c *CLI) clientMethodParam(ctx context.Context) (*clientMethodParam, error)
return p, nil
}

func (c *CLI) ListMethods(_ context.Context) error {
func (c *CLI) ListMethods(ctx context.Context) error {
methods := make([]string, 0)
if m, ok := clientMethods[c.Service]; !ok {
return fmt.Errorf("unknown service %s", c.Service)
Expand All @@ -255,22 +269,42 @@ func (c *CLI) ListMethods(_ context.Context) error {
}
}
sort.Strings(methods)
buf := bytes.NewBuffer(nil)
for _, name := range methods {
fmt.Fprintln(c.w, name)
fmt.Fprintln(buf, name)
}
return nil
if c.Interactive {
name, err := c.runFilter(ctx, buf, "method")
if err != nil {
return err
}
c.Method = name
return c.CallMethod(ctx)
}
_, err := io.Copy(c.w, buf)
return err
}

func (c *CLI) ListServices(_ context.Context) error {
func (c *CLI) ListServices(ctx context.Context) error {
var names []string
for name := range clientMethods {
names = append(names, name)
}
sort.Strings(names)
buf := bytes.NewBuffer(nil)
for _, name := range names {
fmt.Fprintln(c.w, name)
fmt.Fprintln(buf, name)
}
return nil
if c.Interactive {
name, err := c.runFilter(ctx, buf, "service")
if err != nil {
return err
}
c.Service = name
return c.ListMethods(ctx)
}
_, err := io.Copy(c.w, buf)
return err
}

func buildKey(service, method string) string {
Expand Down
Loading