first version of scripting
ADD
internal/scripting/engine.go
+package scripting
+
+// Engine describes a type that is capable of handling events from the main
+// application and allows mutation of data.
+type Engine interface {
+ LoadScripts(string) error // loads scripts from a directory into the VM
+ OnMessage(string) string // OnMessage handles new messages
+}

ADD
internal/scripting/javascript.go
+package scripting
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/pkg/errors"
+ "github.com/robertkrimen/otto"
+)
+
+var _ Engine = &JavaScriptEngine{}
+
+// JavaScriptEngine stores scripting engine state
+type JavaScriptEngine struct {
+ vm *otto.Otto
+}
+
+// New instantiates a new scripting engine
+func New() (e *JavaScriptEngine) {
+ e = &JavaScriptEngine{
+ vm: otto.New(),
+ }
+
+ return
+}
+
+// LoadScripts implements Engine
+func (e *JavaScriptEngine) LoadScripts(dirname string) (err error) {
+ files, err := ioutil.ReadDir(dirname)
+ if err != nil {
+ return
+ }
+
+ for _, file := range files {
+ if file.IsDir() {
+ continue
+ }
+ path := filepath.Join(dirname, file.Name())
+ f, err := os.Open(path)
+ if err != nil {
+ return errors.Wrap(err, path)
+ }
+ _, err = e.vm.Run(f)
+ if err != nil {
+ return errors.Wrap(err, "failed to run script")
+ }
+ }
+
+ return
+}
+
+// OnMessage implements Engine
+func (e *JavaScriptEngine) OnMessage(oldText string) (newText string) {
+ v, err := e.vm.Run(fmt.Sprintf(`onMessage("%s")`, oldText))
+ if err != nil {
+ return oldText
+ }
+ return v.String()
+}

ADD
internal/scripting/javascript_test.go
+package scripting
+
+import (
+ "testing"
+)
+
+func TestJavaScriptEngine(t *testing.T) {
+ tests := []struct {
+ dir string
+ input string
+ want string
+ }{
+ {
+ "test/simple",
+ "Replace me",
+ "Replace this",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.input, func(t *testing.T) {
+ e := New()
+ if err := e.LoadScripts(tt.dir); err != nil {
+ t.Error("LoadScripts failed:", err)
+ return
+ }
+ if gotNewText := e.OnMessage(tt.input); gotNewText != tt.want {
+ t.Errorf("JavaScriptEngine.OnMessage() = %v, want %v", gotNewText, tt.want)
+ }
+ })
+ }
+}

ADD
internal/scripting/test/simple/simple.js
+function onMessage(input) {
+ return input.replace("me", "this");
+}

first version of scripting