Skip to content

Commit f50fb16

Browse files
committed
feat: commands with run command can supply env variables to their run targets
- also, enchances visibility wth logging commands prior to their execution - also, adds syntax highlighting those commands with the help of `github.com/alecthomas/chroma/v2/quick`. And, `github.com/charmbracelet/lipgloss` for beautiful borders
1 parent acd2ef1 commit f50fb16

File tree

9 files changed

+140
-107
lines changed

9 files changed

+140
-107
lines changed

Runfile.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ tasks:
2727
env:
2828
pattern:
2929
default: ""
30+
only_failing:
31+
default: false
3032
watch:
3133
enable: true
3234
dir:
@@ -35,11 +37,13 @@ tasks:
3537
- .go
3638
cmd:
3739
- |+
38-
if [ -z "$pattern" ]; then
39-
go test -json ./parser/... | gotestfmt
40-
else
41-
go test -json ./parser/... -run "$pattern" | gotestfmt
42-
fi
40+
pattern_args=""
41+
[ -n "$pattern" ] && pattern_args="-run '$pattern'"
42+
43+
testfmt_args=""
44+
[ "$only_failing" = "true" ] && testfmt_args="--hide successful-tests"
45+
46+
go test -json ./parser/... $pattern_args | gotestfmt $testfmt_args
4347
4448
test:only-failing:
4549
cmd:

cmd/run/completions/run.fish

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,14 @@
11
# run fish shell completion
22
set PROGNAME run
33

4-
function __runfile_list_targets --description 'fetches all runnable tasks'
5-
for i in (commandline -opc)
6-
if contains -- $i help h
7-
return 1
8-
end
9-
end
10-
11-
$PROGNAME --list 2>&1 | read -lz rawOutput
12-
13-
# RETURN on non-zero exit code (in case of errors)
14-
if test $status -ne 0
15-
return
16-
end
17-
18-
set -l output (echo $rawOutput | string split0)
19-
20-
if test $output
21-
if test "$DEBUG" = "true"
22-
echo "$output" > /tmp/test.txt
23-
end
24-
echo $output
25-
end
26-
27-
return 0
4+
# list_targets fetches all targets, with all flags provided on the cli
5+
function list_targets
6+
eval $PROGNAME --list
7+
# eval (commandline -b) --list
288
end
299

30-
complete -c $PROGNAME -d "runs target with given name" -xa "(__runfile_list_targets)"
10+
complete -c $PROGNAME -d "runs named task" -xa '(list_targets)'
11+
complete -c $PROGNAME -l help -s h -d 'show help'
12+
complete -c $PROGNAME -l list -s l -d 'list all tasks'
3113
complete -c $PROGNAME -rF -l file -s f -d 'runs targets from this runfile'
32-
# complete -c $PROGNAME -n '__runfile_list_targets' -f -l help -s h -d 'show help'
33-
# complete -c $PROGNAME -n '__runfile_list_targets' -f -l help -s h -d 'show help'
34-
# complete -r -c $PROGNAME -n '__runfile_list_targets' -a 'help h' -d 'Shows a list of commands or help for one command'
3514

cmd/run/main.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func main() {
8282
&cli.BoolFlag{
8383
Name: "list",
8484
Value: false,
85-
Aliases: []string{"ls"},
85+
Aliases: []string{"l"},
8686
},
8787

8888
&cli.BoolFlag{
@@ -112,13 +112,15 @@ func main() {
112112
Commands: []*cli.Command{
113113
{
114114
Name: "shell:completion",
115+
Usage: "<bash|zsh|fish|ps>",
115116
Suggest: true,
116117
Action: func(ctx context.Context, c *cli.Command) error {
117-
if c.NArg() != 2 {
118+
fmt.Printf("args: (%d)\n", c.NArg())
119+
if c.NArg() != 1 {
118120
return fmt.Errorf("needs argument one of [bash,zsh,fish,ps]")
119121
}
120122

121-
switch c.Args().Slice()[1] {
123+
switch c.Args().First() {
122124
case "fish":
123125
fmt.Fprint(c.Writer, shellCompletionFISH)
124126
case "bash":

errors/errors.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ func (e *Error) resolveTaskName() string {
4646

4747
// Error implements error.
4848
func (e *Error) Error() string {
49-
return e.err.Error()
49+
if e.err != nil {
50+
return e.err.Error()
51+
}
52+
return ""
5053
// return fmt.Sprintf("%v {%#v}", e.err, e.kv)
5154
}
5255

runner/run-task.go

Lines changed: 73 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package runner
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"io"
@@ -10,8 +11,8 @@ import (
1011
"strings"
1112
"sync"
1213

13-
// "github.com/alecthomas/chroma/v2/quick"
14-
// "github.com/charmbracelet/lipgloss"
14+
"github.com/alecthomas/chroma/v2/quick"
15+
"github.com/charmbracelet/lipgloss"
1516
"github.com/muesli/termenv"
1617
"github.com/nxtcoder17/fwatcher/pkg/executor"
1718
"github.com/nxtcoder17/fwatcher/pkg/watcher"
@@ -33,8 +34,8 @@ func isDarkTheme() bool {
3334
return termenv.NewOutput(os.Stdout).HasDarkBackground()
3435
}
3536

36-
func padString(v string, padWith string) string {
37-
sp := strings.Split(v, "\n")
37+
func padString(str string, padWith string) string {
38+
sp := strings.Split(str, "\n")
3839
for i := range sp {
3940
if i == 0 {
4041
sp[i] = fmt.Sprintf("%s | %s", padWith, sp[i])
@@ -58,29 +59,29 @@ func hasANSISupport() bool {
5859
return strings.Contains(term, "xterm") || strings.Contains(term, "screen") || strings.Contains(term, "vt100")
5960
}
6061

61-
// func printCommand(writer io.Writer, prefix, lang, cmd string) {
62-
// if isTTY() {
63-
// borderColor := "#4388cc"
64-
// if !isDarkTheme() {
65-
// borderColor = "#3d5485"
66-
// }
67-
// s := lipgloss.NewStyle().BorderForeground(lipgloss.Color(borderColor)).PaddingLeft(1).PaddingRight(1).Border(lipgloss.RoundedBorder(), true, true, true, true)
68-
//
69-
// hlCode := new(bytes.Buffer)
70-
// // choose colorschemes from `https://swapoff.org/chroma/playground/`
71-
// colorscheme := "catppuccin-macchiato"
72-
// if !isDarkTheme() {
73-
// colorscheme = "monokailight"
74-
// }
75-
// _ = colorscheme
76-
//
77-
// cmdStr := strings.TrimSpace(cmd)
78-
//
79-
// quick.Highlight(hlCode, cmdStr, lang, "terminal16m", colorscheme)
80-
//
81-
// fmt.Fprintf(writer, "\r%s%s\n", s.Render(padString(hlCode.String(), prefix)), s.UnsetBorderStyle())
82-
// }
83-
// }
62+
func printCommand(writer io.Writer, prefix, lang, cmd string) {
63+
if isTTY() {
64+
borderColor := "#4388cc"
65+
if !isDarkTheme() {
66+
borderColor = "#3d5485"
67+
}
68+
s := lipgloss.NewStyle().BorderForeground(lipgloss.Color(borderColor)).PaddingLeft(1).PaddingRight(1).Border(lipgloss.RoundedBorder(), true, true, true, true)
69+
70+
hlCode := new(bytes.Buffer)
71+
// choose colorschemes from `https://swapoff.org/chroma/playground/`
72+
colorscheme := "catppuccin-macchiato"
73+
if !isDarkTheme() {
74+
colorscheme = "monokailight"
75+
}
76+
_ = colorscheme
77+
78+
cmdStr := strings.TrimSpace(cmd)
79+
80+
quick.Highlight(hlCode, cmdStr, lang, "terminal16m", colorscheme)
81+
82+
fmt.Fprintf(writer, "\r%s%s\n", s.Render(padString(hlCode.String(), prefix)), s.UnsetBorderStyle())
83+
}
84+
}
8485

8586
type CreateCommandGroupArgs struct {
8687
Runfile *types.ParsedRunfile
@@ -90,10 +91,12 @@ type CreateCommandGroupArgs struct {
9091

9192
Stdout *LogWriter
9293
Stderr *LogWriter
94+
95+
EnvOverrides map[string]string
9396
}
9497

9598
func createCommandGroups(ctx types.Context, args CreateCommandGroupArgs) ([]executor.CommandGroup, error) {
96-
var cmds []executor.CommandGroup
99+
var groups []executor.CommandGroup
97100

98101
for _, cmd := range args.Task.Commands {
99102
switch {
@@ -110,11 +113,12 @@ func createCommandGroups(ctx types.Context, args CreateCommandGroupArgs) ([]exec
110113
}
111114

112115
rtCommands, err := createCommandGroups(ctx, CreateCommandGroupArgs{
113-
Runfile: args.Runfile,
114-
Task: rtp,
115-
Trail: append(append([]string{}, args.Trail...), rtp.Name),
116-
Stdout: args.Stdout,
117-
Stderr: args.Stderr,
116+
Runfile: args.Runfile,
117+
Task: rtp,
118+
Trail: append(append([]string{}, args.Trail...), rtp.Name),
119+
Stdout: args.Stdout,
120+
Stderr: args.Stderr,
121+
EnvOverrides: cmd.Env,
118122
})
119123
if err != nil {
120124
return nil, errors.WithErr(err).KV("env-vars", args.Runfile.Env)
@@ -123,52 +127,58 @@ func createCommandGroups(ctx types.Context, args CreateCommandGroupArgs) ([]exec
123127
cg := executor.CommandGroup{
124128
Groups: rtCommands,
125129
Parallel: rtp.Parallel,
130+
PreExecCommand: func(c *exec.Cmd) {
131+
str := c.String()
132+
sp := strings.SplitN(str, " ", 3)
133+
args.Stderr.WithDimmedPrefix(*cmd.Run).Write([]byte(sp[2]))
134+
},
126135
}
127136

128-
cmds = append(cmds, cg)
137+
groups = append(groups, cg)
129138
}
130139

131140
case cmd.Command != nil:
132141
{
133142
cg := executor.CommandGroup{Parallel: args.Task.Parallel}
134143

135-
cg.Commands = append(cg.Commands, func(c context.Context) *exec.Cmd {
136-
commandsList := make([]string, 0, len(args.Task.Commands))
137-
for _, c := range args.Task.Commands {
138-
if c.Command != nil {
139-
commandsList = append(commandsList, *c.Command)
140-
}
141-
}
142-
143-
return CreateCommand(ctx, CmdArgs{
144-
Shell: args.Task.Shell,
145-
Env: fn.ToEnviron(args.Task.Env),
146-
Cmd: *cmd.Command,
147-
WorkingDir: args.Task.WorkingDir,
148-
interactive: args.Task.Interactive,
149-
Stdout: func() io.Writer {
150-
if args.Task.Interactive {
151-
return os.Stdout
152-
}
153-
return args.Stdout.WithPrefix(args.Task.Name)
154-
}(),
155-
Stderr: func() io.Writer {
156-
if args.Task.Interactive {
157-
return os.Stderr
158-
}
159-
return args.Stderr.WithPrefix(args.Task.Name)
160-
}(),
144+
cg.PreExecCommand = func(cmd *exec.Cmd) {
145+
str := strings.TrimSpace(cmd.String())
146+
sp := strings.SplitN(str, " ", len(args.Task.Shell)+1)
147+
printCommand(args.Stderr, args.Task.Name, "bash", sp[2])
148+
}
149+
150+
cg.Commands = append(
151+
cg.Commands,
152+
func(c context.Context) *exec.Cmd {
153+
return CreateCommand(ctx, CmdArgs{
154+
Shell: args.Task.Shell,
155+
Env: fn.ToEnviron(fn.MapMerge(args.Task.Env, args.EnvOverrides)),
156+
Cmd: *cmd.Command,
157+
WorkingDir: args.Task.WorkingDir,
158+
interactive: args.Task.Interactive,
159+
Stdout: func() io.Writer {
160+
if args.Task.Interactive {
161+
return os.Stdout
162+
}
163+
return args.Stdout.WithPrefix(args.Task.Name)
164+
}(),
165+
Stderr: func() io.Writer {
166+
if args.Task.Interactive {
167+
return os.Stderr
168+
}
169+
return args.Stderr.WithPrefix(args.Task.Name)
170+
}(),
171+
})
161172
})
162-
})
163173

164174
ctx.Debug("HERE", "cmd", *cmd.Command, "parallel", args.Task.Parallel)
165175

166-
cmds = append(cmds, cg)
176+
groups = append(groups, cg)
167177
}
168178
}
169179
}
170180

171-
return cmds, nil
181+
return groups, nil
172182
}
173183

174184
func runTask(ctx types.Context, prf *types.ParsedRunfile, args runTaskArgs) error {

runner/writer.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type PrefixedWriter struct {
1313
w io.Writer
1414
prefix []byte
1515
buf *bytes.Buffer
16+
render func([]byte) []byte
1617
}
1718

1819
func (pw *PrefixedWriter) Write(p []byte) (int, error) {
@@ -26,11 +27,11 @@ func (pw *PrefixedWriter) Write(p []byte) (int, error) {
2627
line, err := pw.buf.ReadBytes('\n')
2728
if errors.Is(err, io.EOF) {
2829
pw.buf.Reset()
29-
pw.buf.Write(line)
30+
pw.buf.Write(pw.render(line))
3031
break
3132
}
3233

33-
if _, err := pw.w.Write(append(pw.prefix, line...)); err != nil {
34+
if _, err := pw.w.Write(append(pw.prefix, pw.render(line)...)); err != nil {
3435
return n, err
3536
}
3637
}
@@ -59,5 +60,23 @@ func (s *LogWriter) WithPrefix(prefix string) io.Writer {
5960
prefix = types.GetStyledPrefix(prefix)
6061
}
6162

62-
return &PrefixedWriter{s.w, []byte(prefix), bytes.NewBuffer(nil)}
63+
return &PrefixedWriter{
64+
w: s.w,
65+
prefix: []byte(prefix),
66+
buf: bytes.NewBuffer(nil),
67+
render: func(b []byte) []byte { return b },
68+
}
69+
}
70+
71+
func (s *LogWriter) WithDimmedPrefix(prefix string) io.Writer {
72+
if prefix != "" && hasANSISupport() {
73+
prefix = types.GetDimStyledPrefix(prefix)
74+
}
75+
76+
return &PrefixedWriter{
77+
w: s.w,
78+
prefix: []byte(prefix),
79+
buf: bytes.NewBuffer(nil),
80+
render: func(b []byte) []byte { return []byte(types.GetDimmedText(b)) },
81+
}
6382
}

types/colors.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,29 @@ const (
77
StyleBold = "\033[1m"
88
StyleFgGreen = "\033[32m"
99
StyleFgRed = "\033[31m"
10+
StyleFgGray = "\033[90m"
11+
// StyleFgGray = "\033[2;30m"
1012
)
1113

1214
func GetStyledPrefix(prefix string) string {
1315
return fmt.Sprintf("%s[%s]%s ", StyleFgGreen, prefix, StyleReset)
1416
// return fmt.Sprintf("%s%s |%s ", Green, prefix, Reset)
1517
}
1618

19+
func GetDimStyledPrefix(prefix string) string {
20+
return fmt.Sprintf("%s[%s]%s ", "\033[3;36m", prefix, StyleReset)
21+
// return fmt.Sprintf("%s%s |%s ", Green, prefix, Reset)
22+
}
23+
24+
func GetDimmedText(text []byte) string {
25+
return fmt.Sprintf("%s%s%s", "\033[0;36m", text, StyleReset)
26+
// return fmt.Sprintf("%s%s%s", StyleFgGray, text, StyleReset)
27+
}
28+
29+
func GetCommandHighlight(text []byte) string {
30+
return fmt.Sprintf("%s%s%s", StyleFgGreen, text, StyleReset)
31+
}
32+
1733
func GetErrorStyledPrefix(prefix string) string {
1834
return fmt.Sprintf("%s[%s]%s ", StyleFgRed, prefix, StyleReset)
1935
// return fmt.Sprintf("%s%s |%s ", Green, prefix, Reset)

0 commit comments

Comments
 (0)