package caster import ( "bytes" "context" "errors" "fmt" errors2 "github.com/abelith/spellbook/internal/errors" "github.com/abelith/spellbook/internal/models" "io" "mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/syntax" "text/template" "time" ) type SpellCaster struct{} func (sc *SpellCaster) Cast(ctx context.Context, cast models.SpellCast) (models.CastResult, error) { start := time.Now() err := runSpell(ctx, &cast) duration := time.Since(start) res := models.CastResult{ Duration: duration, } if err != nil { var exitErr *interp.ExitStatus if errors.As(err, &exitErr) { res.ExitCode = int(*exitErr) res.Err = err return res, nil } return res, fmt.Errorf("%w: %w", errors2.ErrFailedToCast, err) } return res, nil } func renderSpell(cast *models.SpellCast) (io.Reader, error) { tmpl, err := template.New(cast.Spell.Name).Option("missingkey=error").Parse(cast.Spell.Spell) if err != nil { return nil, fmt.Errorf("%w: %w", errors2.ErrTemplateFailed, err) } buf := bytes.NewBuffer(nil) if err := tmpl.Execute(buf, cast.Args); err != nil { return nil, fmt.Errorf("%w: %w", errors2.ErrTemplateFailed, err) } return buf, nil } func runSpell(ctx context.Context, cast *models.SpellCast) error { r, err := renderSpell(cast) if err != nil { return err } node, err := syntax.NewParser().Parse(r, cast.Spell.Name) if err != nil { return fmt.Errorf("failed to parse spell: %w", err) } runner, err := interp.New(interp.StdIO(cast.Stdin, cast.Stdout, cast.Stderr)) if err != nil { return fmt.Errorf("failed to run spell: %w", err) } return runner.Run(ctx, node) }