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 architecture #347

Merged
merged 8 commits into from
Jan 3, 2023
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
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ example.scratch.watch:
@ watch -- $(MAKE) example.scratch

example.hn:
@ (cd example/hn && npm link ../../livebud)
@ go run main.go -C example/hn run
@ # (cd example/hn && npm link ../../livebud)
@ go run main.go -log=debug -C example/hn generate

example.hn.embed:
@ (cd example/hn && npm link ../../livebud)
Expand Down
2 changes: 2 additions & 0 deletions example/hn/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ require (
github.com/ajg/form v1.5.2-0.20200323032839-9aeb3cf462e1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/evanw/esbuild v0.14.11 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/matthewmueller/gotext v0.0.0-20210424201144-265ed61725ac // indirect
github.com/matthewmueller/text v0.0.0-20210424201111-ec1e4af8dfe8 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/timewasted/go-accept-headers v0.0.0-20130320203746-c78f304b1b09 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
Expand Down
3 changes: 3 additions & 0 deletions example/hn/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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/evanw/esbuild v0.14.11 h1:bw50N4v70Dqf/B6Wn+3BM6BVttz4A6tHn8m8Ydj9vxk=
github.com/evanw/esbuild v0.14.11/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 h1:Uc+IZ7gYqAf/rSGFplbWBSHaGolEQlNLgMgSE3ccnIQ=
Expand Down Expand Up @@ -50,6 +51,8 @@ github.com/matthewmueller/hackernews v0.3.0/go.mod h1:LLJX9jtOV7MNmPN0AWn2E9Rg4z
github.com/matthewmueller/text v0.0.0-20201215225457-a00346c71bb3/go.mod h1:vtPaEU72VzARd4tSSzHIX7DddCEamoO2X4ozlrdmtNY=
github.com/matthewmueller/text v0.0.0-20210424201111-ec1e4af8dfe8 h1:XTmVlF7P9bpSNkLFlxpNlhig0kaVJ5mO4D3yK2CYjmM=
github.com/matthewmueller/text v0.0.0-20210424201111-ec1e4af8dfe8/go.mod h1:vtPaEU72VzARd4tSSzHIX7DddCEamoO2X4ozlrdmtNY=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
Expand Down
129 changes: 129 additions & 0 deletions framework/afs/afs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package afs

import (
_ "embed"
"fmt"
"io/fs"

"github.com/livebud/bud/framework"
"github.com/livebud/bud/internal/bail"
"github.com/livebud/bud/internal/gotemplate"
"github.com/livebud/bud/internal/imports"
"github.com/livebud/bud/package/di"
"github.com/livebud/bud/package/genfs"
"github.com/livebud/bud/package/gomod"
"github.com/livebud/bud/package/log"
)

//go:embed afs.gotext
var template string

var generator = gotemplate.MustParse("framework/afs/afs.gotext", template)

func Generate(state *State) ([]byte, error) {
return generator.Generate(state)
}

func New(flag *framework.Flag, injector *di.Injector, log log.Log, module *gomod.Module) *Generator {
return &Generator{flag, injector, log, module}
}

type Generator struct {
flag *framework.Flag
injector *di.Injector
log log.Log
module *gomod.Module
}

func (g *Generator) GenerateFile(fsys genfs.FS, file *genfs.File) error {
if _, err := fs.Stat(fsys, "bud/internal/generator/generator.go"); err != nil {
return err
}
state, err := Load(g.injector, g.log, g.module)
if err != nil {
return fmt.Errorf("framework/afs: unable to load state %w", err)
}
code, err := Generate(state)
if err != nil {
return err
}
file.Data = code
return nil
}

func Load(injector *di.Injector, log log.Log, module *gomod.Module) (*State, error) {
loader := &loader{
injector: injector,
imports: imports.New(),
module: module,
}
return loader.Load()
}

type loader struct {
injector *di.Injector
module *gomod.Module

imports *imports.Set
bail.Struct
}

type State struct {
Imports []*imports.Import
Provider *di.Provider
}

func (l *loader) Load() (state *State, err error) {
defer l.Recover2(&err, "afs")
state = new(State)
state.Provider = l.loadProvider()
l.imports.AddStd("context", "errors", "os")
l.imports.AddNamed("commander", "github.com/livebud/bud/package/commander")
l.imports.AddNamed("afsrt", "github.com/livebud/bud/framework/afs/afsrt")
l.imports.AddNamed("console", "github.com/livebud/bud/package/log/console")
l.imports.AddNamed("genfs", "github.com/livebud/bud/package/genfs")
l.imports.AddNamed("parser", "github.com/livebud/bud/package/parser")
state.Imports = l.imports.List()
return state, nil
}

func (l *loader) loadProvider() *di.Provider {
jsVM := di.ToType("github.com/livebud/bud/package/js", "VM")
// TODO: the public generator should be able to configure this
publicFS := di.ToType("github.com/livebud/bud/framework/public/publicrt", "FS")
viewFS := di.ToType("github.com/livebud/bud/framework/view/viewrt", "FS")
provider, err := l.injector.Wire(&di.Function{
Name: "loadGeneratorFS",
Imports: l.imports,
Target: l.module.Import("bud", "cmd", "afs"),
Params: []*di.Param{
{Import: "github.com/livebud/bud/package/log", Type: "Log"},
{Import: "github.com/livebud/bud/package/gomod", Type: "*Module"},
{Import: "github.com/livebud/bud/framework", Type: "*Flag"},
{Import: "github.com/livebud/bud/package/genfs", Type: "*FileSystem"},
{Import: "github.com/livebud/bud/package/di", Type: "*Injector"},
{Import: "github.com/livebud/bud/package/parser", Type: "*Parser"},
{Import: "context", Type: "Context"},
{Import: "github.com/livebud/bud/package/budhttp", Type: "Client"},
},
Aliases: di.Aliases{
publicFS: di.ToType("github.com/livebud/bud/package/budhttp", "Client"),
viewFS: di.ToType("github.com/livebud/bud/package/budhttp", "Client"),
jsVM: di.ToType("github.com/livebud/bud/package/budhttp", "Client"),
},
Results: []di.Dependency{
di.ToType(l.module.Import("bud/internal/generator"), "FS"),
&di.Error{},
},
})
if err != nil {
fmt.Println(l.module.Import("bud/internal/generator"))
// Intentionally don't wrap this error, it gets swallowed up too easily
l.Bail(fmt.Errorf("afs: unable to wire. %s", err))
}
// Add generated imports
for _, imp := range provider.Imports {
l.imports.AddNamed(imp.Name, imp.Path)
}
return provider
}
86 changes: 86 additions & 0 deletions framework/afs/afs.gotext
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

// GENERATED BY BUD. DO NOT EDIT.

{{- if $.Imports }}

import (
{{- range $import := $.Imports }}
{{$import.Name}} "{{$import.Path}}"
{{- end }}
)
{{- end }}

// main entrypoint
func main() {
ctx := context.Background()
if err := run(ctx); err != nil {
if errors.Is(err, context.Canceled) {
return
}
console.Error(err.Error())
os.Exit(1)
}
}

func run(ctx context.Context) error {
cmd := new(Command)
cli := commander.New("afs")
cli.Flag("chdir", "change the working directory").Short('C').String(&cmd.Dir).Default(".")
cli.Flag("embed", "embed assets").Bool(&cmd.Flag.Embed).Default(false)
cli.Flag("hot", "hot reloading").Bool(&cmd.Flag.Hot).Default(true)
cli.Flag("minify", "minify assets").Bool(&cmd.Flag.Minify).Default(false)
cli.Flag("listen", "address to serve from").String(&cmd.Listen).Default(":65000")
cli.Flag("log", "filter logs with this pattern").Short('L').String(&cmd.Log).Default("info")
cli.Run(cmd.Serve)
return cli.Parse(ctx, os.Args[1:])
}

type Command struct {
Dir string
Flag framework.Flag
Listen string
Log string
}


// Serve the filesystem
func (c *Command) Serve(ctx context.Context) (err error) {
module, err := gomod.Find(c.Dir)
if err != nil {
return err
}
log, err := afsrt.Logger(c.Log)
if err != nil {
return err
}
gfs, err := afsrt.GenFS(module, log)
if err != nil {
return err
}
parser := parser.New(gfs, module)
injector := di.New(gfs, log, module, parser)
_ = injector
budClient, err := budhttp.Try(log, os.Getenv("BUD_LISTEN"))
if err != nil {
return err
}
fsys, err := {{ $.Provider.Name }}(
{{- /* Order matters. Ordered by package name (e.g. budfs.*Server > context.Context) */}}
{{- if $.Provider.Variable "github.com/livebud/bud/package/budhttp.Client" }}budClient,{{ end }}
{{- if $.Provider.Variable "context.Context" }}ctx,{{ end }}
{{- if $.Provider.Variable "github.com/livebud/bud/package/di.*Injector" }}injector,{{ end }}
{{- if $.Provider.Variable "github.com/livebud/bud/framework.*Flag" }}&c.Flag,{{ end }}
{{- if $.Provider.Variable "github.com/livebud/bud/package/genfs.*FileSystem" }}gfs,{{ end }}
{{- if $.Provider.Variable "github.com/livebud/bud/package/gomod.*Module" }}module,{{ end }}
{{- if $.Provider.Variable "github.com/livebud/bud/package/log.Log" }}log,{{ end }}
{{- if $.Provider.Variable "github.com/livebud/bud/package/parser.*Parser" }}parser,{{ end }}
)
if err != nil {
return err
}
return afsrt.Serve(ctx, log, fsys, c.Listen)
}

// {{ $.Provider.Name }} provides the generator filesystem
{{ $.Provider.Function }}
82 changes: 82 additions & 0 deletions framework/afs/afsrt/appfsrt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package afsrt

import (
"context"
"fmt"
"io/fs"
"os"
"strings"

"github.com/livebud/bud/internal/dag"
"github.com/livebud/bud/package/genfs"
"github.com/livebud/bud/package/virtual"

"golang.org/x/sync/errgroup"

"github.com/livebud/bud/package/gomod"
"github.com/livebud/bud/package/log"
"github.com/livebud/bud/package/log/console"
"github.com/livebud/bud/package/log/levelfilter"
"github.com/livebud/bud/package/remotefs"

"github.com/livebud/bud/internal/extrafile"
"github.com/livebud/bud/package/socket"
)

func Logger(level string) (log.Log, error) {
lvl, err := log.ParseLevel(level)
if err != nil {
return nil, err
}
logger := log.New(levelfilter.New(console.New(os.Stderr), lvl))
return logger, nil
}

// GenFS creates a new filesystem
func GenFS(module *gomod.Module, log log.Log) (*genfs.FileSystem, error) {
fsys := virtual.Exclude(module, func(path string) bool {
return path == "bud" || strings.HasPrefix(path, "bud/")
})
cache, err := dag.Load(log, module.Directory("bud/bud.db"))
if err != nil {
return nil, fmt.Errorf("afs: unable to load cache. %w", err)
}
return genfs.New(cache, fsys, log), nil
}

// Serve the remote filesystem
func Serve(ctx context.Context, log log.Log, fsys fs.FS, path string) error {
// First try to load the listener from the parent process.
ln, err := listen(log, path)
if err != nil {
return err
}
eg, ctx := errgroup.WithContext(ctx)
// Handle any immediate errors from remotefs
eg.Go(func() error {
return remotefs.Serve(fsys, ln)
})
// Any errors in the group will trigger ctx to be canceled, closing the
// listener. The listener will also be closed if the outside context is
// canceled.
eg.Go(func() error {
<-ctx.Done()
return ln.Close()
})
// Wait for both goroutines to finish
return eg.Wait()
}

func listen(log log.Log, path string) (socket.Listener, error) {
files := extrafile.Load("BUD_REMOTEFS")
if len(files) > 0 {
log.Debug("afs: serving from BUD_REMOTEFS file listener passed in from the parent process")
return socket.From(files[0])
}
ln, err := socket.ListenUp(path, 5)
if err != nil {
return nil, err
}
log.Debug("afs: serving from %s", ln.Addr())
return ln, nil
}
4 changes: 2 additions & 2 deletions framework/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
_ "embed"

"github.com/livebud/bud/framework"
"github.com/livebud/bud/package/budfs"
"github.com/livebud/bud/package/di"
"github.com/livebud/bud/package/genfs"

"github.com/livebud/bud/internal/gotemplate"
"github.com/livebud/bud/package/gomod"
Expand All @@ -30,7 +30,7 @@ type Generator struct {
module *gomod.Module
}

func (g *Generator) GenerateFile(fsys budfs.FS, file *budfs.File) error {
func (g *Generator) GenerateFile(fsys genfs.FS, file *genfs.File) error {
state, err := Load(fsys, g.injector, g.module, g.flag)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion framework/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestWelcome(t *testing.T) {
is.Equal(res.Status(), 200)
is.In(res.Body().String(), "Hey Bud")
is.Equal(app.Stdout(), "")
is.Equal(app.Stderr(), "")
// is.Equal(app.Stderr(), "")
is.NoErr(td.Exists("bud/app"))
is.NoErr(app.Close())
}
4 changes: 2 additions & 2 deletions framework/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"fmt"

"github.com/livebud/bud/internal/gotemplate"
"github.com/livebud/bud/package/budfs"
"github.com/livebud/bud/package/di"
"github.com/livebud/bud/package/genfs"
"github.com/livebud/bud/package/gomod"
"github.com/livebud/bud/package/parser"
)
Expand All @@ -36,7 +36,7 @@ type Generator struct {
parser *parser.Parser
}

func (g *Generator) GenerateFile(fsys budfs.FS, file *budfs.File) error {
func (g *Generator) GenerateFile(fsys genfs.FS, file *genfs.File) error {
state, err := Load(fsys, g.injector, g.module, g.parser)
if err != nil {
return fmt.Errorf("framework/controller: unable to load. %w", err)
Expand Down
1 change: 0 additions & 1 deletion framework/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1984,7 +1984,6 @@ func TestSameNestedName(t *testing.T) {

"/users"
`)
is.NoErr(app.Close())
res, err = app.Get("/admins/10/users")
is.NoErr(err)
res.Diff(`
Expand Down
Loading