diff --git a/cli/debounce.go b/cli/htmgo/debounce.go similarity index 97% rename from cli/debounce.go rename to cli/htmgo/debounce.go index 67c4759..95f173b 100644 --- a/cli/debounce.go +++ b/cli/htmgo/debounce.go @@ -1,4 +1,4 @@ -package main +package htmgo import ( "sync" diff --git a/cli/go.mod b/cli/htmgo/go.mod similarity index 83% rename from cli/go.mod rename to cli/htmgo/go.mod index 5c3677c..f7e9fc8 100644 --- a/cli/go.mod +++ b/cli/htmgo/go.mod @@ -1,4 +1,4 @@ -module github.com/maddalax/htmgo/cli +module github.com/maddalax/htmgo/cli/htmgo go 1.23.0 diff --git a/cli/htmltogo/entry.go b/cli/htmgo/htmltogo/entry.go similarity index 100% rename from cli/htmltogo/entry.go rename to cli/htmgo/htmltogo/entry.go diff --git a/cli/logger.go b/cli/htmgo/logger.go similarity index 96% rename from cli/logger.go rename to cli/htmgo/logger.go index 053adc8..f486709 100644 --- a/cli/logger.go +++ b/cli/htmgo/logger.go @@ -1,4 +1,4 @@ -package main +package htmgo import ( "log/slog" diff --git a/cli/runner.go b/cli/htmgo/runner.go similarity index 96% rename from cli/runner.go rename to cli/htmgo/runner.go index 0cb7615..9a0cf61 100644 --- a/cli/runner.go +++ b/cli/htmgo/runner.go @@ -20,7 +20,7 @@ func main() { done := RegisterSignals() commandMap := make(map[string]*flag.FlagSet) - commands := []string{"template", "run", "watch", "build", "setup", "css", "schema"} + commands := []string{"template", "run", "watch", "build", "setup", "css", "schema", "generate"} for _, command := range commands { commandMap[command] = flag.NewFlagSet(command, flag.ExitOnError) @@ -100,6 +100,8 @@ func main() { downloadtemplate.DownloadTemplate(fmt.Sprintf("./%s", text)) } else if taskName == "build" { run.Build() + } else if taskName == "generate" { + astgen.GenAst(process.ExitOnError) } else { fmt.Println(fmt.Sprintf("Usage: htmgo [%s]", strings.Join(commands, " | "))) } diff --git a/cli/signals.go b/cli/htmgo/signals.go similarity index 97% rename from cli/signals.go rename to cli/htmgo/signals.go index b4ccdee..b1552d5 100644 --- a/cli/signals.go +++ b/cli/htmgo/signals.go @@ -1,4 +1,4 @@ -package main +package htmgo import ( "fmt" diff --git a/cli/tasks/astgen/ast.go b/cli/htmgo/tasks/astgen/ast.go similarity index 100% rename from cli/tasks/astgen/ast.go rename to cli/htmgo/tasks/astgen/ast.go diff --git a/cli/tasks/astgen/codebuilder.go b/cli/htmgo/tasks/astgen/codebuilder.go similarity index 100% rename from cli/tasks/astgen/codebuilder.go rename to cli/htmgo/tasks/astgen/codebuilder.go diff --git a/cli/tasks/astgen/entry.go b/cli/htmgo/tasks/astgen/entry.go similarity index 94% rename from cli/tasks/astgen/entry.go rename to cli/htmgo/tasks/astgen/entry.go index ad107b3..96454db 100644 --- a/cli/tasks/astgen/entry.go +++ b/cli/htmgo/tasks/astgen/entry.go @@ -56,7 +56,7 @@ func sliceCommonPrefix(dir1, dir2 string) string { return slicedDir2 } -func findPublicFuncsReturningHPartial(dir string) ([]Partial, error) { +func findPublicFuncsReturningHPartial(dir string, predicate func(partial Partial) bool) ([]Partial, error) { var partials []Partial cwd := process.GetWorkingDir() @@ -93,11 +93,14 @@ func findPublicFuncsReturningHPartial(dir string) ([]Partial, error) { // Check if the package name is 'h' and type is 'Partial'. if ident, ok := selectorExpr.X.(*ast.Ident); ok && ident.Name == "h" { if selectorExpr.Sel.Name == "Partial" { - partials = append(partials, Partial{ + p := Partial{ Package: node.Name.Name, Import: sliceCommonPrefix(cwd, filepath.Dir(path)), FuncName: funcDecl.Name.Name, - }) + } + if predicate(p) { + partials = append(partials, p) + } break } } @@ -191,6 +194,10 @@ func buildGetPartialFromContext(builder *CodeBuilder, partials []Partial) { path := ctx.Request().URL.Path ` + if len(partials) == 0 { + body = "" + } + moduleName := GetModuleName() for _, f := range partials { if f.FuncName == fName { @@ -240,16 +247,15 @@ func buildGetPartialFromContext(builder *CodeBuilder, partials []Partial) { func writePartialsFile() { cwd := process.GetWorkingDir() partialPath := filepath.Join(cwd, "partials") - partials, err := findPublicFuncsReturningHPartial(partialPath) + partials, err := findPublicFuncsReturningHPartial(partialPath, func(partial Partial) bool { + return partial.FuncName != "GetPartialFromContext" + }) + if err != nil { fmt.Println(err) return } - if len(partials) == 0 { - return - } - builder := NewCodeBuilder(nil) builder.AppendLine(`// Package partials THIS FILE IS GENERATED. DO NOT EDIT.`) builder.AppendLine("package load") @@ -296,12 +302,11 @@ func writePagesFile() { builder.AppendLine(`// Package pages THIS FILE IS GENERATED. DO NOT EDIT.`) builder.AppendLine("package pages") builder.AddImport("github.com/labstack/echo/v4") - builder.AddImport("github.com/maddalax/htmgo/framework/h") pages, _ := findPublicFuncsReturningHPage("pages") - if len(pages) == 0 { - return + if len(pages) > 0 { + builder.AddImport("github.com/maddalax/htmgo/framework/h") } for _, page := range pages { diff --git a/cli/tasks/astgen/map.go b/cli/htmgo/tasks/astgen/map.go similarity index 100% rename from cli/tasks/astgen/map.go rename to cli/htmgo/tasks/astgen/map.go diff --git a/cli/tasks/astgen/util.go b/cli/htmgo/tasks/astgen/util.go similarity index 100% rename from cli/tasks/astgen/util.go rename to cli/htmgo/tasks/astgen/util.go diff --git a/cli/tasks/astgen/writer.go b/cli/htmgo/tasks/astgen/writer.go similarity index 100% rename from cli/tasks/astgen/writer.go rename to cli/htmgo/tasks/astgen/writer.go diff --git a/cli/tasks/copyassets/bundle.go b/cli/htmgo/tasks/copyassets/bundle.go similarity index 89% rename from cli/tasks/copyassets/bundle.go rename to cli/htmgo/tasks/copyassets/bundle.go index 56e9ac8..71ad73c 100644 --- a/cli/tasks/copyassets/bundle.go +++ b/cli/htmgo/tasks/copyassets/bundle.go @@ -58,7 +58,7 @@ func copyFile(src, dst string) error { } // copyDir copies a directory recursively from src to dst. -func copyDir(srcDir, dstDir string) error { +func copyDir(srcDir, dstDir string, skip func(path string) bool) error { // Walk the source directory tree. return filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error { if err != nil { @@ -77,6 +77,9 @@ func copyDir(srcDir, dstDir string) error { return fmt.Errorf("failed to create directory: %v", err) } } else { + if skip != nil && skip(srcPath) { + return nil + } // If it's a file, copy the file. err := copyFile(srcPath, dstPath) if err != nil { @@ -116,8 +119,15 @@ func CopyAssets() { destDirDist := fmt.Sprintf("%s/dist", destDir) destDirCss := fmt.Sprintf("%s/css", destDir) - err := copyDir(assetDistDir, destDirDist) - err = copyDir(assetCssDir, destDirCss) + err := copyDir(assetDistDir, destDirDist, func(path string) bool { + return false + }) + err = copyDir(assetCssDir, destDirCss, func(path string) bool { + if strings.HasSuffix(path, "tailwind.config.js") { + return true + } + return false + }) if err != nil { log.Fatalf("Error: %v", err) diff --git a/cli/tasks/css/css.go b/cli/htmgo/tasks/css/css.go similarity index 100% rename from cli/tasks/css/css.go rename to cli/htmgo/tasks/css/css.go diff --git a/cli/tasks/downloadtemplate/main.go b/cli/htmgo/tasks/downloadtemplate/main.go similarity index 100% rename from cli/tasks/downloadtemplate/main.go rename to cli/htmgo/tasks/downloadtemplate/main.go diff --git a/cli/tasks/module/module.go b/cli/htmgo/tasks/module/module.go similarity index 100% rename from cli/tasks/module/module.go rename to cli/htmgo/tasks/module/module.go diff --git a/cli/tasks/process/process.go b/cli/htmgo/tasks/process/process.go similarity index 100% rename from cli/tasks/process/process.go rename to cli/htmgo/tasks/process/process.go diff --git a/cli/tasks/reloader/reloader.go b/cli/htmgo/tasks/reloader/reloader.go similarity index 97% rename from cli/tasks/reloader/reloader.go rename to cli/htmgo/tasks/reloader/reloader.go index 49fd037..8d23eda 100644 --- a/cli/tasks/reloader/reloader.go +++ b/cli/htmgo/tasks/reloader/reloader.go @@ -84,6 +84,10 @@ func OnFileChange(events []*fsnotify.Event) { tasks.Run = true } + if c.HasAnySuffix(".md") { + tasks.Run = true + } + if c.HasAnySuffix("tailwind.config.js", ".css") { tasks.Run = true } diff --git a/cli/tasks/run/build.go b/cli/htmgo/tasks/run/build.go similarity index 100% rename from cli/tasks/run/build.go rename to cli/htmgo/tasks/run/build.go diff --git a/cli/tasks/run/entschema.go b/cli/htmgo/tasks/run/entschema.go similarity index 100% rename from cli/tasks/run/entschema.go rename to cli/htmgo/tasks/run/entschema.go diff --git a/cli/tasks/run/runserver.go b/cli/htmgo/tasks/run/runserver.go similarity index 100% rename from cli/tasks/run/runserver.go rename to cli/htmgo/tasks/run/runserver.go diff --git a/cli/tasks/run/setup.go b/cli/htmgo/tasks/run/setup.go similarity index 100% rename from cli/tasks/run/setup.go rename to cli/htmgo/tasks/run/setup.go diff --git a/cli/tasks/util/file.go b/cli/htmgo/tasks/util/file.go similarity index 100% rename from cli/tasks/util/file.go rename to cli/htmgo/tasks/util/file.go diff --git a/cli/tasks/util/trace.go b/cli/htmgo/tasks/util/trace.go similarity index 100% rename from cli/tasks/util/trace.go rename to cli/htmgo/tasks/util/trace.go diff --git a/cli/watcher.go b/cli/htmgo/watcher.go similarity index 99% rename from cli/watcher.go rename to cli/htmgo/watcher.go index 930017f..704f680 100644 --- a/cli/watcher.go +++ b/cli/htmgo/watcher.go @@ -1,4 +1,4 @@ -package main +package htmgo import ( "github.com/fsnotify/fsnotify" diff --git a/framework/h/tag.go b/framework/h/tag.go index c0a8616..1ba54b9 100644 --- a/framework/h/tag.go +++ b/framework/h/tag.go @@ -244,6 +244,17 @@ func Body(children ...Ren) *Element { return Tag("body", children...) } +func Meta(name string, content string) Ren { + return &Element{ + tag: "meta", + attributes: map[string]string{ + "name": name, + "content": content, + }, + children: make([]Ren, 0), + } +} + func Link(href string, rel string) Ren { attributeMap := AttributeMap{ "href": href, @@ -291,6 +302,10 @@ func Div(children ...Ren) *Element { return Tag("div", children...) } +func Article(children ...Ren) *Element { + return Tag("article", children...) +} + func ReplaceUrlHeader(url string) *Headers { return NewHeaders("HX-Replace-Url", url) } @@ -363,11 +378,8 @@ func List[T any](items []T, mapper func(item T, index int) *Element) *Element { return node } -func Fragment(children ...Ren) *Element { - return &Element{ - tag: "", - children: children, - } +func Fragment(children ...Ren) *ChildList { + return Children(children...) } func AttributeList(children ...*AttributeMap) *AttributeMap { diff --git a/htmgo-site/Taskfile.yml b/htmgo-site/Taskfile.yml new file mode 100644 index 0000000..8e9b9c3 --- /dev/null +++ b/htmgo-site/Taskfile.yml @@ -0,0 +1,16 @@ +version: '3' + +tasks: + run: + cmds: + - go run github.com/maddalax/htmgo/cli@latest run + silent: true + + build: + cmds: + - go run github.com/maddalax/htmgo/cli@latest build + + watch: + cmds: + - go run github.com/maddalax/htmgo/cli@latest watch + silent: true \ No newline at end of file diff --git a/htmgo-site/assets/css/input.css b/htmgo-site/assets/css/input.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/htmgo-site/assets/css/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/htmgo-site/assets/css/tailwind.config.js b/htmgo-site/assets/css/tailwind.config.js new file mode 100644 index 0000000..12fc782 --- /dev/null +++ b/htmgo-site/assets/css/tailwind.config.js @@ -0,0 +1,11 @@ +const {join} = require("node:path"); +/** @type {import('tailwindcss').Config} */ +const root = join(__dirname, "../../"); +const contentGo = join(root, "**/*.go"); +const contentJs = join(root, "**/pages/**/*.js"); + + +module.exports = { + content: [contentGo, contentJs], + plugins: [ require('@tailwindcss/typography')], +}; diff --git a/htmgo-site/assets/css/tailwindcss b/htmgo-site/assets/css/tailwindcss new file mode 100755 index 0000000..f012497 Binary files /dev/null and b/htmgo-site/assets/css/tailwindcss differ diff --git a/htmgo-site/go.mod b/htmgo-site/go.mod new file mode 100644 index 0000000..7c0aae5 --- /dev/null +++ b/htmgo-site/go.mod @@ -0,0 +1,26 @@ +module htmgo-site + +go 1.23.0 + +require ( + github.com/google/uuid v1.6.0 + github.com/labstack/echo/v4 v4.12.0 + github.com/maddalax/htmgo/framework v0.0.0-20240920021308-279a3c716342 + github.com/mattn/go-sqlite3 v1.14.16 +) + +require ( + github.com/alecthomas/chroma/v2 v2.2.0 // indirect + github.com/dlclark/regexp2 v1.7.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/yuin/goldmark v1.7.4 // indirect + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect +) diff --git a/htmgo-site/go.sum b/htmgo-site/go.sum new file mode 100644 index 0000000..7fd62ef --- /dev/null +++ b/htmgo-site/go.sum @@ -0,0 +1,53 @@ +github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/maddalax/htmgo/framework v0.0.0-20240920021308-279a3c716342 h1:r7Gr/9jj+vAqhgMAnWt96mNA9rXYsiyCwrqn9FcWryE= +github.com/maddalax/htmgo/framework v0.0.0-20240920021308-279a3c716342/go.mod h1:WRIlLlHJG/xB+RR84LgNFq3hwYFKXvLfEEG8RzTUH50= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/htmgo-site/internal/dirwalk/walk.go b/htmgo-site/internal/dirwalk/walk.go new file mode 100644 index 0000000..18a2f64 --- /dev/null +++ b/htmgo-site/internal/dirwalk/walk.go @@ -0,0 +1,37 @@ +package dirwalk + +import ( + "github.com/maddalax/htmgo/framework/h" + "os" + "path/filepath" + "strings" +) + +type Page struct { + RoutePath string + FilePath string + Parts []string +} + +func WalkPages(dir string) []Page { + pages := make([]Page, 0) + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + name := info.Name() + if !info.IsDir() && (strings.HasSuffix(name, ".md") || strings.HasSuffix(name, ".go")) { + fullPath := strings.Replace(path, dir, "", 1) + fullPath = strings.TrimSuffix(fullPath, ".md") + pages = append(pages, Page{ + RoutePath: fullPath, + FilePath: path, + Parts: h.Filter(strings.Split(fullPath, string(os.PathSeparator)), func(item string) bool { + return item != "" + }), + }) + } + return nil + }) + return pages +} diff --git a/htmgo-site/internal/markdown/render.go b/htmgo-site/internal/markdown/render.go new file mode 100644 index 0000000..585eb52 --- /dev/null +++ b/htmgo-site/internal/markdown/render.go @@ -0,0 +1,66 @@ +package markdown + +import ( + "bytes" + "github.com/yuin/goldmark" + highlighting "github.com/yuin/goldmark-highlighting/v2" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer/html" + "io" + "os" +) + +type Renderer struct { + cache map[string]string +} + +func NewRenderer() *Renderer { + return &Renderer{cache: make(map[string]string)} +} + +func (r *Renderer) RenderFile(source string) string { + if val, ok := r.cache[source]; ok { + return val + } + + o, err := os.Open(source) + defer func(o *os.File) { + _ = o.Close() + }(o) + + if err != nil { + return "" + } + + buf := RenderMarkdown(o) + r.cache[source] = buf.String() + return r.cache[source] +} + +func RenderMarkdown(reader io.Reader) bytes.Buffer { + md := goldmark.New( + goldmark.WithExtensions(extension.GFM), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + html.WithHardWraps(), + html.WithXHTML(), + html.WithUnsafe(), + ), + goldmark.WithExtensions( + highlighting.NewHighlighting( + highlighting.WithStyle("github"), + ), + ), + ) + + source, _ := io.ReadAll(reader) + var buf bytes.Buffer + if err := md.Convert(source, &buf); err != nil { + panic(err) + } + + return buf +} diff --git a/htmgo-site/main.go b/htmgo-site/main.go new file mode 100644 index 0000000..269a1f7 --- /dev/null +++ b/htmgo-site/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/labstack/echo/v4" + "github.com/maddalax/htmgo/framework/h" + "github.com/maddalax/htmgo/framework/htmgo/service" + _ "github.com/mattn/go-sqlite3" + "htmgo-site/internal/markdown" + "htmgo-site/pages" + "htmgo-site/partials/load" +) + +func main() { + locator := service.NewLocator() + + service.Set(locator, service.Singleton, markdown.NewRenderer) + + h.Start(h.AppOpts{ + ServiceLocator: locator, + LiveReload: true, + Register: func(e *echo.Echo) { + e.Static("/public", "./assets/dist") + load.RegisterPartials(e) + pages.RegisterPages(e) + pages.RegisterMarkdown(e, "md", func(ctx echo.Context, path string) error { + return pages.MarkdownHandler(ctx.(*h.RequestContext), path) + }) + }, + }) +} diff --git a/htmgo-site/md/docs/example.md b/htmgo-site/md/docs/example.md new file mode 100644 index 0000000..e69de29 diff --git a/htmgo-site/md/docs/quick-start/installation.md b/htmgo-site/md/docs/quick-start/installation.md new file mode 100644 index 0000000..0bccc1e --- /dev/null +++ b/htmgo-site/md/docs/quick-start/installation.md @@ -0,0 +1,6 @@ +### **Installation Instructions** + +```bash +go install github.com/maddalax/htmgo@latest +go run htmgo template +``` \ No newline at end of file diff --git a/htmgo-site/md/index.md b/htmgo-site/md/index.md new file mode 100644 index 0000000..f055c6a --- /dev/null +++ b/htmgo-site/md/index.md @@ -0,0 +1,36 @@ +## **htmgo** + +### build simple and scalable systems with go + htmx + +------- + + + + + +**introduction:** + +htmgo is a lightweight pure go way to build interactive websites / web applications using go & htmx. + +By combining the speed & simplicity of go + hypermedia attributes ([htmx](https://htmx.org)) to add interactivity to websites, all conveniently wrapped in pure go, you can build simple, fast, interactive websites without touching javascript. All compiled to a **single deployable binary**. + +```go +func IndexPage(ctx *h.RequestContext) *h.Page { + now := time.Now() + return h.NewPage( + h.Div( + h.Class("flex gap-2"), + h.TextF("the current time is %s", now.String()) + ) + ) +} +``` + +**core features:** + +1. deployable single binary +2. live reload (rebuilds css, go, ent schema, and routes upon change) +3. automatic page and partial registration based on file path +4. built in tailwindcss support, no need to configure anything by default +5. plugin architecture to include optional plugins to streamline development, such as http://entgo.io +6. custom [htmx extensions](https://github.com/maddalax/htmgo/tree/b610aefa36e648b98a13823a6f8d87566120cfcc/framework/assets/js/htmxextensions) to reduce boilerplate with common tasks diff --git a/htmgo-site/pages/base/root.go b/htmgo-site/pages/base/root.go new file mode 100644 index 0000000..4824e53 --- /dev/null +++ b/htmgo-site/pages/base/root.go @@ -0,0 +1,32 @@ +package base + +import ( + "github.com/maddalax/htmgo/framework/h" + "htmgo-site/partials" + "strings" +) + +func Extensions() string { + extensions := []string{"path-deps", "response-targets", "mutation-error"} + if h.IsDevelopment() { + extensions = append(extensions, "livereload") + } + return strings.Join(extensions, ", ") +} + +func RootPage(children ...h.Ren) *h.Element { + return h.Html( + h.HxExtension(Extensions()), + h.Head( + h.Meta("viewport", "width=device-width, initial-scale=1"), + h.Link("/public/main.css", "stylesheet"), + h.Script("/public/htmgo.js"), + h.Raw(` + + `), + ), + h.Class("bg-neutral-50 min-h-screen"), + partials.NavBar(), + h.Fragment(children...), + ) +} diff --git a/htmgo-site/pages/docs.go b/htmgo-site/pages/docs.go new file mode 100644 index 0000000..45802c9 --- /dev/null +++ b/htmgo-site/pages/docs.go @@ -0,0 +1,24 @@ +package pages + +import ( + "github.com/maddalax/htmgo/framework/h" + "htmgo-site/internal/dirwalk" + "htmgo-site/pages/base" +) + +func DocsPage(ctx *h.RequestContext) *h.Page { + pages := dirwalk.WalkPages("md/docs") + + return h.NewPage(base.RootPage( + h.Div( + h.Class("flex flex-col p-4 justify-center items-center"), + h.List(pages, func(page dirwalk.Page, index int) *h.Element { + return MarkdownContent(ctx, page.FilePath) + }), + ), + h.Div( + h.Class("min-h-12"), + ), + ), + ) +} diff --git a/htmgo-site/pages/generated.go b/htmgo-site/pages/generated.go new file mode 100644 index 0000000..2e0e656 --- /dev/null +++ b/htmgo-site/pages/generated.go @@ -0,0 +1,16 @@ +// Package pages THIS FILE IS GENERATED. DO NOT EDIT. +package pages + +import "github.com/labstack/echo/v4" +import "github.com/maddalax/htmgo/framework/h" + +func RegisterPages(f *echo.Echo) { + f.GET("/docs", func(ctx echo.Context) error { + cc := ctx.(*h.RequestContext) + return h.HtmlView(ctx, DocsPage(cc)) + }) + f.GET("/", func(ctx echo.Context) error { + cc := ctx.(*h.RequestContext) + return h.HtmlView(ctx, IndexPage(cc)) + }) +} diff --git a/htmgo-site/pages/index.go b/htmgo-site/pages/index.go new file mode 100644 index 0000000..7edb861 --- /dev/null +++ b/htmgo-site/pages/index.go @@ -0,0 +1,9 @@ +package pages + +import ( + "github.com/maddalax/htmgo/framework/h" +) + +func IndexPage(ctx *h.RequestContext) *h.Page { + return h.NewPage(MarkdownPage(ctx, "md/index.md")) +} diff --git a/htmgo-site/pages/markdown.go b/htmgo-site/pages/markdown.go new file mode 100644 index 0000000..e801cd2 --- /dev/null +++ b/htmgo-site/pages/markdown.go @@ -0,0 +1,34 @@ +package pages + +import ( + "github.com/maddalax/htmgo/framework/h" + "github.com/maddalax/htmgo/framework/htmgo/service" + "htmgo-site/internal/markdown" + "htmgo-site/pages/base" +) + +func MarkdownHandler(ctx *h.RequestContext, path string) error { + return h.HtmlView(ctx, h.NewPage(MarkdownPage(ctx, path))) +} + +func MarkdownPage(ctx *h.RequestContext, path string) *h.Element { + return base.RootPage( + h.Div( + h.Class("flex flex-col p-4 justify-center items-center"), + MarkdownContent(ctx, path), + h.Div( + h.Class("min-h-12"), + ), + ), + ) +} + +func MarkdownContent(ctx *h.RequestContext, path string) *h.Element { + renderer := service.Get[markdown.Renderer](ctx.ServiceLocator()) + return h.Div( + h.Article( + h.Class("prose max-w-[95%] pt-3 md:p-4 md:max-w-2xl prose-code:text-black"), + h.Raw(renderer.RenderFile(path)), + ), + ) +} diff --git a/htmgo-site/pages/setup.go b/htmgo-site/pages/setup.go new file mode 100644 index 0000000..19f9f50 --- /dev/null +++ b/htmgo-site/pages/setup.go @@ -0,0 +1,14 @@ +package pages + +import ( + "github.com/labstack/echo/v4" + "htmgo-site/internal/dirwalk" +) + +func RegisterMarkdown(app *echo.Echo, dir string, handler func(ctx echo.Context, path string) error) { + for _, page := range dirwalk.WalkPages(dir) { + app.GET(page.RoutePath, func(ctx echo.Context) error { + return handler(ctx, page.FilePath) + }) + } +} diff --git a/htmgo-site/partials/load/generated.go b/htmgo-site/partials/load/generated.go new file mode 100644 index 0000000..ba527a5 --- /dev/null +++ b/htmgo-site/partials/load/generated.go @@ -0,0 +1,19 @@ +// Package partials THIS FILE IS GENERATED. DO NOT EDIT. +package load + +import "github.com/maddalax/htmgo/framework/h" +import "github.com/labstack/echo/v4" + +func GetPartialFromContext(ctx echo.Context) *h.Partial { + return nil +} + +func RegisterPartials(f *echo.Echo) { + f.Any("htmgo-site/partials*", func(ctx echo.Context) error { + partial := GetPartialFromContext(ctx) + if partial == nil { + return ctx.NoContent(404) + } + return h.PartialView(ctx, partial) + }) +} diff --git a/htmgo-site/partials/navbar.go b/htmgo-site/partials/navbar.go new file mode 100644 index 0000000..838863b --- /dev/null +++ b/htmgo-site/partials/navbar.go @@ -0,0 +1,60 @@ +package partials + +import "github.com/maddalax/htmgo/framework/h" + +type NavItem struct { + Name string + Url string +} + +func NavBar() *h.Element { + + star := h.Raw(` + Star + `) + + navItems := []NavItem{ + {Name: "Docs", Url: "/docs"}, + {Name: "Examples", Url: "/examples"}, + } + + return h.Nav( + h.Class("bg-neutral-100 border border-b-slate-300 p-4 md:p-3"), + h.Div( + h.Class("max-w-[95%] md:max-w-prose mx-auto"), + h.Div( + h.Class("flex justify-between items-center"), + h.Div( + h.Class("flex items-center"), + h.A( + h.Boost(), + h.Class("text-2xl"), + h.Href("/"), + h.Text("htmgo"), + )), + h.Div( + h.Class("flex gap-4 items-center"), + h.List(navItems, func(item NavItem, index int) *h.Element { + return h.Div( + h.Class("flex items-center"), + h.A( + h.Boost(), + h.Class(""), + h.Href(item.Url), + h.Text(item.Name), + ), + ) + }), + h.Div(h.Class("ml-2"), star), + ), + ), + ), + ) +} diff --git a/htmgo-site/partials/sidebar.go b/htmgo-site/partials/sidebar.go new file mode 100644 index 0000000..6230e8e --- /dev/null +++ b/htmgo-site/partials/sidebar.go @@ -0,0 +1,23 @@ +package partials + +import "github.com/maddalax/htmgo/framework/h" + +func SideBar() *h.Element { + return h.Div( + h.Class("w-40 top-[57px] absolute min-h-screen bg-neutral-50 border border-r-slate-300 p-3"), + h.Div( + h.Class("max-w-prose mx-auto"), + h.Div( + h.Class("flex flex-col gap-4"), + h.A( + h.Href("/docs"), + h.Text("Docs"), + ), + h.A( + h.Href("/examples"), + h.Text("Examples"), + ), + ), + ), + ) +} diff --git a/tailwind-lsp-config.json b/tailwind-lsp-config.json index 058edb9..f6afb22 100644 --- a/tailwind-lsp-config.json +++ b/tailwind-lsp-config.json @@ -39,7 +39,7 @@ "experimental": { "configFile": null, "classRegex": [[ - "tailwind|Class|h.Class\\(([^)]*)\\)", + "Class|h.Class\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" ]] }