setup service locator

start making sample todo app
This commit is contained in:
maddalax 2024-09-18 14:52:57 -05:00
parent f0f979e3a2
commit f0a38379c3
13 changed files with 168 additions and 41 deletions

View file

@ -187,7 +187,7 @@ func buildGetPartialFromContext(builder *CodeBuilder, partials []Partial) {
fName := "GetPartialFromContext"
body := `
path := ctx.Path()
path := ctx.Request().URL.Path
`
moduleName := GetModuleName()
@ -200,7 +200,8 @@ func buildGetPartialFromContext(builder *CodeBuilder, partials []Partial) {
body += fmt.Sprintf(`
if path == "%s" || path == "%s" {
return %s(ctx)
cc := ctx.(*h.RequestContext)
return %s(cc)
}
`, f.FuncName, path, caller)
}
@ -320,7 +321,8 @@ func writePagesFile() {
body += fmt.Sprintf(`
f.GET("%s", func(ctx echo.Context) error {
return h.HtmlView(ctx, %s(ctx))
cc := ctx.(*h.RequestContext)
return h.HtmlView(ctx, %s(cc))
})
`, formatRoute(page.Path), call)
}

View file

@ -96,6 +96,10 @@ func DownloadTemplate(outPath string) {
fmt.Sprintf("module %s", templateName),
fmt.Sprintf("module %s", newModuleName))
_ = util.ReplaceTextInDirRecursive(newDir, templateName, newModuleName, func(file string) bool {
return strings.HasSuffix(file, ".go")
})
fmt.Printf("Setting up the project in %s\n", newDir)
process.SetWorkingDir(newDir)
run.Setup()

View file

@ -13,4 +13,5 @@ func Setup() {
copyassets.CopyAssets()
_ = astgen.GenAst(true)
_ = css.GenerateCss(true)
EntGenerate()
}

View file

@ -1,7 +1,9 @@
package util
import (
"io/fs"
"os"
"path/filepath"
"strings"
)
@ -14,3 +16,12 @@ func ReplaceTextInFile(file string, text string, replacement string) error {
updated := strings.ReplaceAll(str, text, replacement)
return os.WriteFile(file, []byte(updated), 0644)
}
func ReplaceTextInDirRecursive(dir string, text string, replacement string, filter func(file string) bool) error {
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if filter(path) {
_ = ReplaceTextInFile(path, text, replacement)
}
return nil
})
}

View file

@ -34,7 +34,7 @@ func startWatcher(cb func(file []*fsnotify.Event)) {
if !ok {
return
}
if event.Has(fsnotify.Write) {
if event.Has(fsnotify.Write) || event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) {
events = append(events, &event)
go cb(events)
events = make([]*fsnotify.Event, 0)

File diff suppressed because one or more lines are too long

View file

@ -10,6 +10,12 @@ type WsOpts = {
export function createWebSocketClient(opts: WsOpts) {
let socket: WebSocket | null = null;
const connect = (tries: number) => {
if(tries > 50) {
console.error('failed to connect to websocket after 50 tries, please reload the page');
return;
}
socket = new WebSocket(opts.url);
// Handle connection open
socket.onopen = () => {

View file

@ -3,14 +3,30 @@ package h
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/htmgo/service"
"github.com/maddalax/htmgo/framework/util/process"
"log/slog"
"time"
)
type RequestContext struct {
echo.Context
locator *service.Locator
}
func (c *RequestContext) ServiceLocator() *service.Locator {
return c.locator
}
type AppOpts struct {
LiveReload bool
ServiceLocator *service.Locator
Register func(echo *echo.Echo)
}
type App struct {
LiveReload bool
Echo *echo.Echo
Opts AppOpts
Echo *echo.Echo
}
var instance *App
@ -22,16 +38,32 @@ func GetApp() *App {
return instance
}
func Start(app *echo.Echo, opts App) {
instance = &opts
instance.start(app)
func Start(opts AppOpts) {
e := echo.New()
instance := App{
Opts: opts,
Echo: e,
}
instance.start()
}
func (a App) start(app *echo.Echo) {
func (a App) start() {
a.Echo = app
a.Echo.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc := &RequestContext{
c,
a.Opts.ServiceLocator,
}
return next(cc)
}
})
if a.LiveReload {
if a.Opts.Register != nil {
a.Opts.Register(a.Echo)
}
if a.Opts.LiveReload {
AddLiveReloadHandler("/dev/livereload", a.Echo)
}

View file

@ -1,7 +1,6 @@
package h
import (
"github.com/labstack/echo/v4"
"html"
"net/http"
"reflect"
@ -51,10 +50,10 @@ func NewPartial(root Renderable) *Partial {
}
}
func GetPartialPath(partial func(ctx echo.Context) *Partial) string {
func GetPartialPath(partial func(ctx *RequestContext) *Partial) string {
return runtime.FuncForPC(reflect.ValueOf(partial).Pointer()).Name()
}
func GetPartialPathWithQs(partial func(ctx echo.Context) *Partial, qs string) string {
func GetPartialPathWithQs(partial func(ctx *RequestContext) *Partial, qs string) string {
return html.EscapeString(GetPartialPath(partial) + "?" + qs)
}

View file

@ -3,7 +3,6 @@ package h
import (
"encoding/json"
"fmt"
"github.com/labstack/echo/v4"
"html"
"net/url"
"strings"
@ -77,6 +76,10 @@ func Attributes(attrs map[string]string) Renderable {
}
}
func Checked() Renderable {
return Attribute("checked", "true")
}
func Boost() Renderable {
return Attribute("hx-boost", "true")
}
@ -101,11 +104,11 @@ func Get(path string) Renderable {
return Attribute("hx-get", path)
}
func GetPartial(partial func(ctx echo.Context) *Partial) Renderable {
func GetPartial(partial func(ctx *RequestContext) *Partial) Renderable {
return Get(GetPartialPath(partial))
}
func GetPartialWithQs(partial func(ctx echo.Context) *Partial, qs string) Renderable {
func GetPartialWithQs(partial func(ctx *RequestContext) *Partial, qs string) Renderable {
return Get(GetPartialPathWithQs(partial, qs))
}
@ -119,13 +122,13 @@ type ReloadParams struct {
Children Renderable
}
func ViewOnLoad(partial func(ctx echo.Context) *Partial) Renderable {
func ViewOnLoad(partial func(ctx *RequestContext) *Partial) Renderable {
return View(partial, ReloadParams{
Triggers: CreateTriggers("load"),
})
}
func View(partial func(ctx echo.Context) *Partial, params ReloadParams) Renderable {
func View(partial func(ctx *RequestContext) *Partial, params ReloadParams) Renderable {
return Div(Attributes(map[string]string{
"hx-get": GetPartialPath(partial),
"hx-trigger": strings.Join(params.Triggers, ", "),
@ -133,7 +136,7 @@ func View(partial func(ctx echo.Context) *Partial, params ReloadParams) Renderab
}), params.Children)
}
func PartialWithTriggers(partial func(ctx echo.Context) *Partial, triggers ...string) Renderable {
func PartialWithTriggers(partial func(ctx *RequestContext) *Partial, triggers ...string) Renderable {
return Div(Attributes(map[string]string{
"hx-get": GetPartialPath(partial),
"hx-trigger": strings.Join(triggers, ", "),
@ -283,7 +286,7 @@ func CombineHeaders(headers ...*Headers) *Headers {
return &m
}
func CurrentPath(ctx echo.Context) string {
func CurrentPath(ctx *RequestContext) string {
current := ctx.Request().Header.Get("Hx-Current-Url")
parsed, err := url.Parse(current)
if err != nil {
@ -292,7 +295,7 @@ func CurrentPath(ctx echo.Context) string {
return parsed.Path
}
func PushQsHeader(ctx echo.Context, key string, value string) *Headers {
func PushQsHeader(ctx *RequestContext, key string, value string) *Headers {
current := ctx.Request().Header.Get("Hx-Current-Url")
parsed, err := url.Parse(current)
if err != nil {
@ -512,11 +515,11 @@ func IfElseLazy(condition bool, cb1 func() Renderable, cb2 func() Renderable) Re
}
}
func GetTriggerName(ctx echo.Context) string {
func GetTriggerName(ctx *RequestContext) string {
return ctx.Request().Header.Get("HX-Trigger-Name")
}
func IfHtmxRequest(ctx echo.Context, node Renderable) Renderable {
func IfHtmxRequest(ctx *RequestContext, node Renderable) Renderable {
if ctx.Get("HX-Request") != "" {
return node
}
@ -535,11 +538,11 @@ func NewSwap(selector string, content *Node) SwapArg {
}
}
func Swap(ctx echo.Context, content Renderable) Renderable {
func Swap(ctx *RequestContext, content Renderable) Renderable {
return SwapWithSelector(ctx, "", content)
}
func SwapWithSelector(ctx echo.Context, selector string, content Renderable) Renderable {
func SwapWithSelector(ctx *RequestContext, selector string, content Renderable) Renderable {
if ctx == nil || ctx.Get("HX-Request") == "" {
return Empty()
}
@ -547,7 +550,7 @@ func SwapWithSelector(ctx echo.Context, selector string, content Renderable) Ren
return c.AppendChild(OutOfBandSwap(selector))
}
func SwapMany(ctx echo.Context, args ...SwapArg) Renderable {
func SwapMany(ctx *RequestContext, args ...SwapArg) Renderable {
if ctx.Get("HX-Request") == "" {
return Empty()
}

View file

@ -0,0 +1,77 @@
package service
import (
"log"
"reflect"
"sync"
)
type Lifecycle = string
var (
Singleton Lifecycle = "singleton"
)
type Provider struct {
cb func() any
lifecycle Lifecycle
}
type Locator struct {
services map[string]Provider
cache map[string]any
mutex sync.RWMutex
}
func NewLocator() *Locator {
return &Locator{
services: make(map[string]Provider),
cache: make(map[string]any),
mutex: sync.RWMutex{},
}
}
func (l *Locator) setCache(key string, value any) {
l.cache[key] = value
}
func (l *Locator) getCache(key string) any {
return l.cache[key]
}
func Get[T any](locator *Locator) *T {
locator.mutex.RLock()
i := new(T)
t := reflect.TypeOf(i).String()
cached := locator.getCache(t)
if cached != nil {
locator.mutex.RUnlock()
return cached.(*T)
}
entry, ok := locator.services[t]
if !ok {
log.Fatalf("%s is not registered in the service locator", t)
}
cb := entry.cb().(*T)
locator.mutex.RUnlock()
locator.mutex.Lock()
if entry.lifecycle == Singleton {
locator.setCache(t, cb)
}
locator.mutex.Unlock()
return cb
}
func Set[T any](locator *Locator, lifecycle Lifecycle, value func() *T) {
t := reflect.TypeOf(value)
rt := t.Out(0)
locator.services[rt.String()] = Provider{
cb: func() any {
return value()
},
lifecycle: lifecycle,
}
}

4
starter-template/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/assets/dist
tmp
node_modules
.idea

View file

@ -1,12 +1,10 @@
package main
import (
"context"
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h"
_ "github.com/mattn/go-sqlite3"
"log"
"starter-template/ent"
"starter-template/pages"
"starter-template/partials/load"
"time"
@ -21,16 +19,6 @@ func main() {
load.RegisterPartials(f)
pages.RegisterPages(f)
client, err := ent.Open("sqlite3", "file:ent.db?cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()
// Run the auto migration tool.
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed schema resources: %v", err)
}
log.Printf("main() ready in %s", time.Since(startTime))
h.Start(f, h.App{
LiveReload: true,