Skip to content

Commit fd38bed

Browse files
rsteubecaarlos0
andauthored
fix: source nested tapes (#567)
Co-authored-by: Carlos Alexandro Becker <[email protected]>
1 parent b41d47f commit fd38bed

File tree

3 files changed

+55
-86
lines changed

3 files changed

+55
-86
lines changed

command.go

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package main
33
import (
44
"encoding/json"
55
"fmt"
6-
"io"
76
"os"
87
"os/exec"
98
"regexp"
@@ -12,25 +11,16 @@ import (
1211
"time"
1312

1413
"github.com/atotto/clipboard"
15-
"github.com/charmbracelet/vhs/lexer"
1614
"github.com/charmbracelet/vhs/parser"
1715
"github.com/charmbracelet/vhs/token"
1816
"github.com/go-rod/rod/lib/input"
19-
"github.com/mattn/go-runewidth"
2017
)
2118

2219
// Execute executes a command on a running instance of vhs.
2320
func Execute(c parser.Command, v *VHS) error {
24-
if c.Type == token.SOURCE {
25-
err := ExecuteSourceTape(c, v)
26-
if err != nil {
27-
return fmt.Errorf("failed to execute source tape: %w", err)
28-
}
29-
} else {
30-
err := CommandFuncs[c.Type](c, v)
31-
if err != nil {
32-
return fmt.Errorf("failed to execute command: %w", err)
33-
}
21+
err := CommandFuncs[c.Type](c, v)
22+
if err != nil {
23+
return fmt.Errorf("failed to execute command: %w", err)
3424
}
3525

3626
if v.recording && v.Options.Test.Output != "" {
@@ -710,49 +700,6 @@ func ExecuteSetCursorBlink(c parser.Command, v *VHS) error {
710700
return nil
711701
}
712702

713-
const sourceDisplayMaxLength = 10
714-
715-
// ExecuteSourceTape is a CommandFunc that executes all commands of source tape.
716-
func ExecuteSourceTape(c parser.Command, v *VHS) error {
717-
tapePath := c.Args
718-
var out io.Writer = os.Stdout
719-
if quietFlag {
720-
out = io.Discard
721-
}
722-
723-
// read tape file
724-
tape, err := os.ReadFile(tapePath)
725-
if err != nil {
726-
return fmt.Errorf("failed to read tape %s: %w", tapePath, err)
727-
}
728-
729-
l := lexer.New(string(tape))
730-
p := parser.New(l)
731-
732-
cmds := p.Parse()
733-
if len(p.Errors()) != 0 {
734-
return InvalidSyntaxError{p.Errors()}
735-
}
736-
737-
displayPath := runewidth.Truncate(strings.TrimSuffix(tapePath, extension), sourceDisplayMaxLength, "…")
738-
739-
// Run all commands from the sourced tape file.
740-
for _, cmd := range cmds {
741-
// Output have to be avoid in order to not overwrite output of the original tape.
742-
if cmd.Type == token.SOURCE ||
743-
cmd.Type == token.OUTPUT {
744-
continue
745-
}
746-
_, _ = fmt.Fprintf(out, "%s %s\n", GrayStyle.Render(displayPath+":"), Highlight(cmd, false))
747-
err := CommandFuncs[cmd.Type](cmd, v)
748-
if err != nil {
749-
return fmt.Errorf("failed to execute command %s: %w", cmd.Type.String(), err)
750-
}
751-
}
752-
753-
return nil
754-
}
755-
756703
// ExecuteScreenshot is a CommandFunc that indicates a new screenshot must be taken.
757704
func ExecuteScreenshot(c parser.Command, v *VHS) error {
758705
v.ScreenshotNextFrame(c.Args)

parser/parser.go

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type Command struct {
6565
Type CommandType
6666
Options string
6767
Args string
68+
Source string
6869
}
6970

7071
// String returns the string representation of the command.
@@ -123,15 +124,15 @@ func (p *Parser) Parse() []Command {
123124
p.nextToken()
124125
continue
125126
}
126-
cmds = append(cmds, p.parseCommand())
127+
cmds = append(cmds, p.parseCommand()...)
127128
p.nextToken()
128129
}
129130

130131
return cmds
131132
}
132133

133134
// parseCommand parses a command.
134-
func (p *Parser) parseCommand() Command {
135+
func (p *Parser) parseCommand() []Command {
135136
switch p.cur.Type {
136137
case token.SPACE,
137138
token.BACKSPACE,
@@ -146,42 +147,42 @@ func (p *Parser) parseCommand() Command {
146147
token.UP,
147148
token.PAGE_UP,
148149
token.PAGE_DOWN:
149-
return p.parseKeypress(p.cur.Type)
150+
return []Command{p.parseKeypress(p.cur.Type)}
150151
case token.SET:
151-
return p.parseSet()
152+
return []Command{p.parseSet()}
152153
case token.OUTPUT:
153-
return p.parseOutput()
154+
return []Command{p.parseOutput()}
154155
case token.SLEEP:
155-
return p.parseSleep()
156+
return []Command{p.parseSleep()}
156157
case token.TYPE:
157-
return p.parseType()
158+
return []Command{p.parseType()}
158159
case token.CTRL:
159-
return p.parseCtrl()
160+
return []Command{p.parseCtrl()}
160161
case token.ALT:
161-
return p.parseAlt()
162+
return []Command{p.parseAlt()}
162163
case token.SHIFT:
163-
return p.parseShift()
164+
return []Command{p.parseShift()}
164165
case token.HIDE:
165-
return p.parseHide()
166+
return []Command{p.parseHide()}
166167
case token.REQUIRE:
167-
return p.parseRequire()
168+
return []Command{p.parseRequire()}
168169
case token.SHOW:
169-
return p.parseShow()
170+
return []Command{p.parseShow()}
170171
case token.WAIT:
171-
return p.parseWait()
172+
return []Command{p.parseWait()}
172173
case token.SOURCE:
173174
return p.parseSource()
174175
case token.SCREENSHOT:
175-
return p.parseScreenshot()
176+
return []Command{p.parseScreenshot()}
176177
case token.COPY:
177-
return p.parseCopy()
178+
return []Command{p.parseCopy()}
178179
case token.PASTE:
179-
return p.parsePaste()
180+
return []Command{p.parsePaste()}
180181
case token.ENV:
181-
return p.parseEnv()
182+
return []Command{p.parseEnv()}
182183
default:
183184
p.errors = append(p.errors, NewError(p.cur, "Invalid command: "+p.cur.Literal))
184-
return Command{Type: token.ILLEGAL}
185+
return []Command{{Type: token.ILLEGAL}}
185186
}
186187
}
187188

@@ -659,13 +660,13 @@ func (p *Parser) parseEnv() Command {
659660
// Source command takes a tape path to include in current tape.
660661
//
661662
// Source <path>
662-
func (p *Parser) parseSource() Command {
663+
func (p *Parser) parseSource() []Command {
663664
cmd := Command{Type: token.SOURCE}
664665

665666
if p.peek.Type != token.STRING {
666667
p.errors = append(p.errors, NewError(p.cur, "Expected path after Source"))
667668
p.nextToken()
668-
return cmd
669+
return []Command{cmd}
669670
}
670671

671672
srcPath := p.peek.Literal
@@ -675,15 +676,15 @@ func (p *Parser) parseSource() Command {
675676
if ext != ".tape" {
676677
p.errors = append(p.errors, NewError(p.peek, "Expected file with .tape extension"))
677678
p.nextToken()
678-
return cmd
679+
return []Command{cmd}
679680
}
680681

681682
// Check if tape exist
682683
if _, err := os.Stat(srcPath); os.IsNotExist(err) {
683684
notFoundErr := fmt.Sprintf("File %s not found", srcPath)
684685
p.errors = append(p.errors, NewError(p.peek, notFoundErr))
685686
p.nextToken()
686-
return cmd
687+
return []Command{cmd}
687688
}
688689

689690
// Check if source tape contains nested Source command
@@ -692,7 +693,7 @@ func (p *Parser) parseSource() Command {
692693
readErr := fmt.Sprintf("Unable to read file: %s", srcPath)
693694
p.errors = append(p.errors, NewError(p.peek, readErr))
694695
p.nextToken()
695-
return cmd
696+
return []Command{cmd}
696697
}
697698

698699
srcTape := string(d)
@@ -701,7 +702,7 @@ func (p *Parser) parseSource() Command {
701702
readErr := fmt.Sprintf("Source tape: %s is empty", srcPath)
702703
p.errors = append(p.errors, NewError(p.peek, readErr))
703704
p.nextToken()
704-
return cmd
705+
return []Command{cmd}
705706
}
706707

707708
srcLexer := lexer.New(srcTape)
@@ -713,7 +714,7 @@ func (p *Parser) parseSource() Command {
713714
if cmd.Type == token.SOURCE {
714715
p.errors = append(p.errors, NewError(p.peek, "Nested Source detected"))
715716
p.nextToken()
716-
return cmd
717+
return []Command{cmd}
717718
}
718719
}
719720

@@ -722,12 +723,23 @@ func (p *Parser) parseSource() Command {
722723
if len(srcErrors) > 0 {
723724
p.errors = append(p.errors, NewError(p.peek, fmt.Sprintf("%s has %d errors", srcPath, len(srcErrors))))
724725
p.nextToken()
725-
return cmd
726+
return []Command{cmd}
726727
}
727728

728729
cmd.Args = p.peek.Literal
730+
filtered := make([]Command, 0)
731+
for _, srcCmd := range srcCmds {
732+
// Output have to be avoid in order to not overwrite output of the original tape.
733+
if srcCmd.Type == token.SOURCE ||
734+
srcCmd.Type == token.OUTPUT {
735+
continue
736+
}
737+
srcCmd.Source = cmd.Args
738+
filtered = append(filtered, srcCmd)
739+
}
740+
729741
p.nextToken()
730-
return cmd
742+
return filtered
731743
}
732744

733745
// parseScreenshot parses screenshot command.

syntax.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import (
77

88
"github.com/charmbracelet/vhs/parser"
99
"github.com/charmbracelet/vhs/token"
10+
"github.com/mattn/go-runewidth"
1011
)
1112

13+
const sourceDisplayMaxLength = 10
14+
1215
// Highlight syntax highlights a command for prettier printing.
1316
// It takes an argument whether or not to print the command in a faint style to
1417
// represent hidden commands.
@@ -18,11 +21,17 @@ func Highlight(c parser.Command, faint bool) string {
1821
argsStyle = NumberStyle
1922
)
2023

24+
sourcePrefix := ""
25+
if c.Source != "" {
26+
displayPath := runewidth.Truncate(strings.TrimSuffix(c.Source, extension), sourceDisplayMaxLength, "…")
27+
sourcePrefix = GrayStyle.Render(displayPath+":") + " "
28+
}
29+
2130
if faint {
2231
if c.Options != "" {
23-
return FaintStyle.Render(fmt.Sprintf("%s %s %s", c.Type, c.Options, c.Args))
32+
return sourcePrefix + FaintStyle.Render(fmt.Sprintf("%s %s %s", c.Type, c.Options, c.Args))
2433
}
25-
return FaintStyle.Render(fmt.Sprintf("%s %s", c.Type, c.Args))
34+
return sourcePrefix + FaintStyle.Render(fmt.Sprintf("%s %s", c.Type, c.Args))
2635
}
2736

2837
switch c.Type {
@@ -55,6 +64,7 @@ func Highlight(c parser.Command, faint bool) string {
5564
}
5665

5766
var s strings.Builder
67+
s.WriteString(sourcePrefix)
5868
s.WriteString(CommandStyle.Render(c.Type.String()) + " ")
5969
if c.Options != "" {
6070
s.WriteString(optionsStyle.Render(c.Options))

0 commit comments

Comments
 (0)