Files
spellbook/internal/adapters/caster/caster.go
T

73 lines
1.6 KiB
Go

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)
}