Skip to content

Commit

Permalink
feat: pre-rendered completions, for better testing and assertions
Browse files Browse the repository at this point in the history
[GA] for fish shell, rest are under-development
  • Loading branch information
nxtcoder17 committed Oct 19, 2024
1 parent 9116b0f commit 49c3c77
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 64 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.direnv
bin/
.secrets/
Taskfile.yml
2 changes: 1 addition & 1 deletion Runfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ tasks:
cmd:
- |+
echo "building ..."
go build -o bin/run -ldflags="-s -w" -tags urfave_cli_no_docs cmd/run/main.go
go build -o bin/run -ldflags="-s -w" -tags urfave_cli_no_docs ./cmd/run
echo "DONE"
example:
Expand Down
46 changes: 46 additions & 0 deletions cmd/run/completions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
"context"
"fmt"
"io"
"log/slog"

"github.com/nxtcoder17/runfile/pkg/runfile"
)

func generateShellCompletion(_ context.Context, writer io.Writer, rfpath string) error {
// if c.NArg() > 0 {
// return nil
// }

// runfilePath, err := locateRunfile(c)
// if err != nil {
// slog.Error("locating runfile", "err", err)
// panic(err)
// }

runfile, err := runfile.Parse(rfpath)
if err != nil {
slog.Error("parsing, got", "err", err)
panic(err)
}

for k := range runfile.Tasks {
fmt.Fprintf(writer, "%s\n", k)
}

m, err := runfile.ParseIncludes()
if err != nil {
slog.Error("parsing, got", "err", err)
panic(err)
}

for k, v := range m {
for tn := range v.Runfile.Tasks {
fmt.Fprintf(writer, "%s:%s\n", k, tn)
}
}

return nil
}
35 changes: 35 additions & 0 deletions cmd/run/completions/bash/run.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#! /bin/bash

: ${PROG:=$(basename ${BASH_SOURCE})}

# Macs have bash3 for which the bash-completion package doesn't include
# _init_completion. This is a minimal version of that function.
_cli_init_completion() {
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}

_cli_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base words
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -n "=:" || return
else
_cli_init_completion -n "=:" || return
fi
words=("${words[@]:0:$cword}")
if [[ "$cur" == "-"* ]]; then
requestComp="${words[*]} ${cur} completion:gen"
else
requestComp="${words[*]} completion:gen"
fi
opts=$(eval "${requestComp}" 2>/dev/null)
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
return 0
fi
}

complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
unset PROG
26 changes: 18 additions & 8 deletions cmd/run/completions/fish/run.fish
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
# run fish shell completion
set PROGNAME run

function __fetch_runnable_tasks --description 'fetches all runnable tasks'
function __runfile_list_targets --description 'fetches all runnable tasks'
for i in (commandline -opc)
if contains -- $i help h
return 1
end
end

# Grab names and descriptions (if any) of the tasks
set -l output (run --generate-shell-completion | string split0)
echo "$output" > /tmp/test.txt
$PROGNAME --list 2>&1 | read -lz rawOutput

# RETURN on non-zero exit code (in case of errors)
if test $status -ne 0
return
end

set -l output (echo $rawOutput | string split0)

if test $output
if test "$DEBUG" = "true"
echo "$output" > /tmp/test.txt
end
echo $output
end

return 0
end

complete -c run -d "runs a task with given name" -xa "(__fish_run_no_subcommand)"
complete -c run -n '__fish_run_no_subcommand' -f -l help -s h -d 'show help'
complete -c run -n '__fish_run_no_subcommand' -f -l help -s h -d 'show help'
complete -r -c run -n '__fish_run_no_subcommand' -a 'help h' -d 'Shows a list of commands or help for one command'
complete -c $PROGNAME -d "runs target with given name" -xa "(__runfile_list_targets)"
complete -c $PROGNAME -rF -l file -s f -d 'runs targets from this runfile'
# complete -c $PROGNAME -n '__runfile_list_targets' -f -l help -s h -d 'show help'
# complete -c $PROGNAME -n '__runfile_list_targets' -f -l help -s h -d 'show help'
# complete -r -c $PROGNAME -n '__runfile_list_targets' -a 'help h' -d 'Shows a list of commands or help for one command'

Binary file added cmd/run/completions/ps/run.ps
Binary file not shown.
20 changes: 20 additions & 0 deletions cmd/run/completions/zsh/run.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#compdef $PROG

_cli_zsh_autocomplete() {
local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} completion:gen)}")
else
opts=("${(@f)$(${words[@]:0:#words[@]-1} completion:gen)}")
fi

if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi
}

compdef _cli_zsh_autocomplete $PROG
113 changes: 78 additions & 35 deletions cmd/run/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package main

import (
"context"
_ "embed"
"fmt"
"log/slog"
"os"
"os/signal"
"path/filepath"
Expand All @@ -11,7 +13,6 @@ import (

"github.com/nxtcoder17/runfile/pkg/logging"
"github.com/nxtcoder17/runfile/pkg/runfile"
"github.com/nxtcoder17/runfile/pkg/runfile/errors"
"github.com/urfave/cli/v3"
)

Expand All @@ -23,16 +24,29 @@ var runfileNames []string = []string{
"Runfile.yaml",
}

//go:embed completions/fish/run.fish
var shellCompletionFISH string

//go:embed completions/bash/run.bash
var shellCompletionBASH string

//go:embed completions/zsh/run.zsh
var shellCompletionZSH string

//go:embed completions/ps/run.ps
var shellCompletionPS string

func main() {
cmd := cli.Command{
Name: "run",
Version: Version,
Description: "A simple task runner",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "file",
Aliases: []string{"f"},
Value: "",
Name: "file",
Aliases: []string{"f"},
TakesFile: true,
Value: "",
},

&cli.BoolFlag{
Expand All @@ -51,56 +65,59 @@ func main() {
Name: "debug",
Value: false,
},

&cli.BoolFlag{
Name: "list",
Value: false,
Aliases: []string{"ls"},
},
},
// ShellCompletionCommandName: "completion:shell",
EnableShellCompletion: true,
// DefaultCommand: "help",
ShellComplete: func(ctx context.Context, c *cli.Command) {
if c.NArg() > 0 {
return
}

runfilePath, err := locateRunfile(c)
if err != nil {
slog.Error("locating runfile", "err", err)
panic(err)
}

runfile, err := runfile.Parse(runfilePath)
if err != nil {
panic(err)
}

for k := range runfile.Tasks {
fmt.Fprintf(c.Root().Writer, "%s\n", k)
}

m, err := runfile.ParseIncludes()
if err != nil {
panic(err)
}

for k, v := range m {
for tn := range v.Runfile.Tasks {
fmt.Fprintf(c.Root().Writer, "%s:%s\n", k, tn)
}
}
generateShellCompletion(ctx, c.Root().Writer, runfilePath)
},
Action: func(ctx context.Context, c *cli.Command) error {
parallel := c.Bool("parallel")
watch := c.Bool("watch")
debug := c.Bool("debug")

showList := c.Bool("list")
if showList {
runfilePath, err := locateRunfile(c)
if err != nil {
slog.Error("locating runfile, got", "err", err)
return err
}
return generateShellCompletion(ctx, c.Root().Writer, runfilePath)
}

if c.NArg() == 0 {
c.Command("help").Run(ctx, nil)
return nil
}

runfilePath, err := locateRunfile(c)
if err != nil {
slog.Error("locating runfile, got", "err", err)
return err
}

rf, err := runfile.Parse(runfilePath)
if err != nil {
panic(err)
rf, err2 := runfile.Parse(runfilePath)
if err2 != nil {
slog.Error("parsing runfile, got", "err", err2)
panic(err2)
}

kv := make(map[string]string)
Expand Down Expand Up @@ -136,8 +153,8 @@ func main() {
return fmt.Errorf("parallel and watch can't be set together")
}

logger := logging.NewSlogLogger(logging.SlogOptions{
ShowCaller: debug,
logger := logging.New(logging.Options{
SlogKeyAsPrefix: "task",
ShowDebugLogs: debug,
SetAsDefaultLogger: true,
})
Expand All @@ -150,23 +167,49 @@ func main() {
KVs: kv,
})
},
Commands: []*cli.Command{
{
Name: "shell:completion",
Action: func(ctx context.Context, c *cli.Command) error {
if c.NArg() != 2 {
return fmt.Errorf("needs argument one of [bash,zsh,fish,ps]")
}

switch c.Args().Slice()[1] {
case "fish":
fmt.Fprint(c.Writer, shellCompletionFISH)
case "bash":
fmt.Fprint(c.Writer, shellCompletionBASH)
case "zsh":
fmt.Fprint(c.Writer, shellCompletionZSH)
case "ps":
fmt.Fprint(c.Writer, shellCompletionPS)
}

return nil
},
},
},
}

ctx, cf := context.WithCancel(context.TODO())
ctx, cf := signal.NotifyContext(context.TODO(), os.Interrupt, syscall.SIGTERM)
defer cf()

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
fmt.Println("\n\rcanceling...")
<-ctx.Done()
cf()
os.Exit(1)
}()

if err := cmd.Run(ctx, os.Args); err != nil {
errm, ok := err.(errors.Message)
errm, ok := err.(*runfile.Error)
slog.Debug("got", "err", err)
if ok {
errm.Log()
if errm != nil {
errm.Log()
}
} else {
slog.Error("got", "err", err)
}
os.Exit(1)
}
Expand Down
Loading

0 comments on commit 49c3c77

Please sign in to comment.