From 6552a9be3010aa9600e694ac96da467302ab4ab0 Mon Sep 17 00:00:00 2001 From: Matthew Mueller Date: Sun, 27 Mar 2022 14:13:23 -0500 Subject: [PATCH] pass the flags through. embed public files --- internal/bud/bud.go | 6 +- internal/bud/flag.go | 22 ------- internal/bud/process.go | 60 ------------------ internal/bud/project.go | 32 +++++----- internal/budtest/compiler.go | 23 +++++-- internal/command/build/build.go | 2 +- internal/command/command.go | 5 +- internal/command/run/run.go | 2 +- internal/generator/command/command.gotext | 8 +-- internal/generator/command/parser.go | 2 +- internal/generator/program/program.go | 11 ++-- internal/generator/program/program.gotext | 13 +++- package/conjure/embedg.go | 11 ++++ package/exe/command.go | 76 +++++++++++++++++++++++ package/overlay/embed.go | 20 ++++++ package/overlay/file.go | 3 +- package/overlay/overlay.go | 2 + {internal => runtime}/bud/app.go | 16 ++--- runtime/bud/flag.go | 18 ++++++ runtime/{project => bud}/project.go | 19 ++---- runtime/command/build/build.go | 6 +- runtime/command/run/run.go | 6 +- runtime/generator/action/action_test.go | 35 +++++------ runtime/generator/public/loader.go | 64 ++++++++++++++----- runtime/generator/public/public.go | 19 +++--- runtime/generator/public/public.gotext | 45 ++++++-------- runtime/generator/public/public_test.go | 72 +++++++++++++++++++++ runtime/generator/public/state.go | 9 ++- 28 files changed, 375 insertions(+), 232 deletions(-) delete mode 100644 internal/bud/flag.go delete mode 100644 internal/bud/process.go create mode 100644 package/exe/command.go create mode 100644 package/overlay/embed.go rename {internal => runtime}/bud/app.go (81%) create mode 100644 runtime/bud/flag.go rename runtime/{project => bud}/project.go (73%) diff --git a/internal/bud/bud.go b/internal/bud/bud.go index 2d073461..853aa8a1 100644 --- a/internal/bud/bud.go +++ b/internal/bud/bud.go @@ -19,6 +19,7 @@ import ( "gitlab.com/mnm/bud/package/overlay" "gitlab.com/mnm/bud/package/parser" "gitlab.com/mnm/bud/package/trace" + "gitlab.com/mnm/bud/runtime/bud" ) func defaultEnv(module *gomod.Module) (Env, error) { @@ -123,7 +124,7 @@ func (c *Compiler) goBuild(ctx context.Context, module *gomod.Module, outPath st return nil } -func (c *Compiler) Compile(ctx context.Context, flag Flag) (p *Project, err error) { +func (c *Compiler) Compile(ctx context.Context, flag *bud.Flag) (p *Project, err error) { // Start the trace ctx, span := trace.Start(ctx, "compile project cli") defer span.End(&err) @@ -138,7 +139,7 @@ func (c *Compiler) Compile(ctx context.Context, flag Flag) (p *Project, err erro // Setup the generators overlay.FileGenerator("bud/import.go", importfile.New(c.module)) overlay.FileGenerator("bud/.cli/main.go", mainfile.New(c.module)) - overlay.FileGenerator("bud/.cli/program/program.go", program.New(injector, c.module)) + overlay.FileGenerator("bud/.cli/program/program.go", program.New(flag, injector, c.module)) overlay.FileGenerator("bud/.cli/command/command.go", command.New(overlay, c.module, parser)) overlay.FileGenerator("bud/.cli/generator/generator.go", generator.New(overlay, c.module, parser)) // Sync the generators @@ -155,7 +156,6 @@ func (c *Compiler) Compile(ctx context.Context, flag Flag) (p *Project, err erro } return &Project{ Module: c.module, - Flag: flag, Env: c.Env, Stdout: c.Stdout, Stderr: c.Stderr, diff --git a/internal/bud/flag.go b/internal/bud/flag.go deleted file mode 100644 index 8333d2ca..00000000 --- a/internal/bud/flag.go +++ /dev/null @@ -1,22 +0,0 @@ -package bud - -import ( - "strconv" -) - -type Flag struct { - Embed bool - Hot bool - Minify bool - // Cache bool -} - -func (f *Flag) List() []string { - args := []string{ - "--embed=" + strconv.FormatBool(f.Embed), - "--hot=" + strconv.FormatBool(f.Hot), - "--minify=" + strconv.FormatBool(f.Minify), - // "--cache=" + strconv.FormatBool(f.Cache), - } - return args -} diff --git a/internal/bud/process.go b/internal/bud/process.go deleted file mode 100644 index 0a7a8ed3..00000000 --- a/internal/bud/process.go +++ /dev/null @@ -1,60 +0,0 @@ -package bud - -import ( - "context" - "os" - "os/exec" - "strings" -) - -type Process struct { - cmd *exec.Cmd -} - -func (p *Process) Close() error { - sp := p.cmd.Process - if sp != nil { - if err := sp.Signal(os.Interrupt); err != nil { - sp.Kill() - } - } - if err := p.cmd.Wait(); err != nil { - if !isExitStatus(err) && !isWaitError(err) { - return err - } - } - return nil -} - -func (p *Process) Wait() error { - return p.cmd.Wait() -} - -func isExitStatus(err error) bool { - return err != nil && strings.Contains(err.Error(), "exit status ") -} - -func isWaitError(err error) bool { - return err != nil && strings.Contains(err.Error(), "Wait was already called") -} - -func (p *Process) Restart(ctx context.Context) error { - // Close the process first - if err := p.Close(); err != nil { - return err - } - // Re-run the command again. cmd.Args[0] is the path, so we skip that. - cmd := exec.CommandContext(ctx, p.cmd.Path, p.cmd.Args[1:]...) - cmd.Env = p.cmd.Env - cmd.Stdout = p.cmd.Stdout - cmd.Stderr = p.cmd.Stderr - cmd.Stdin = p.cmd.Stdin - cmd.ExtraFiles = p.cmd.ExtraFiles - cmd.Dir = p.cmd.Dir - if err := cmd.Start(); err != nil { - return err - } - // Point to the new command - p.cmd = cmd - return nil -} diff --git a/internal/bud/project.go b/internal/bud/project.go index 49b04a12..53b5f78c 100644 --- a/internal/bud/project.go +++ b/internal/bud/project.go @@ -4,26 +4,22 @@ import ( "context" "io" "net" - "os/exec" + "gitlab.com/mnm/bud/package/exe" "gitlab.com/mnm/bud/package/gomod" "gitlab.com/mnm/bud/package/socket" + "gitlab.com/mnm/bud/runtime/bud" ) type Project struct { Module *gomod.Module - Flag Flag Env Env Stdout io.Writer Stderr io.Writer } -func (p *Project) args(args ...string) []string { - return append(args, p.Flag.List()...) -} - -func (p *Project) command(ctx context.Context, args ...string) *exec.Cmd { - cmd := exec.CommandContext(ctx, p.Module.Directory("bud", "cli"), args...) +func (p *Project) command(ctx context.Context, args ...string) *exe.Cmd { + cmd := exe.Command(ctx, p.Module.Directory("bud", "cli"), args...) cmd.Dir = p.Module.Directory() cmd.Env = p.Env.List() cmd.Stderr = p.Stderr @@ -31,8 +27,8 @@ func (p *Project) command(ctx context.Context, args ...string) *exec.Cmd { return cmd } -func (p *Project) Executor(ctx context.Context, args ...string) *exec.Cmd { - return p.command(ctx, p.args(args...)...) +func (p *Project) Executor(ctx context.Context, args ...string) *exe.Cmd { + return p.command(ctx, args...) } // Execute a custom command @@ -45,16 +41,16 @@ func (p *Project) Execute(ctx context.Context, args ...string) error { return nil } -func (p *Project) Builder(ctx context.Context) *exec.Cmd { - return p.command(ctx, p.args("build")...) +func (p *Project) Builder(ctx context.Context) *exe.Cmd { + return p.command(ctx, "build") } -func (p *Project) Build(ctx context.Context) (*App, error) { +func (p *Project) Build(ctx context.Context) (*bud.App, error) { cmd := p.Builder(ctx) if err := cmd.Run(); err != nil { return nil, err } - return &App{ + return &bud.App{ Module: p.Module, Env: p.Env.List(), Stderr: p.Stderr, @@ -62,19 +58,19 @@ func (p *Project) Build(ctx context.Context) (*App, error) { }, nil } -func (p *Project) Runner(ctx context.Context, listener net.Listener) (*exec.Cmd, error) { +func (p *Project) Runner(ctx context.Context, listener net.Listener) (*exe.Cmd, error) { // Pass the socket through files, env, err := socket.Files(listener) if err != nil { return nil, err } - cmd := p.command(ctx, p.args("run")...) + cmd := p.command(ctx, "run") cmd.Env = append(p.Env.List(), string(env)) cmd.ExtraFiles = append(cmd.ExtraFiles, files...) return cmd, nil } -func (p *Project) Run(ctx context.Context, listener net.Listener) (*Process, error) { +func (p *Project) Run(ctx context.Context, listener net.Listener) (*exe.Cmd, error) { cmd, err := p.Runner(ctx, listener) if err != nil { return nil, err @@ -82,5 +78,5 @@ func (p *Project) Run(ctx context.Context, listener net.Listener) (*Process, err if err := cmd.Start(); err != nil { return nil, err } - return &Process{cmd}, nil + return cmd, nil } diff --git a/internal/budtest/compiler.go b/internal/budtest/compiler.go index 6ccf1279..bc989d60 100644 --- a/internal/budtest/compiler.go +++ b/internal/budtest/compiler.go @@ -21,15 +21,17 @@ import ( "github.com/matthewmueller/diff" "gitlab.com/mnm/bud/internal/bud" "gitlab.com/mnm/bud/internal/testdir" + "gitlab.com/mnm/bud/package/exe" "gitlab.com/mnm/bud/package/gomod" "gitlab.com/mnm/bud/package/modcache" "gitlab.com/mnm/bud/package/socket" + runtime_bud "gitlab.com/mnm/bud/runtime/bud" ) func New(dir string) *Compiler { return &Compiler{ dir: dir, - Flag: bud.Flag{ + Flag: runtime_bud.Flag{ Embed: false, Minify: false, Hot: true, @@ -57,7 +59,7 @@ func New(dir string) *Compiler { type Compiler struct { dir string - Flag bud.Flag + Flag runtime_bud.Flag Files map[string]string // String files (convenient) BFiles map[string][]byte // Byte files (for images and binaries) Modules modcache.Modules // name@version[path[data]] @@ -95,7 +97,7 @@ func (c *Compiler) Compile(ctx context.Context) (p *Project, err error) { } compiler.Env = c.Env compiler.ModCacheRW = true - project, err := compiler.Compile(ctx, c.Flag) + project, err := compiler.Compile(ctx, &c.Flag) if err != nil { return nil, err } @@ -194,7 +196,7 @@ func (p *Project) Run(ctx context.Context) (*Server, error) { type App struct { module *gomod.Module - app *bud.App + app *runtime_bud.App } func (a *App) Exists(paths ...string) error { @@ -296,7 +298,7 @@ func (s Stdio) String() string { } type Server struct { - process *bud.Process + process *exe.Cmd listener net.Listener client *http.Client } @@ -450,6 +452,17 @@ func (r *Response) ExpectHeaders(expect string) error { return diffHTTP(expect, dump) } +func (r *Response) ContainsBody(expect string) error { + body, err := io.ReadAll(r.Response.Body) + if err != nil { + return err + } + if strings.Contains(string(body), expect) { + return nil + } + return fmt.Errorf("%s does not contain %q", string(body), expect) +} + func (r *Response) Query(selector string) (*goquery.Selection, error) { doc, err := goquery.NewDocumentFromReader(r.Body) if err != nil { diff --git a/internal/command/build/build.go b/internal/command/build/build.go index b47f570c..fc7f0f9f 100644 --- a/internal/command/build/build.go +++ b/internal/command/build/build.go @@ -26,7 +26,7 @@ func (c *Command) Run(ctx context.Context) error { return err } // Compile the project CLI - project, err := compiler.Compile(ctx, c.Bud.Flag) + project, err := compiler.Compile(ctx, &c.Bud.Flag) if err != nil { return err } diff --git a/internal/command/command.go b/internal/command/command.go index f0b72006..f37f19a5 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -8,11 +8,12 @@ import ( "gitlab.com/mnm/bud/internal/bud" "gitlab.com/mnm/bud/package/trace" + runtime_bud "gitlab.com/mnm/bud/runtime/bud" ) // Bud command type Bud struct { - Flag bud.Flag + Flag runtime_bud.Flag Trace bool Dir string Args []string @@ -61,7 +62,7 @@ func (c *Bud) Run(ctx context.Context) (err error) { return err } // Compiler the project CLI - project, err := compiler.Compile(ctx, c.Flag) + project, err := compiler.Compile(ctx, &c.Flag) if err != nil { return err } diff --git a/internal/command/run/run.go b/internal/command/run/run.go index 41ee5984..7de7b8a8 100644 --- a/internal/command/run/run.go +++ b/internal/command/run/run.go @@ -42,7 +42,7 @@ func (c *Command) Run(ctx context.Context) error { return err } // Compiler the project CLI - project, err := compiler.Compile(ctx, c.Bud.Flag) + project, err := compiler.Compile(ctx, &c.Bud.Flag) if err != nil { return err } diff --git a/internal/generator/command/command.gotext b/internal/generator/command/command.gotext index 1ffe3452..7710de1d 100644 --- a/internal/generator/command/command.gotext +++ b/internal/generator/command/command.gotext @@ -19,15 +19,12 @@ type CLI struct { } func (c *CLI) Parse(ctx context.Context, args ...string) error { - project := project.New(c.fsys, c.module) + project := bud.New(c.fsys, c.module) cli := commander.New("cli") { // cli run cmd := &run.Command{Project: project} cli := cli.Command("run", "run command") - cli.Flag("embed", "embed assets").Bool(&cmd.Flag.Embed).Default(false) - cli.Flag("hot", "hot reload").Bool(&cmd.Flag.Hot).Default(true) - cli.Flag("minify", "minify assets").Bool(&cmd.Flag.Minify).Default(false) cli.Flag("port", "port to listen to").String(&cmd.Port).Default(":3000") cli.Run(cmd.Run) } @@ -35,9 +32,6 @@ func (c *CLI) Parse(ctx context.Context, args ...string) error { { // cli build cmd := &build.Command{Project: project} cli := cli.Command("build", "build command") - cli.Flag("embed", "embed assets").Bool(&cmd.Flag.Embed).Default(false) - cli.Flag("hot", "hot reload").Bool(&cmd.Flag.Hot).Default(true) - cli.Flag("minify", "minify assets").Bool(&cmd.Flag.Minify).Default(false) cli.Run(cmd.Run) } diff --git a/internal/generator/command/parser.go b/internal/generator/command/parser.go index ef54842a..f300d131 100644 --- a/internal/generator/command/parser.go +++ b/internal/generator/command/parser.go @@ -34,7 +34,7 @@ func (p *parser) Parse(ctx context.Context) (state *State, err error) { p.imports.AddNamed("commander", "gitlab.com/mnm/bud/package/commander") // p.imports.AddNamed("command", "gitlab.com/mnm/bud/runtime/command") p.imports.AddNamed("gomod", "gitlab.com/mnm/bud/package/gomod") - p.imports.AddNamed("project", "gitlab.com/mnm/bud/runtime/project") + p.imports.AddNamed("bud", "gitlab.com/mnm/bud/runtime/bud") p.imports.AddNamed("run", "gitlab.com/mnm/bud/runtime/command/run") p.imports.AddNamed("build", "gitlab.com/mnm/bud/runtime/command/build") p.imports.AddNamed("generator", p.module.Import("bud/.cli/generator")) diff --git a/internal/generator/program/program.go b/internal/generator/program/program.go index 6053c9c5..4404993b 100644 --- a/internal/generator/program/program.go +++ b/internal/generator/program/program.go @@ -11,6 +11,7 @@ import ( "gitlab.com/mnm/bud/package/di" "gitlab.com/mnm/bud/package/gomod" "gitlab.com/mnm/bud/package/overlay" + "gitlab.com/mnm/bud/runtime/bud" ) //go:embed program.gotext @@ -23,6 +24,7 @@ var ErrCantWire = errors.New(`program: unable to wire`) // State of the program code type State struct { Imports []*imports.Import + Flags map[string]string Provider *di.Provider } @@ -31,11 +33,12 @@ func Generate(state *State) ([]byte, error) { return generator.Generate(state) } -func New(injector *di.Injector, module *gomod.Module) *Program { - return &Program{injector, module} +func New(flag *bud.Flag, injector *di.Injector, module *gomod.Module) *Program { + return &Program{flag, injector, module} } type Program struct { + flag *bud.Flag injector *di.Injector module *gomod.Module } @@ -46,8 +49,6 @@ func (p *Program) Parse(ctx context.Context) (*State, error) { imports.AddStd("errors", "context") imports.AddNamed("console", "gitlab.com/mnm/bud/package/log/console") imports.AddNamed("command", p.module.Import("bud/.cli/command")) - // imports.AddNamed("gomod", "gitlab.com/mnm/bud/package/gomod") - // imports.AddNamed("trace", "gitlab.com/mnm/bud/package/trace") // Write up the dependencies provider, err := p.injector.Wire(&di.Function{ Name: "loadCLI", @@ -55,6 +56,7 @@ func (p *Program) Parse(ctx context.Context) (*State, error) { Target: p.module.Import("bud/.cli/program"), Params: []di.Dependency{ di.ToType("gitlab.com/mnm/bud/package/gomod", "*Module"), + di.ToType("gitlab.com/mnm/bud/runtime/bud", "*Flag"), }, Aliases: di.Aliases{ di.ToType("io/fs", "FS"): di.ToType("gitlab.com/mnm/bud/package/overlay", "*FileSystem"), @@ -74,6 +76,7 @@ func (p *Program) Parse(ctx context.Context) (*State, error) { } return &State{ Imports: imports.List(), + Flags: p.flag.Map(), Provider: provider, }, nil } diff --git a/internal/generator/program/program.gotext b/internal/generator/program/program.gotext index 10e5e89e..34db43fb 100644 --- a/internal/generator/program/program.gotext +++ b/internal/generator/program/program.gotext @@ -33,13 +33,20 @@ func run(ctx context.Context, args ...string) error { } func Load() (*Program, error) { - {{- with $variable := $.Provider.Variable "gitlab.com/mnm/bud/package/gomod.*Module" }} - {{ $variable }}, err := gomod.Find(".") + {{- with $gomod := $.Provider.Variable "gitlab.com/mnm/bud/package/gomod.*Module" }} + {{ $gomod }}, err := gomod.Find(".") if err != nil { return nil, err } {{- end }} - cli, err := {{ $.Provider.Name }}({{ $.Provider.Variables "gitlab.com/mnm/bud/package/gomod.*Module" }}) + cli, err := {{ $.Provider.Name }}( + &bud.Flag{ + {{- range $key, $value := $.Flags }} + {{ $key }}: {{ $value }}, + {{- end }} + }, + {{- with $gomod := $.Provider.Variable "gitlab.com/mnm/bud/package/gomod.*Module" }}{{ $gomod }},{{ end }} + ) if err != nil { return nil, err } diff --git a/package/conjure/embedg.go b/package/conjure/embedg.go index 6f44af4f..b26731b9 100644 --- a/package/conjure/embedg.go +++ b/package/conjure/embedg.go @@ -12,6 +12,9 @@ type Embed struct { Sys interface{} } +var _ FileGenerator = (*Embed)(nil) +var _ FileServer = (*Embed)(nil) + func (e *Embed) GenerateFile(file *File) error { file.Data = e.Data file.Mode = e.Mode @@ -19,3 +22,11 @@ func (e *Embed) GenerateFile(file *File) error { file.sys = e.Sys return nil } + +func (e *Embed) ServeFile(file *File) error { + file.Data = e.Data + file.Mode = e.Mode + file.modTime = e.ModTime + file.sys = e.Sys + return nil +} diff --git a/package/exe/command.go b/package/exe/command.go new file mode 100644 index 00000000..2ebbd228 --- /dev/null +++ b/package/exe/command.go @@ -0,0 +1,76 @@ +package exe + +import ( + "context" + "os" + "os/exec" + "strings" +) + +func Command(ctx context.Context, name string, args ...string) (c *Cmd) { + return (*Cmd)(exec.CommandContext(ctx, name, args...)) +} + +type Cmd exec.Cmd + +func (c *Cmd) cmd() *exec.Cmd { + return (*exec.Cmd)(c) +} + +func (c *Cmd) Close() error { + cmd := c.cmd() + sp := cmd.Process + if sp != nil { + if err := sp.Signal(os.Interrupt); err != nil { + sp.Kill() + } + } + if err := cmd.Wait(); err != nil { + if !isExitStatus(err) && !isWaitError(err) { + return err + } + } + return nil +} + +func (c *Cmd) Wait() error { + return c.cmd().Wait() +} + +func isExitStatus(err error) bool { + return err != nil && strings.Contains(err.Error(), "exit status ") +} + +func isWaitError(err error) bool { + return err != nil && strings.Contains(err.Error(), "Wait was already called") +} + +func (c *Cmd) Run() error { + return c.cmd().Run() +} + +func (c *Cmd) Start() error { + return c.cmd().Start() +} + +func (c *Cmd) Restart(ctx context.Context) error { + // Close the process first + if err := c.Close(); err != nil { + return err + } + cmd := c.cmd() + // Re-run the command again. cmd.Args[0] is the path, so we skip that. + next := Command(ctx, cmd.Path, cmd.Args[1:]...) + next.Env = cmd.Env + next.Stdout = cmd.Stdout + next.Stderr = cmd.Stderr + next.Stdin = cmd.Stdin + next.ExtraFiles = cmd.ExtraFiles + next.Dir = cmd.Dir + if err := next.cmd().Start(); err != nil { + return err + } + // Point to the new command + *c = *next + return nil +} diff --git a/package/overlay/embed.go b/package/overlay/embed.go new file mode 100644 index 00000000..8977395c --- /dev/null +++ b/package/overlay/embed.go @@ -0,0 +1,20 @@ +package overlay + +import ( + "context" + + "gitlab.com/mnm/bud/package/conjure" +) + +type Embed conjure.Embed + +var _ FileGenerator = (*Embed)(nil) +var _ FileServer = (*Embed)(nil) + +func (e *Embed) GenerateFile(_ context.Context, _ F, file *File) error { + return (*conjure.Embed)(e).GenerateFile(file.File) +} + +func (e *Embed) ServeFile(_ context.Context, _ F, file *File) error { + return (*conjure.Embed)(e).ServeFile(file.File) +} diff --git a/package/overlay/file.go b/package/overlay/file.go index a9b9d4e7..2bd51446 100644 --- a/package/overlay/file.go +++ b/package/overlay/file.go @@ -10,6 +10,7 @@ type FileGenerator interface { GenerateFile(ctx context.Context, fsys F, file *File) error } +// TODO: don't wrap, just extend type File struct { *conjure.File } @@ -18,5 +19,3 @@ type File struct { func (f *File) Link(path string) { } - -type Embed = conjure.Embed diff --git a/package/overlay/overlay.go b/package/overlay/overlay.go index 1d5f216f..3de7dd1d 100644 --- a/package/overlay/overlay.go +++ b/package/overlay/overlay.go @@ -2,6 +2,7 @@ package overlay import ( "context" + "fmt" "gitlab.com/mnm/bud/internal/pubsub" @@ -83,6 +84,7 @@ func (f *FileSystem) DirGenerator(path string, generator DirGenerator) { func (f *FileSystem) ServeFile(path string, fn func(ctx context.Context, fsys F, file *File) error) { f.cfs.ServeFile(path, func(file *conjure.File) error { + fmt.Println("serving....", file.Path()) return fn(context.TODO(), f, &File{file}) }) } diff --git a/internal/bud/app.go b/runtime/bud/app.go similarity index 81% rename from internal/bud/app.go rename to runtime/bud/app.go index b781d72f..827d4e45 100644 --- a/internal/bud/app.go +++ b/runtime/bud/app.go @@ -4,8 +4,8 @@ import ( "context" "io" "net" - "os/exec" + "gitlab.com/mnm/bud/package/exe" "gitlab.com/mnm/bud/package/gomod" "gitlab.com/mnm/bud/package/socket" ) @@ -21,8 +21,8 @@ func (a *App) args(args ...string) []string { return args } -func (a *App) command(ctx context.Context, args ...string) *exec.Cmd { - cmd := exec.CommandContext(ctx, a.Module.Directory("bud", "app"), args...) +func (a *App) command(ctx context.Context, args ...string) *exe.Cmd { + cmd := exe.Command(ctx, a.Module.Directory("bud", "app"), args...) cmd.Dir = a.Module.Directory() cmd.Env = a.Env cmd.Stderr = a.Stderr @@ -30,7 +30,7 @@ func (a *App) command(ctx context.Context, args ...string) *exec.Cmd { return cmd } -func (a *App) Executor(ctx context.Context, args ...string) *exec.Cmd { +func (a *App) Executor(ctx context.Context, args ...string) *exe.Cmd { return a.command(ctx, a.args(args...)...) } @@ -45,7 +45,7 @@ func (a *App) Execute(ctx context.Context, args ...string) error { } // Start the application -func (a *App) Start(ctx context.Context, listener net.Listener) (*Process, error) { +func (a *App) Start(ctx context.Context, listener net.Listener) (*exe.Cmd, error) { // Pass the socket through files, env, err := socket.Files(listener) if err != nil { @@ -57,14 +57,14 @@ func (a *App) Start(ctx context.Context, listener net.Listener) (*Process, error if err := cmd.Start(); err != nil { return nil, err } - return &Process{cmd}, nil + return cmd, nil } // Run the app and wait for the result func (a *App) Run(ctx context.Context, listener net.Listener) error { - process, err := a.Start(ctx, listener) + cmd, err := a.Start(ctx, listener) if err != nil { return err } - return process.Wait() + return cmd.Wait() } diff --git a/runtime/bud/flag.go b/runtime/bud/flag.go new file mode 100644 index 00000000..77ce98cc --- /dev/null +++ b/runtime/bud/flag.go @@ -0,0 +1,18 @@ +package bud + +import "strconv" + +type Flag struct { + Embed bool + Hot bool + Minify bool +} + +// Map flags into a map to be generated +func (f *Flag) Map() map[string]string { + return map[string]string{ + "Embed": strconv.FormatBool(f.Embed), + "Hot": strconv.FormatBool(f.Hot), + "Minify": strconv.FormatBool(f.Minify), + } +} diff --git a/runtime/project/project.go b/runtime/bud/project.go similarity index 73% rename from runtime/project/project.go rename to runtime/bud/project.go index 7b13caba..c004ea16 100644 --- a/runtime/project/project.go +++ b/runtime/bud/project.go @@ -1,4 +1,4 @@ -package project +package bud import ( "context" @@ -7,14 +7,13 @@ import ( "os" "path/filepath" - "gitlab.com/mnm/bud/internal/bud" "gitlab.com/mnm/bud/internal/dsync" "gitlab.com/mnm/bud/internal/gobin" "gitlab.com/mnm/bud/package/gomod" ) -func New(fsys fs.FS, module *gomod.Module) *Compiler { - return &Compiler{ +func New(fsys fs.FS, module *gomod.Module) *Project { + return &Project{ fsys: fsys, module: module, Env: os.Environ(), @@ -23,7 +22,7 @@ func New(fsys fs.FS, module *gomod.Module) *Compiler { } } -type Compiler struct { +type Project struct { fsys fs.FS module *gomod.Module Env []string @@ -31,13 +30,7 @@ type Compiler struct { Stderr io.Writer } -type Flag struct { - Embed bool - Hot bool - Minify bool -} - -func (c *Compiler) Compile(ctx context.Context, flag *Flag) (*bud.App, error) { +func (c *Project) Compile(ctx context.Context, flag *Flag) (*App, error) { if err := dsync.Dir(c.fsys, "bud/.app", c.module.DirFS("bud/.app"), "."); err != nil { return nil, err } @@ -49,7 +42,7 @@ func (c *Compiler) Compile(ctx context.Context, flag *Flag) (*bud.App, error) { if err := gobin.Build(ctx, c.module.Directory(), "bud/.app/main.go", filepath.Join("bud", "app")); err != nil { return nil, err } - return &bud.App{ + return &App{ Module: c.module, Env: c.Env, Stderr: c.Stderr, diff --git a/runtime/command/build/build.go b/runtime/command/build/build.go index 38da81ce..fec8e7fd 100644 --- a/runtime/command/build/build.go +++ b/runtime/command/build/build.go @@ -3,12 +3,12 @@ package build import ( "context" - "gitlab.com/mnm/bud/runtime/project" + "gitlab.com/mnm/bud/runtime/bud" ) type Command struct { - Project *project.Compiler - Flag project.Flag + Project *bud.Project + Flag bud.Flag } func (c *Command) Run(ctx context.Context) error { diff --git a/runtime/command/run/run.go b/runtime/command/run/run.go index dd3291b7..472a57a7 100644 --- a/runtime/command/run/run.go +++ b/runtime/command/run/run.go @@ -14,12 +14,12 @@ import ( "golang.org/x/sync/errgroup" "gitlab.com/mnm/bud/package/socket" - "gitlab.com/mnm/bud/runtime/project" + "gitlab.com/mnm/bud/runtime/bud" ) type Command struct { - Project *project.Compiler - Flag project.Flag + Project *bud.Project + Flag bud.Flag Port string } diff --git a/runtime/generator/action/action_test.go b/runtime/generator/action/action_test.go index 81d9b2b6..74214077 100644 --- a/runtime/generator/action/action_test.go +++ b/runtime/generator/action/action_test.go @@ -37,13 +37,12 @@ func TestIndexString(t *testing.T) { res, err := server.Get("/") is.NoErr(err) // HTML response - is.NoErr(res.Expect(` + is.NoErr(res.ExpectHeaders(` HTTP/1.1 200 OK Content-Type: text/html Date: Fri, 31 Dec 2021 00:00:00 GMT - - Hello Users! `)) + is.NoErr(res.ContainsBody(`Hello Users!`)) // JSON response res, err = server.GetJSON("/") is.NoErr(err) @@ -77,13 +76,12 @@ func TestAboutIndexString(t *testing.T) { defer server.Close() res, err := server.Get("/about") is.NoErr(err) - is.NoErr(res.Expect(` + is.NoErr(res.ExpectHeaders(` HTTP/1.1 200 OK Content-Type: text/html Date: Fri, 31 Dec 2021 00:00:00 GMT - - About `)) + is.NoErr(res.ContainsBody(`About`)) res, err = server.GetJSON("/about") is.NoErr(err) is.NoErr(res.Expect(` @@ -277,11 +275,11 @@ func TestIndexListObject500(t *testing.T) { defer server.Close() res, err := server.Get("/") is.NoErr(err) - res.Expect(` + is.NoErr(res.Expect(` HTTP/1.1 302 Found Date: Fri, 31 Dec 2021 00:00:00 GMT Location: / - `) + `)) } func TestIndexListObject200(t *testing.T) { @@ -485,13 +483,12 @@ func TestDependencyRequest(t *testing.T) { defer server.Close() res, err := server.Get("/") is.NoErr(err) - is.NoErr(res.Expect(` + is.NoErr(res.ExpectHeaders(` HTTP/1.1 200 OK Content-Type: text/html Date: Fri, 31 Dec 2021 00:00:00 GMT - - / `)) + is.NoErr(res.ContainsBody(`/`)) } func TestShareStruct(t *testing.T) { @@ -1331,13 +1328,13 @@ func TestViewRootResourceUnkeyed(t *testing.T) { // res, err := server.GetJSON("") is.NoErr(err) - res.Expect(` + is.NoErr(res.Expect(` HTTP/1.1 200 OK Content-Type: application/json Date: Fri, 31 Dec 2021 00:00:00 GMT [{"id":1,"name":"a"},{"id":2,"name":"b"}] - `) + `)) res, err = server.Get("") is.NoErr(err) is.NoErr(res.ExpectHeaders(` @@ -1858,13 +1855,12 @@ func TestWorkingChangeWorking(t *testing.T) { res, err := server.Get("/") is.NoErr(err) is.NoErr(err) - res.Expect(` + is.NoErr(res.ExpectHeaders(` HTTP/1.1 200 OK Content-Type: text/html Date: Fri, 31 Dec 2021 00:00:00 GMT - - Hello Users! - `) + `)) + is.NoErr(res.ContainsBody(`Hello Users!`)) // Update file project.Files["action/action.go"] = ` package action @@ -1887,11 +1883,10 @@ func TestWorkingChangeWorking(t *testing.T) { is.NoErr(err) res, err = server.Get("/") is.NoErr(err) - is.NoErr(res.Expect(` + is.NoErr(res.ExpectHeaders(` HTTP/1.1 200 OK Content-Type: text/html Date: Fri, 31 Dec 2021 00:00:00 GMT - - Hello Humans! `)) + is.NoErr(res.ContainsBody(`Hello Humans!`)) } diff --git a/runtime/generator/public/loader.go b/runtime/generator/public/loader.go index 35eda51d..ebe57c42 100644 --- a/runtime/generator/public/loader.go +++ b/runtime/generator/public/loader.go @@ -1,44 +1,48 @@ package public import ( - "errors" "io/fs" "path" + "strings" "gitlab.com/mnm/bud/internal/bail" "gitlab.com/mnm/bud/internal/imports" "gitlab.com/mnm/bud/package/gomod" + "gitlab.com/mnm/bud/runtime/bud" ) -func Load(fsys fs.FS, module *gomod.Module, embed, minify bool) (*State, error) { +func Load(flag *bud.Flag, fsys fs.FS, module *gomod.Module) (*State, error) { loader := &loader{ fsys: fsys, + flag: flag, imports: imports.New(), module: module, - embed: embed, - minify: minify, } return loader.Load() } type loader struct { bail.Struct + flag *bud.Flag fsys fs.FS imports *imports.Set module *gomod.Module - embed bool - minify bool } // Load the command state func (l *loader) Load() (state *State, err error) { defer l.Recover(&err) state = new(State) - state.Embed = l.embed + state.Flag = l.flag state.Files = l.loadFiles() if len(state.Files) == 0 { return nil, fs.ErrNotExist } + l.imports.AddStd("errors", "io", "io/fs", "net/http", "path", "time") + // l.imports.AddStd("fmt") + l.imports.AddNamed("middleware", "gitlab.com/mnm/bud/package/middleware") + l.imports.AddNamed("overlay", "gitlab.com/mnm/bud/package/overlay") + state.Imports = l.imports.List() return state, nil } @@ -48,27 +52,53 @@ func (l *loader) loadFiles() (files []*File) { } func (l *loader) loadFilesFrom(root, dir string) (files []*File) { - fullPath := path.Join(root, dir) - des, err := fs.ReadDir(l.fsys, fullPath) + fullDir := path.Join(root, dir) + des, err := fs.ReadDir(l.fsys, fullDir) if err != nil { - if !errors.Is(err, fs.ErrNotExist) { - l.Bail(err) - } - return files + l.Bail(err) } for _, de := range des { name := de.Name() if name[0] == '_' || name[0] == '.' { continue } + filePath := path.Join(dir, name) if de.IsDir() { - files = append(files, l.loadFilesFrom(root, path.Join(dir, name))...) + files = append(files, l.loadFilesFrom(root, filePath)...) continue } - files = append(files, &File{ - Path: path.Join("bud", fullPath, name), + fullPath := path.Join(root, filePath) + file := &File{ + Path: fullPath, Root: root, - }) + } + if l.flag.Embed { + file.Data = l.loadData(fullPath) + file.Mode = "0644" // TODO: configurable + } + files = append(files, file) } return files } + +const lowerHex = "0123456789abcdef" + +// Based on: +// https://github.com/go-bindata/go-bindata/blob/26949cc13d95310ffcc491c325da869a5aafce8f/stringwriter.go#L18-L36 +func (l *loader) loadData(filePath string) string { + data, err := fs.ReadFile(l.fsys, filePath) + if err != nil { + l.Bail(err) + } + if len(data) == 0 { + return "" + } + s := new(strings.Builder) + buf := []byte(`\x00`) + for _, b := range data { + buf[2] = lowerHex[b/16] + buf[3] = lowerHex[b%16] + s.Write(buf) + } + return s.String() +} diff --git a/runtime/generator/public/public.go b/runtime/generator/public/public.go index b67d0044..a13c6f8d 100644 --- a/runtime/generator/public/public.go +++ b/runtime/generator/public/public.go @@ -9,6 +9,7 @@ import ( "gitlab.com/mnm/bud/package/overlay" "gitlab.com/mnm/bud/internal/gotemplate" + "gitlab.com/mnm/bud/runtime/bud" ) //go:embed public.gotext @@ -16,24 +17,22 @@ var template string var generator = gotemplate.MustParse("public", template) -func New(fsys fs.FS, module *gomod.Module) *Generator { +func New(flag *bud.Flag, fsys fs.FS, module *gomod.Module) *Generator { return &Generator{ - FS: fsys, - Module: module, - // Embed bool - // Minify bool + flag: flag, + fsys: fsys, + module: module, } } type Generator struct { - FS fs.FS - Module *gomod.Module - Embed bool - Minify bool + flag *bud.Flag + fsys fs.FS + module *gomod.Module } func (g *Generator) GenerateFile(ctx context.Context, _ overlay.F, file *overlay.File) error { - state, err := Load(g.FS, g.Module, g.Embed, g.Minify) + state, err := Load(g.flag, g.fsys, g.module) if err != nil { return err } diff --git a/runtime/generator/public/public.gotext b/runtime/generator/public/public.gotext index d7bc7edd..a510a299 100644 --- a/runtime/generator/public/public.gotext +++ b/runtime/generator/public/public.gotext @@ -1,52 +1,43 @@ package public // GENERATED. DO NOT EDIT. +// TODO: move most of this logic into the runtime/public -import ( - "errors" - "io" - "io/fs" - "net/http" - "path" - "time" +{{- if $.Imports }} - "gitlab.com/mnm/bud/package/middleware" +import ( + {{- range $import := $.Imports }} + {{$import.Name}} "{{$import.Path}}" + {{- end }} ) +{{- end }} -func New(fsys fs.FS) Middleware { - {{- if $.Embed }} +func New(fsys *overlay.FileSystem) Middleware { + {{- if $.Flag.Embed }} return embed(fsys) {{- else }} return reference(fsys) {{- end }} } -func reference(fsys fs.FS) Middleware { +func reference(fsys *overlay.FileSystem) Middleware { return serve(http.FS(fsys), serveContent) } -func embed(fsys fs.FS) Middleware { - panic("embed not implemented yet") +{{- if $.Flag.Embed }} +func embed(fsys *overlay.FileSystem) Middleware { {{- range $file := $.Files }} - // bfs.Entry(`{{ $file.Path }}`, nil) + fsys.FileGenerator(`{{ $file.Path }}`, &overlay.Embed{ + {{ if $file.Data }}Data: []byte("{{ $file.Data }}"),{{ end }} + {{ if $file.Mode }}Mode: fs.FileMode({{ $file.Mode }}),{{ end }} + }) {{- end }} - return serve(http.FS(fsys), serveGzipContent) + return serve(http.FS(fsys), serveContent) } +{{- end }} type Middleware = middleware.Middleware -{{/* func serveFrom(dir string) func(f gen.F, file *gen.File) error { - return func(f gen.F, file *gen.File) error { - path := strings.TrimPrefix(file.Path(), "bud/public/") - code, err := fs.ReadFile(f, filepath.Join(dir, path)) - if err != nil { - return err - } - file.Write(code) - return nil - } -} */}} - func serve(hfs http.FileSystem, serveContent func(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker)) Middleware { return middleware.Function(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/runtime/generator/public/public_test.go b/runtime/generator/public/public_test.go index 10ef1098..daa1f195 100644 --- a/runtime/generator/public/public_test.go +++ b/runtime/generator/public/public_test.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "io" + "os" + "path/filepath" "testing" "github.com/matryer/is" @@ -112,6 +114,76 @@ func TestPlugin(t *testing.T) { is.Equal(preflight, string(body)) } +func TestGetChangeGet(t *testing.T) { + is := is.New(t) + ctx := context.Background() + dir := t.TempDir() + bud := budtest.New(dir) + bud.BFiles["public/favicon.ico"] = favicon + project, err := bud.Compile(ctx) + is.NoErr(err) + app, err := project.Build(ctx) + is.NoErr(err) + is.NoErr(app.Exists("bud/.app/public/public.go")) + server, err := app.Start(ctx) + is.NoErr(err) + defer server.Close() + res, err := server.Get("/favicon.ico") + is.NoErr(err) + defer res.Body.Close() + is.Equal(200, res.StatusCode) + body, err := io.ReadAll(res.Body) + is.NoErr(err) + is.True(bytes.Equal(favicon, body)) + // Favicon2 + favicon2 := []byte{0x00, 0x00, 0x01} + bud.BFiles["public/favicon.ico"] = favicon2 + err = project.Rewrite() + is.NoErr(err) + // Rebuild + app, err = project.Build(ctx) + is.NoErr(err) + is.NoErr(app.Exists("bud/.app/public/public.go")) + err = server.Restart(ctx) + is.NoErr(err) + res, err = server.Get("/") + is.NoErr(err) + res, err = server.Get("/favicon.ico") + is.NoErr(err) + defer res.Body.Close() + is.Equal(200, res.StatusCode) + body, err = io.ReadAll(res.Body) + is.NoErr(err) + is.True(bytes.Equal(favicon2, body)) +} + +func TestEmbedFavicon(t *testing.T) { + is := is.New(t) + ctx := context.Background() + dir := t.TempDir() + bud := budtest.New(dir) + bud.Flag.Embed = true + bud.BFiles["public/favicon.ico"] = favicon + project, err := bud.Compile(ctx) + is.NoErr(err) + app, err := project.Build(ctx) + is.NoErr(err) + is.NoErr(app.Exists("bud/.app/public/public.go")) + // Remove file to ensure it's been embedded + is.NoErr(os.Remove(filepath.Join(dir, "public/favicon.ico"))) + // Try requesting the favicon, it should be in memory now + server, err := app.Start(ctx) + is.NoErr(err) + defer server.Close() + res, err := server.Get("/favicon.ico") + is.NoErr(err) + defer res.Body.Close() + is.Equal(200, res.StatusCode) + body, err := io.ReadAll(res.Body) + is.NoErr(err) + is.True(bytes.Equal(favicon, body)) +} + func TestAppPluginOverlap(t *testing.T) { t.SkipNow() } diff --git a/runtime/generator/public/state.go b/runtime/generator/public/state.go index f2913a7f..fb210341 100644 --- a/runtime/generator/public/state.go +++ b/runtime/generator/public/state.go @@ -1,14 +1,19 @@ package public -import "gitlab.com/mnm/bud/internal/imports" +import ( + "gitlab.com/mnm/bud/internal/imports" + "gitlab.com/mnm/bud/runtime/bud" +) type State struct { Imports []*imports.Import - Embed bool Files []*File + Flag *bud.Flag } type File struct { Path string Root string + Data string + Mode string }