Sybren A. Stüvel 201236cf46 Refactor: take some functions out of job_compilers.Service
Take some functions out of the `Service` struct, as they are more or less
standalone anyway. This will also make it easier later to make things
thread-safe, as that'll become important when files can get live-reloaded.
2022-06-20 17:26:17 +02:00

148 lines
3.5 KiB
Go

package job_compilers
// SPDX-License-Identifier: GPL-3.0-or-later
import (
"embed"
"fmt"
"io"
"io/fs"
"path"
"strings"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/require"
"github.com/rs/zerolog/log"
)
//go:embed scripts
var scriptsFS embed.FS
// loadScripts iterates over all JavaScript files, compiles them, and stores the
// result into `s.compilers`.
func (s *Service) loadScripts() error {
scriptsSubFS, err := fs.Sub(scriptsFS, "scripts")
if err != nil {
return fmt.Errorf("failed to find embedded 'scripts' directory: %w", err)
}
compilers, err := loadScriptsFrom(scriptsSubFS)
if err != nil {
return err
}
s.compilers = compilers
return nil
}
// loadScriptsFrom iterates over all given directory entries, compiles the
// files, and stores the result into `s.compilers`.
func loadScriptsFrom(filesystem fs.FS) (map[string]Compiler, error) {
dirEntries, err := fs.ReadDir(filesystem, ".")
if err != nil {
return nil, fmt.Errorf("failed to find scripts in %v: %w", filesystem, err)
}
compilers := map[string]Compiler{}
for _, dirEntry := range dirEntries {
filename := dirEntry.Name()
if !strings.HasSuffix(filename, ".js") {
continue
}
script_bytes, err := loadScriptBytes(filesystem, filename)
if err != nil {
log.Error().Err(err).Str("filename", filename).Msg("failed to read script")
continue
}
if len(script_bytes) < 8 {
log.Debug().
Str("script", filename).
Int("fileSizeBytes", len(script_bytes)).
Msg("ignoring tiny JS file, it is unlikely to be a job compiler script")
continue
}
program, err := goja.Compile(filename, string(script_bytes), true)
if err != nil {
log.Error().Err(err).Str("filename", filename).Msg("failed to compile script")
continue
}
jobTypeName := filenameToJobType(filename)
compilers[jobTypeName] = Compiler{
jobType: jobTypeName,
program: program,
filename: filename,
}
log.Debug().
Str("script", filename).
Str("jobType", jobTypeName).
Msg("loaded script")
}
return compilers, nil
}
func loadScriptBytes(filesystem fs.FS, path string) ([]byte, error) {
file, err := filesystem.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open embedded script: %w", err)
}
return io.ReadAll(file)
}
func filenameToJobType(filename string) string {
extension := path.Ext(filename)
stem := filename[:len(filename)-len(extension)]
return strings.ReplaceAll(stem, "_", "-")
}
func newGojaVM(registry *require.Registry) *goja.Runtime {
vm := goja.New()
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
mustSet := func(name string, value interface{}) {
err := vm.Set(name, value)
if err != nil {
log.Panic().Err(err).Msgf("unable to register '%s' in Goja VM", name)
}
}
// Set some global functions.
mustSet("print", jsPrint)
mustSet("alert", jsAlert)
mustSet("frameChunker", jsFrameChunker)
mustSet("formatTimestampLocal", jsFormatTimestampLocal)
// Pre-import some useful modules.
registry.Enable(vm)
mustSet("author", require.Require(vm, "author"))
mustSet("path", require.Require(vm, "path"))
mustSet("process", require.Require(vm, "process"))
return vm
}
// compilerForJobType returns a Goja *Runtime that has the job compiler script for
// the given job type loaded up.
func (s *Service) compilerForJobType(jobTypeName string) (*VM, error) {
program, ok := s.compilers[jobTypeName]
if !ok {
return nil, ErrJobTypeUnknown
}
vm := newGojaVM(s.registry)
if _, err := vm.RunProgram(program.program); err != nil {
return nil, err
}
return &VM{
runtime: vm,
compiler: program,
}, nil
}