Skip to content

Commit d21d873

Browse files
committed
refactor plugin architecture
Signed-off-by: Bryce Palmer <bpalmer@redhat.com>
1 parent f1a7545 commit d21d873

17 files changed

Lines changed: 620 additions & 627 deletions

File tree

examples/simple.star

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github("everettraven", "synkr")

pkg/builtins/github.go

Lines changed: 0 additions & 56 deletions
This file was deleted.

pkg/builtins/utils.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package builtins
2+
3+
import "go.starlark.net/starlark"
4+
5+
func TypeFromStarlarkList[T any](list *starlark.List) []T{
6+
types := []T{}
7+
8+
if list != nil {
9+
for v := range list.Elements() {
10+
if typeInstance, ok := v.(T); ok {
11+
types = append(types, typeInstance)
12+
}
13+
}
14+
}
15+
16+
return types
17+
}

pkg/cmd/root.go

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ import (
66
"os"
77
"path/filepath"
88

9-
"github.com/everettraven/synkr/pkg/builtins"
109
"github.com/everettraven/synkr/pkg/engine"
10+
"github.com/everettraven/synkr/pkg/plugins"
1111
"github.com/everettraven/synkr/pkg/printers"
1212
"github.com/spf13/cobra"
1313
"go.starlark.net/lib/time"
1414
"go.starlark.net/starlark"
1515
"go.starlark.net/syntax"
16+
17+
_ "github.com/everettraven/synkr/pkg/plugins/registration"
1618
)
1719

1820
func NewSynkrCommand() *cobra.Command {
19-
eng := &engine.Engine{}
2021
var configFile string
2122
var outputFormat string
2223

@@ -25,7 +26,7 @@ func NewSynkrCommand() *cobra.Command {
2526
Short: "synkr is an engine for syncing work items based on a Starlark configuration",
2627
Args: cobra.ExactArgs(0),
2728
RunE: func(cmd *cobra.Command, args []string) error {
28-
return run(cmd.Context(), eng, configFile, outputFormat)
29+
return run(cmd.Context(), configFile, outputFormat)
2930
},
3031
}
3132

@@ -35,31 +36,46 @@ func NewSynkrCommand() *cobra.Command {
3536
return cmd
3637
}
3738

38-
func run(ctx context.Context, eng *engine.Engine, configFile, output string) error {
39-
thread, err := configureEngine(eng, configFile, output)
39+
func run(ctx context.Context, configFile, output string) error {
40+
plugins := plugins.Plugins()
41+
thread, err := configureThread(configFile, plugins...)
42+
if err != nil {
43+
return fmt.Errorf("configuring thread: %w", err)
44+
}
45+
46+
eng := engine.New(plugins...)
47+
48+
results, err := eng.Run(ctx, thread)
4049
if err != nil {
41-
return fmt.Errorf("configuring engine: %w", err)
50+
return fmt.Errorf("running engine: %w", err)
4251
}
4352

44-
return eng.Run(ctx, thread)
53+
return printResults(output, results...)
4554
}
4655

47-
func configureEngine(eng *engine.Engine, configFile, output string) (*starlark.Thread, error) {
56+
func printResults(output string, results ...plugins.SourceResult) error {
4857
switch output {
49-
case "markdown":
50-
eng.SetPrinter(&printers.Markdown{})
5158
case "json":
52-
eng.SetPrinter(&printers.JSON{})
59+
out := &printers.JSON{}
60+
return out.Print(results...)
61+
case "markdown":
62+
out := &printers.Markdown{}
63+
return out.Print(results...)
5364
case "web":
54-
eng.SetPrinter(&printers.Web{})
65+
out := &printers.Web{}
66+
return out.Print(results...)
5567
default:
56-
return nil, fmt.Errorf("unknown output format %q", output)
68+
return fmt.Errorf("unknown output format %q", output)
5769
}
70+
}
5871

72+
func configureThread(configFile string, plugins ...plugins.Plugin) (*starlark.Thread, error) {
5973
globals := starlark.StringDict{}
6074
starlark.Universe["time"] = time.Module
6175

62-
builtins.Github(globals, eng)
76+
for _, plugin := range plugins {
77+
plugin.RegisterBuiltins(globals)
78+
}
6379

6480
thread := &starlark.Thread{Name: "main"}
6581

pkg/engine/engine.go

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,64 @@ package engine
33
import (
44
"context"
55
"fmt"
6+
"slices"
67

8+
"github.com/everettraven/synkr/pkg/plugins"
79
"go.starlark.net/starlark"
810
)
911

1012
type Engine struct {
11-
sources []Source
12-
printer Printer
13+
plugins []plugins.Plugin
1314
}
1415

15-
type Source interface {
16-
Fetch(context.Context, *starlark.Thread) ([]any, error)
17-
Name() string
18-
Project() string
16+
func New(plugins ...plugins.Plugin) *Engine {
17+
return &Engine{
18+
plugins: plugins,
19+
}
1920
}
2021

21-
type Printer interface {
22-
Print(...SourceResult) error
23-
}
22+
// TODO: Pulling data from sources should be asynchronous to improve performance
23+
func (e *Engine) Run(ctx context.Context, thread *starlark.Thread) ([]plugins.SourceResult, error) {
24+
results := []plugins.SourceResult{}
2425

25-
type SourceResult struct {
26-
Source string `json:"source"`
27-
Project string `json:"project"`
28-
Items []any `json:"items"`
29-
}
26+
for _, plugin := range e.plugins {
27+
for _, source := range plugin.Sources() {
28+
result, err := source.Fetch(ctx, thread)
29+
if err != nil {
30+
return nil, fmt.Errorf("fetching data for source %q: %w", source.Name(), err)
31+
}
3032

31-
func (e *Engine) Run(ctx context.Context, thread *starlark.Thread) error {
32-
outputs := []SourceResult{}
33-
for _, source := range e.sources {
34-
items, err := source.Fetch(ctx, thread)
35-
if err != nil {
36-
return err
37-
}
33+
if result == nil {
34+
continue
35+
}
36+
37+
if ind := slices.IndexFunc(results, func(e plugins.SourceResult) bool {
38+
return e.Source == result.Source && e.Project == result.Project
39+
}); ind != -1 {
40+
results[ind].Items = appendUniqueResults(results[ind].Items, result.Items...)
41+
continue
42+
}
3843

39-
outputs = append(outputs, SourceResult{
40-
Source: source.Name(),
41-
Project: source.Project(),
42-
Items: items,
43-
})
44-
}
4544

46-
err := e.printer.Print(outputs...)
47-
if err != nil {
48-
return fmt.Errorf("printing source results: %w", err)
45+
results = append(results, *result)
46+
}
4947
}
5048

51-
return nil
49+
return results, nil
5250
}
5351

54-
func (e *Engine) AddSource(source Source) {
55-
e.sources = append(e.sources, source)
56-
}
52+
func appendUniqueResults(base []plugins.SourceEntry, toAppends ...plugins.SourceEntry) []plugins.SourceEntry {
53+
out := append([]plugins.SourceEntry{}, base...)
54+
55+
for _, toAppend := range toAppends {
56+
if slices.ContainsFunc(out, func(e plugins.SourceEntry) bool {
57+
return e.Identifier() == toAppend.Identifier()
58+
}) {
59+
continue
60+
}
61+
62+
out = append(out, toAppend)
63+
}
5764

58-
func (e *Engine) SetPrinter(printer Printer) {
59-
e.printer = printer
65+
return out
6066
}

pkg/plugins/github/builtin.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package github
2+
3+
import (
4+
"github.com/everettraven/synkr/pkg/builtins"
5+
"github.com/google/go-github/v71/github"
6+
"go.starlark.net/starlark"
7+
)
8+
9+
func GithubBuiltinFunc(sourcer *Sourcer) builtins.BuiltinFunc {
10+
return func(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
11+
var filters *starlark.List
12+
var priorities *starlark.List
13+
var status starlark.Callable
14+
var org starlark.String
15+
var repo starlark.String
16+
17+
// GitHub issue list filters
18+
var milestone starlark.String
19+
var state starlark.String
20+
var assignee starlark.String
21+
var creator starlark.String
22+
var mentioned starlark.String
23+
var labels *starlark.List
24+
var sort starlark.String
25+
var direction starlark.String
26+
27+
err := starlark.UnpackArgs("github", args, kwargs,
28+
"org", &org,
29+
"repo", &repo,
30+
"milestone?", &milestone,
31+
"state?", &state,
32+
"assignee?", &assignee,
33+
"creator?", &creator,
34+
"mentioned?", &mentioned,
35+
"labels?", &labels,
36+
"sort?", &sort,
37+
"direction?", &direction,
38+
"filters?", &filters,
39+
"priorities?", &priorities,
40+
"status?", &status,
41+
)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
filterCallables := []starlark.Callable{}
47+
if filters != nil {
48+
filterCallables = builtins.TypeFromStarlarkList[starlark.Callable](filters)
49+
}
50+
51+
priorityCallables := []starlark.Callable{}
52+
if priorities != nil {
53+
priorityCallables = builtins.TypeFromStarlarkList[starlark.Callable](priorities)
54+
}
55+
56+
listOpts := &github.IssueListByRepoOptions{
57+
Milestone: milestone.GoString(),
58+
State: state.GoString(),
59+
Assignee: assignee.GoString(),
60+
Creator: creator.GoString(),
61+
Mentioned: mentioned.GoString(),
62+
Labels: builtins.TypeFromStarlarkList[string](labels),
63+
Sort: sort.GoString(),
64+
Direction: direction.GoString(),
65+
}
66+
67+
ghSource := NewSource(org.GoString(), repo.GoString(), filterCallables, priorityCallables, status, listOpts)
68+
sourcer.AddSource(ghSource)
69+
70+
return starlark.None, nil
71+
}
72+
}
73+
74+
func ptr[T any](in T) *T {
75+
return &in
76+
}

pkg/plugins/github/github.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package github
2+
3+
import (
4+
"github.com/everettraven/synkr/pkg/plugins"
5+
"go.starlark.net/starlark"
6+
)
7+
8+
func init() {
9+
plugins.Register(New())
10+
}
11+
12+
func New() plugins.Plugin {
13+
sourcer := &Sourcer{
14+
sources: make([]plugins.Source, 0),
15+
}
16+
return plugins.Plugin{
17+
Sourcer: sourcer,
18+
Builtins: map[string]*starlark.Builtin{
19+
"github": starlark.NewBuiltin("github", GithubBuiltinFunc(sourcer)),
20+
},
21+
Name: "github",
22+
}
23+
}

0 commit comments

Comments
 (0)