From e8df2f4afcaff575b3c2c59b0ae8385641f4ce2f Mon Sep 17 00:00:00 2001 From: Arsen Musayelyan Date: Mon, 1 Mar 2021 09:11:22 -0800 Subject: [PATCH] Initial Commit --- .gitignore | 3 ++ ast.go | 41 ++++++++++++++++ go.mod | 8 ++++ go.sum | 19 ++++++++ scpt.go | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test.scpt | 5 ++ 6 files changed, 212 insertions(+) create mode 100644 .gitignore create mode 100644 ast.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 scpt.go create mode 100644 test.scpt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c83772c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +scpt +cmd/scpt/scpt +.idea/ \ No newline at end of file diff --git a/ast.go b/ast.go new file mode 100644 index 0000000..9c74b0a --- /dev/null +++ b/ast.go @@ -0,0 +1,41 @@ +package scpt + +type AST struct { + Commands []*Command `@@*` +} + +type Command struct { + Vars []*Var `( @@` + Calls []*FuncCall `| @@ )` +} + +type FuncCall struct { + Name string `@Ident` + Args []*Arg `@@*` +} + +type Arg struct { + Key string `"with" @Ident` + Value *Value `@@` +} + +type Var struct { + Key string `"set" @Ident "to"` + Value *Value `@@` +} + +type Value struct { + String *string ` @String` + Float *float64 `| @Float` + Integer *int64 `| @Int` + Bool *Bool `| @("true" | "false")` + SubCmd *FuncCall `| "(" @@ ")"` + VarVal *string `| "$" @Ident` +} + +type Bool bool + +func (b *Bool) Capture(values []string) error { + *b = values[0] == "true" + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2db65c0 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module scpt + +go 1.16 + +require ( + github.com/alecthomas/participle v0.7.1 + github.com/gen2brain/dlgs v0.0.0-20210222160047-2f436553172f +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2352baf --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/alecthomas/participle v0.7.1 h1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs= +github.com/alecthomas/participle v0.7.1/go.mod h1:HfdmEuwvr12HXQN44HPWXR0lHmVolVYe4dyL6lQ3duY= +github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gen2brain/dlgs v0.0.0-20210222160047-2f436553172f h1:HPKrg4xeWLWOGAJGVJIYXWhVSdy2kaihuoSy7kBP7S4= +github.com/gen2brain/dlgs v0.0.0-20210222160047-2f436553172f/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU= +github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe h1:rcf1P0fm+1l0EjG16p06mYLj9gW9X36KgdHJ/88hS4g= +github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/scpt.go b/scpt.go new file mode 100644 index 0000000..bfd6187 --- /dev/null +++ b/scpt.go @@ -0,0 +1,136 @@ +package scpt + +import ( + "errors" + "fmt" + "github.com/alecthomas/participle" + "github.com/gen2brain/dlgs" + "io" + "os" + "os/exec" + "reflect" + "strings" +) + +var Vars = map[string]interface{}{} + +type FuncMap map[string]func(map[string]interface{}) (interface{}, error) + +var Funcs = FuncMap{ + "displaydialog": displayDialog, + "doshellscript": doShellScript, +} + +func AddFuncs(fnMap FuncMap) { + for name, fn := range fnMap { + Funcs[name] = fn + } +} + +func AddVars(varMap map[string]interface{}) { + for name, val := range varMap { + Vars[name] = val + } +} + +func Parse(r io.Reader) (*AST, error) { + parser, err := participle.Build(&AST{}) + if err != nil { + return nil, err + } + ast := &AST{} + err = parser.Parse("", r, ast) + if err != nil { + return nil, err + } + return ast, nil +} + +func Execute(ast AST) { + for _, cmd := range ast.Commands { + if cmd.Vars != nil { + for _, Var := range cmd.Vars { + val := ParseValue(Var.Value) + if strings.Contains(reflect.TypeOf(val).String(), ".FuncCall") { + Call := val.(*FuncCall) + Vars[Var.Key], _ = CallFunction(Call) + } else { + Vars[Var.Key] = val + } + } + } else if cmd.Calls != nil { + for _, Call := range cmd.Calls { + _, _ = CallFunction(Call) + } + } + } +} + +func ParseValue(val *Value) interface{} { + if val.String != nil { + return strings.Trim(*val.String, `"`) + } else if val.Bool != nil { + return *val.Bool + } else if val.Float != nil { + return *val.Float + } else if val.Integer != nil { + return *val.Integer + } else if val.SubCmd != nil { + return val.SubCmd + } else if val.VarVal != nil { + return Vars[*val.VarVal] + } + return nil +} + +func UnwrapArgs(args []*Arg) map[string]interface{} { + argMap := map[string]interface{}{} + for _, arg := range args { + argMap[arg.Key] = ParseValue(arg.Value) + } + return argMap +} + +func CallFunction(call *FuncCall) (interface{}, error) { + argMap := UnwrapArgs(call.Args) + return Funcs[call.Name](argMap) +} + +func displayDialog(args map[string]interface{}) (interface{}, error) { + title, ok := args["title"] + if !ok { + return nil, errors.New("title not provided") + } + text, ok := args["text"] + if !ok { + return nil, errors.New("text not provided") + } + switch args["type"] { + case "yesno": + return dlgs.Question(fmt.Sprint(title), fmt.Sprint(text), true) + case "info": + return dlgs.Info(fmt.Sprint(title), fmt.Sprint(text)) + case "error": + return dlgs.Error(fmt.Sprint(title), fmt.Sprint(text)) + case "entry": + defaultText, ok := args["default"] + if !ok { + defaultText = "" + } + input, _, err := dlgs.Entry(fmt.Sprint(title), fmt.Sprint(text), fmt.Sprint(defaultText)) + return input, err + } + return nil, nil +} + +func doShellScript(args map[string]interface{}) (interface{}, error) { + script, ok := args["content"].(string) + if ok { + cmd := exec.Command("sh", "-c", script) + cmd.Stdout = os.Stdout + _ = cmd.Run() + return "", nil + } else { + return nil, errors.New("script not provided") + } +} \ No newline at end of file diff --git a/test.scpt b/test.scpt new file mode 100644 index 0000000..0ce9336 --- /dev/null +++ b/test.scpt @@ -0,0 +1,5 @@ +set y to (displaydialog with text "Hello" with title 12 with type "yesno") +displaydialog with text "Goodbye" with title 21 with type "error" +doshellscript with content "notify-send Test Notification" +set g to (displaydialog with text "Test 2" with title 30 with type "entry" with default "text") +set x to $g \ No newline at end of file