Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the notifications schema definition #1077

Merged
merged 2 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 46 additions & 30 deletions runtime/applet.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,14 @@ type AppletOption func(*Applet) error
type ThreadInitializer func(thread *starlark.Thread) *starlark.Thread

type Applet struct {
ID string
ID string
Globals map[string]starlark.StringDict
MainFile string

loader ModuleLoader
initializers []ThreadInitializer
loadedPaths map[string]bool

globals map[string]starlark.StringDict

mainFile string
mainFun *starlark.Function
schemaFile string

Expand Down Expand Up @@ -130,7 +129,7 @@ func NewApplet(id string, src []byte, opts ...AppletOption) (*Applet, error) {
func NewAppletFromFS(id string, fsys fs.FS, opts ...AppletOption) (*Applet, error) {
a := &Applet{
ID: id,
globals: make(map[string]starlark.StringDict),
Globals: make(map[string]starlark.StringDict),
loadedPaths: make(map[string]bool),
}

Expand All @@ -153,23 +152,18 @@ func (a *Applet) Run(ctx context.Context) (roots []render.Root, err error) {
return a.RunWithConfig(ctx, nil)
}

// RunWithConfig exceutes the applet's main function, passing it configuration as a
// starlark dict. It returns the render roots that are returned by the applet.
func (a *Applet) RunWithConfig(ctx context.Context, config map[string]string) (roots []render.Root, err error) {
var args starlark.Tuple
if a.mainFun.NumParams() > 0 {
starlarkConfig := AppletConfig(config)
args = starlark.Tuple{starlarkConfig}
}

returnValue, err := a.Call(ctx, a.mainFun, args...)
if err != nil {
return nil, err
}
// ExtractRoots extracts render roots from a Starlark value. It expects the value
// to be either a single render root or a list of render roots.
//
// It's used internally by RunWithConfig to extract the roots returned by the applet.
func ExtractRoots(val starlark.Value) ([]render.Root, error) {
var roots []render.Root

if returnRoot, ok := returnValue.(render_runtime.Rootable); ok {
if val == starlark.None {
// no roots returned
} else if returnRoot, ok := val.(render_runtime.Rootable); ok {
roots = []render.Root{returnRoot.AsRenderRoot()}
} else if returnList, ok := returnValue.(*starlark.List); ok {
} else if returnList, ok := val.(*starlark.List); ok {
roots = make([]render.Root, returnList.Len())
iter := returnList.Iterate()
defer iter.Done()
Expand All @@ -188,7 +182,29 @@ func (a *Applet) RunWithConfig(ctx context.Context, config map[string]string) (r
i++
}
} else {
return nil, fmt.Errorf("expected app implementation to return Root(s) but found: %s", returnValue.Type())
return nil, fmt.Errorf("expected app implementation to return Root(s) but found: %s", val.Type())
}

return roots, nil
}

// RunWithConfig exceutes the applet's main function, passing it configuration as a
// starlark dict. It returns the render roots that are returned by the applet.
func (a *Applet) RunWithConfig(ctx context.Context, config map[string]string) (roots []render.Root, err error) {
var args starlark.Tuple
if a.mainFun.NumParams() > 0 {
starlarkConfig := AppletConfig(config)
args = starlark.Tuple{starlarkConfig}
}

returnValue, err := a.Call(ctx, a.mainFun, args...)
if err != nil {
return nil, err
}

roots, err = ExtractRoots(returnValue)
if err != nil {
return nil, err
}

return roots, nil
Expand Down Expand Up @@ -220,7 +236,7 @@ func (app *Applet) CallSchemaHandler(ctx context.Context, handlerName, parameter
return options, nil

case schema.ReturnSchema:
sch, err := schema.FromStarlark(resultVal, app.globals[app.schemaFile])
sch, err := schema.FromStarlark(resultVal, app.Globals[app.schemaFile])
if err != nil {
return "", err
}
Expand Down Expand Up @@ -253,7 +269,7 @@ func (app *Applet) RunTests(t *testing.T) {
return thread
})

for file, globals := range app.globals {
for file, globals := range app.Globals {
for name, global := range globals {
if !strings.HasPrefix(name, "test_") {
continue
Expand Down Expand Up @@ -347,7 +363,7 @@ func (a *Applet) ensureLoaded(fsys fs.FS, pathToLoad string, currentlyLoading ..

// normalize path so that it can be used as a key
pathToLoad = path.Clean(pathToLoad)
if _, ok := a.globals[pathToLoad]; ok {
if _, ok := a.Globals[pathToLoad]; ok {
// already loaded, good to go
return nil
}
Expand Down Expand Up @@ -390,7 +406,7 @@ func (a *Applet) ensureLoaded(fsys fs.FS, pathToLoad string, currentlyLoading ..
return nil, err
}

if g, ok := a.globals[modulePath]; !ok {
if g, ok := a.Globals[modulePath]; !ok {
return nil, fmt.Errorf("module %s not loaded", modulePath)
} else {
return g, nil
Expand All @@ -416,17 +432,17 @@ func (a *Applet) ensureLoaded(fsys fs.FS, pathToLoad string, currentlyLoading ..
if err != nil {
return fmt.Errorf("starlark.ExecFile: %v", err)
}
a.globals[pathToLoad] = globals
a.Globals[pathToLoad] = globals

// if the file is in the root directory, check for the main function
// and schema function
mainFun, _ := globals["main"].(*starlark.Function)
if mainFun != nil {
if a.mainFile != "" {
return fmt.Errorf("multiple files with a main() function:\n- %s\n- %s", pathToLoad, a.mainFile)
if a.MainFile != "" {
return fmt.Errorf("multiple files with a main() function:\n- %s\n- %s", pathToLoad, a.MainFile)
}

a.mainFile = pathToLoad
a.MainFile = pathToLoad
a.mainFun = mainFun
}

Expand Down Expand Up @@ -454,7 +470,7 @@ func (a *Applet) ensureLoaded(fsys fs.FS, pathToLoad string, currentlyLoading ..
}

default:
a.globals[pathToLoad] = starlark.StringDict{
a.Globals[pathToLoad] = starlark.StringDict{
"file": &file.File{
FS: fsys,
Path: pathToLoad,
Expand Down
6 changes: 3 additions & 3 deletions runtime/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def main():
app, err := NewApplet(filename, []byte(src))
assert.NoError(t, err)

b := app.globals["test_box.star"]["b"]
b := app.Globals["test_box.star"]["b"]
assert.IsType(t, &render_runtime.Box{}, b)

widget := b.(*render_runtime.Box).AsRenderWidget()
Expand Down Expand Up @@ -196,7 +196,7 @@ def main():
app, err := NewApplet(filename, []byte(src))
assert.NoError(t, err)

txt := app.globals["test_text.star"]["t"]
txt := app.Globals["test_text.star"]["t"]
assert.IsType(t, &render_runtime.Text{}, txt)

widget := txt.(*render_runtime.Text).AsRenderWidget()
Expand Down Expand Up @@ -240,7 +240,7 @@ def main():
app, err := NewApplet(filename, []byte(src))
assert.NoError(t, err)

starlarkP := app.globals["test_png.star"]["img"]
starlarkP := app.Globals["test_png.star"]["img"]
require.IsType(t, &render_runtime.Image{}, starlarkP)

actualIm := render.PaintWidget(starlarkP.(*render_runtime.Image).AsRenderWidget(), image.Rect(0, 0, 64, 32), 0)
Expand Down
2 changes: 1 addition & 1 deletion schema/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func newSchema(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple
)
}

s.Schema.Notifications = append(s.Schema.Notifications, n.AsSchemaField())
s.Schema.Notifications = append(s.Schema.Notifications, *n)
}
}

Expand Down
19 changes: 13 additions & 6 deletions schema/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

type Notification struct {
SchemaField
Builder *starlark.Function `json:"-"`
starlarkSounds *starlark.List
}

Expand All @@ -19,11 +20,12 @@ func newNotification(
kwargs []starlark.Tuple,
) (starlark.Value, error) {
var (
id starlark.String
name starlark.String
desc starlark.String
icon starlark.String
sounds *starlark.List
id starlark.String
name starlark.String
desc starlark.String
icon starlark.String
sounds *starlark.List
builder *starlark.Function
)

if err := starlark.UnpackArgs(
Expand All @@ -34,6 +36,7 @@ func newNotification(
"desc", &desc,
"icon", &icon,
"sounds", &sounds,
"builder", &builder,
); err != nil {
return nil, fmt.Errorf("unpacking arguments for Notification: %s", err)
}
Expand All @@ -44,6 +47,7 @@ func newNotification(
s.Name = name.GoString()
s.Description = desc.GoString()
s.Icon = icon.GoString()
s.Builder = builder

var soundVal starlark.Value
soundIter := sounds.Iterate()
Expand Down Expand Up @@ -75,7 +79,7 @@ func (s *Notification) AsSchemaField() SchemaField {

func (s *Notification) AttrNames() []string {
return []string{
"id", "name", "desc", "icon", "sounds",
"id", "name", "desc", "icon", "sounds", "builder",
}
}

Expand All @@ -97,6 +101,9 @@ func (s *Notification) Attr(name string) (starlark.Value, error) {
case "sounds":
return s.starlarkSounds, nil

case "builder":
return s.Builder, nil

default:
return nil, nil
}
Expand Down
2 changes: 2 additions & 0 deletions schema/notification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing/fstest"

"github.com/stretchr/testify/assert"

"tidbyt.dev/pixlet/runtime"
)

Expand All @@ -29,6 +30,7 @@ s = schema.Notification(
desc = "A new message has arrived",
icon = "message",
sounds = sounds,
builder = lambda: None,
)

assert.eq(s.id, "notification1")
Expand Down
10 changes: 6 additions & 4 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ const (
// Schema holds a configuration object for an applet. It holds a list of fields
// that are exported from an applet.
type Schema struct {
Version string `json:"version" validate:"required"`
Fields []SchemaField `json:"schema" validate:"dive"`
Notifications []SchemaField `json:"notifications,omitempty" validate:"dive"`
Version string `json:"version" validate:"required"`
Fields []SchemaField `json:"schema" validate:"dive"`
Notifications []Notification `json:"notifications,omitempty" validate:"dive"`

Handlers map[string]SchemaHandler `json:"-"`
}
Expand Down Expand Up @@ -107,7 +107,7 @@ func (s Schema) MarshalJSON() ([]byte, error) {
a.Fields = make([]SchemaField, 0)
}
if a.Notifications == nil {
a.Notifications = make([]SchemaField, 0)
a.Notifications = make([]Notification, 0)
}

js, err := json.Marshal(a)
Expand Down Expand Up @@ -165,6 +165,8 @@ func FromStarlark(
if schemaField.StarlarkHandler != nil {
handlerFun = schemaField.StarlarkHandler
} else if schemaField.Handler != "" {
// legacy schema, where the handler was a string instead of
// a function reference
handlerValue, ok := globals[schemaField.Handler]
if !ok {
return nil, fmt.Errorf(
Expand Down
29 changes: 18 additions & 11 deletions schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def typeaheadhandler():
def oauth2handler():
return "a-refresh-token"

def build_notification():
return None

def get_schema():
return schema.Schema(
version = "1",
Expand All @@ -49,6 +52,7 @@ def get_schema():
name = "Notification",
desc = "A Notification",
icon = "notification",
builder = build_notification,
sounds = [
schema.Sound(
id = "ding",
Expand Down Expand Up @@ -154,18 +158,20 @@ def main():
assert.Equal(t, schema.Schema{
Version: "1",

Notifications: []schema.SchemaField{
Notifications: []schema.Notification{
{
Type: "notification",
ID: "notificationid",
Name: "Notification",
Description: "A Notification",
Icon: "notification",
Sounds: []schema.SchemaSound{
{
ID: "ding",
Title: "Ding!",
Path: "ding.mp3",
SchemaField: schema.SchemaField{
Type: "notification",
ID: "notificationid",
Name: "Notification",
Description: "A Notification",
Icon: "notification",
Sounds: []schema.SchemaSound{
{
ID: "ding",
Title: "Ding!",
Path: "ding.mp3",
},
},
},
},
Expand Down Expand Up @@ -307,6 +313,7 @@ def get_schema():
name = "Notification",
desc = "A Notification",
icon = "notification",
builder = lambda: None,
sounds = [
schema.Sound(
id = "ding",
Expand Down
Loading