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\\(([^)]*)\\)",
"[\"'`]([^\"'`]*).*?[\"'`]"
]]
}