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

fix source #567

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
59 changes: 3 additions & 56 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"regexp"
Expand All @@ -12,25 +11,16 @@ import (
"time"

"github.com/atotto/clipboard"
"github.com/charmbracelet/vhs/lexer"
"github.com/charmbracelet/vhs/parser"
"github.com/charmbracelet/vhs/token"
"github.com/go-rod/rod/lib/input"
"github.com/mattn/go-runewidth"
)

// Execute executes a command on a running instance of vhs.
func Execute(c parser.Command, v *VHS) error {
if c.Type == token.SOURCE {
err := ExecuteSourceTape(c, v)
if err != nil {
return fmt.Errorf("failed to execute source tape: %w", err)
}
} else {
err := CommandFuncs[c.Type](c, v)
if err != nil {
return fmt.Errorf("failed to execute command: %w", err)
}
err := CommandFuncs[c.Type](c, v)
if err != nil {
return fmt.Errorf("failed to execute command: %w", err)
}

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

const sourceDisplayMaxLength = 10

// ExecuteSourceTape is a CommandFunc that executes all commands of source tape.
func ExecuteSourceTape(c parser.Command, v *VHS) error {
tapePath := c.Args
var out io.Writer = os.Stdout
if quietFlag {
out = io.Discard
}

// read tape file
tape, err := os.ReadFile(tapePath)
if err != nil {
return fmt.Errorf("failed to read tape %s: %w", tapePath, err)
}

l := lexer.New(string(tape))
p := parser.New(l)

cmds := p.Parse()
if len(p.Errors()) != 0 {
return InvalidSyntaxError{p.Errors()}
}

displayPath := runewidth.Truncate(strings.TrimSuffix(tapePath, extension), sourceDisplayMaxLength, "…")

// Run all commands from the sourced tape file.
for _, cmd := range cmds {
// Output have to be avoid in order to not overwrite output of the original tape.
if cmd.Type == token.SOURCE ||
cmd.Type == token.OUTPUT {
continue
}
_, _ = fmt.Fprintf(out, "%s %s\n", GrayStyle.Render(displayPath+":"), Highlight(cmd, false))
err := CommandFuncs[cmd.Type](cmd, v)
if err != nil {
return fmt.Errorf("failed to execute command %s: %w", cmd.Type.String(), err)
}
}

return nil
}

// ExecuteScreenshot is a CommandFunc that indicates a new screenshot must be taken.
func ExecuteScreenshot(c parser.Command, v *VHS) error {
v.ScreenshotNextFrame(c.Args)
Expand Down
68 changes: 40 additions & 28 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type Command struct {
Type CommandType
Options string
Args string
Source string
}

// String returns the string representation of the command.
Expand Down Expand Up @@ -123,15 +124,15 @@ func (p *Parser) Parse() []Command {
p.nextToken()
continue
}
cmds = append(cmds, p.parseCommand())
cmds = append(cmds, p.parseCommand()...)
p.nextToken()
}

return cmds
}

// parseCommand parses a command.
func (p *Parser) parseCommand() Command {
func (p *Parser) parseCommand() []Command {
switch p.cur.Type {
case token.SPACE,
token.BACKSPACE,
Expand All @@ -146,42 +147,42 @@ func (p *Parser) parseCommand() Command {
token.UP,
token.PAGEUP,
token.PAGEDOWN:
return p.parseKeypress(p.cur.Type)
return []Command{p.parseKeypress(p.cur.Type)}
case token.SET:
return p.parseSet()
return []Command{p.parseSet()}
case token.OUTPUT:
return p.parseOutput()
return []Command{p.parseOutput()}
case token.SLEEP:
return p.parseSleep()
return []Command{p.parseSleep()}
case token.TYPE:
return p.parseType()
return []Command{p.parseType()}
case token.CTRL:
return p.parseCtrl()
return []Command{p.parseCtrl()}
case token.ALT:
return p.parseAlt()
return []Command{p.parseAlt()}
case token.SHIFT:
return p.parseShift()
return []Command{p.parseShift()}
case token.HIDE:
return p.parseHide()
return []Command{p.parseHide()}
case token.REQUIRE:
return p.parseRequire()
return []Command{p.parseRequire()}
case token.SHOW:
return p.parseShow()
return []Command{p.parseShow()}
case token.WAIT:
return p.parseWait()
return []Command{p.parseWait()}
case token.SOURCE:
return p.parseSource()
case token.SCREENSHOT:
return p.parseScreenshot()
return []Command{p.parseScreenshot()}
case token.COPY:
return p.parseCopy()
return []Command{p.parseCopy()}
case token.PASTE:
return p.parsePaste()
return []Command{p.parsePaste()}
case token.ENV:
return p.parseEnv()
return []Command{p.parseEnv()}
default:
p.errors = append(p.errors, NewError(p.cur, "Invalid command: "+p.cur.Literal))
return Command{Type: token.ILLEGAL}
return []Command{{Type: token.ILLEGAL}}
}
}

Expand Down Expand Up @@ -652,13 +653,13 @@ func (p *Parser) parseEnv() Command {
// Source command takes a tape path to include in current tape.
//
// Source <path>
func (p *Parser) parseSource() Command {
func (p *Parser) parseSource() []Command {
cmd := Command{Type: token.SOURCE}

if p.peek.Type != token.STRING {
p.errors = append(p.errors, NewError(p.cur, "Expected path after Source"))
p.nextToken()
return cmd
return []Command{cmd}
}

srcPath := p.peek.Literal
Expand All @@ -668,15 +669,15 @@ func (p *Parser) parseSource() Command {
if ext != ".tape" {
p.errors = append(p.errors, NewError(p.peek, "Expected file with .tape extension"))
p.nextToken()
return cmd
return []Command{cmd}
}

// Check if tape exist
if _, err := os.Stat(srcPath); os.IsNotExist(err) {
notFoundErr := fmt.Sprintf("File %s not found", srcPath)
p.errors = append(p.errors, NewError(p.peek, notFoundErr))
p.nextToken()
return cmd
return []Command{cmd}
}

// Check if source tape contains nested Source command
Expand All @@ -685,7 +686,7 @@ func (p *Parser) parseSource() Command {
readErr := fmt.Sprintf("Unable to read file: %s", srcPath)
p.errors = append(p.errors, NewError(p.peek, readErr))
p.nextToken()
return cmd
return []Command{cmd}
}

srcTape := string(d)
Expand All @@ -694,7 +695,7 @@ func (p *Parser) parseSource() Command {
readErr := fmt.Sprintf("Source tape: %s is empty", srcPath)
p.errors = append(p.errors, NewError(p.peek, readErr))
p.nextToken()
return cmd
return []Command{cmd}
}

srcLexer := lexer.New(srcTape)
Expand All @@ -706,7 +707,7 @@ func (p *Parser) parseSource() Command {
if cmd.Type == token.SOURCE {
p.errors = append(p.errors, NewError(p.peek, "Nested Source detected"))
p.nextToken()
return cmd
return []Command{cmd}
}
}

Expand All @@ -715,12 +716,23 @@ func (p *Parser) parseSource() Command {
if len(srcErrors) > 0 {
p.errors = append(p.errors, NewError(p.peek, fmt.Sprintf("%s has %d errors", srcPath, len(srcErrors))))
p.nextToken()
return cmd
return []Command{cmd}
}

cmd.Args = p.peek.Literal
filtered := make([]Command, 0, len(srcCmds))
for _, srcCmd := range srcCmds {
// Output have to be avoid in order to not overwrite output of the original tape.
if srcCmd.Type == token.SOURCE ||
srcCmd.Type == token.OUTPUT {
continue
}
srcCmd.Source = cmd.Args
filtered = append(filtered, srcCmd)
}

p.nextToken()
return cmd
return filtered
}

// parseScreenshot parses screenshot command.
Expand Down
14 changes: 12 additions & 2 deletions syntax.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (

"github.com/charmbracelet/vhs/parser"
"github.com/charmbracelet/vhs/token"
"github.com/mattn/go-runewidth"
)

const sourceDisplayMaxLength = 10

// Highlight syntax highlights a command for prettier printing.
// It takes an argument whether or not to print the command in a faint style to
// represent hidden commands.
Expand All @@ -18,11 +21,17 @@ func Highlight(c parser.Command, faint bool) string {
argsStyle = NumberStyle
)

sourcePrefix := ""
if c.Source != "" {
displayPath := runewidth.Truncate(strings.TrimSuffix(c.Source, extension), sourceDisplayMaxLength, "…")
sourcePrefix = GrayStyle.Render(displayPath+":") + " "
}

if faint {
if c.Options != "" {
return FaintStyle.Render(fmt.Sprintf("%s %s %s", c.Type, c.Options, c.Args))
return sourcePrefix + FaintStyle.Render(fmt.Sprintf("%s %s %s", c.Type, c.Options, c.Args))
}
return FaintStyle.Render(fmt.Sprintf("%s %s", c.Type, c.Args))
return sourcePrefix + FaintStyle.Render(fmt.Sprintf("%s %s", c.Type, c.Args))
}

switch c.Type {
Expand Down Expand Up @@ -55,6 +64,7 @@ func Highlight(c parser.Command, faint bool) string {
}

var s strings.Builder
s.WriteString(sourcePrefix)
s.WriteString(CommandStyle.Render(c.Type.String()) + " ")
if c.Options != "" {
s.WriteString(optionsStyle.Render(c.Options))
Expand Down