move files around, build custom watcher / runner

This commit is contained in:
maddalax 2024-09-16 17:41:46 -05:00
parent 4ba9340317
commit a38064ed12
32 changed files with 655 additions and 299 deletions

34
cli/debounce.go Normal file
View file

@ -0,0 +1,34 @@
package main
import (
"sync"
"time"
)
// Debouncer is a struct that holds the debounce logic
type Debouncer struct {
delay time.Duration
timer *time.Timer
mu sync.Mutex
}
// NewDebouncer creates a new Debouncer with the specified delay
func NewDebouncer(delay time.Duration) *Debouncer {
return &Debouncer{
delay: delay,
}
}
// Do calls the provided function after the delay, resetting the delay if called again
func (d *Debouncer) Do(f func()) {
d.mu.Lock()
defer d.mu.Unlock()
// If there's an existing timer, stop it
if d.timer != nil {
d.timer.Stop()
}
// Create a new timer
d.timer = time.AfterFunc(d.delay, f)
}

13
cli/go.mod Normal file
View file

@ -0,0 +1,13 @@
module github.com/maddalax/htmgo/cli
go 1.23.0
require (
github.com/dave/jennifer v1.7.1
github.com/fsnotify/fsnotify v1.7.0
golang.org/x/mod v0.21.0
golang.org/x/net v0.29.0
golang.org/x/tools v0.25.0
)
require golang.org/x/sys v0.25.0 // indirect

83
cli/runner.go Normal file
View file

@ -0,0 +1,83 @@
package main
import (
"flag"
"fmt"
"github.com/maddalax/htmgo/cli/tasks/astgen"
"github.com/maddalax/htmgo/cli/tasks/copyassets"
"github.com/maddalax/htmgo/cli/tasks/css"
"github.com/maddalax/htmgo/cli/tasks/downloadtemplate"
"github.com/maddalax/htmgo/cli/tasks/process"
"github.com/maddalax/htmgo/cli/tasks/reloader"
"github.com/maddalax/htmgo/cli/tasks/run"
"os"
"strings"
)
func main() {
done := RegisterSignals()
commandMap := make(map[string]*flag.FlagSet)
commands := []string{"template", "run", "watch", "build", "setup", "css"}
for _, command := range commands {
commandMap[command] = flag.NewFlagSet(command, flag.ExitOnError)
}
if len(os.Args) < 2 {
fmt.Println(fmt.Sprintf("Usage: htmgo [%s]", strings.Join(commands, " | ")))
os.Exit(1)
}
c := commandMap[os.Args[1]]
if c == nil {
fmt.Println(fmt.Sprintf("Usage: htmgo [%s]", strings.Join(commands, " | ")))
os.Exit(1)
return
}
err := c.Parse(os.Args[2:])
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
return
}
taskName := os.Args[1]
if taskName == "watch" {
astgen.GenAst(true)
css.GenerateCss(true)
go func() {
_ = run.Server(true)
}()
startWatcher(reloader.OnFileChange)
} else {
if taskName == "setup" {
process.RunOrExit("go mod download")
process.RunOrExit("go mod tidy")
copyassets.CopyAssets()
_ = astgen.GenAst(true)
_ = css.GenerateCss(true)
}
if taskName == "css" {
_ = css.GenerateCss(true)
}
if taskName == "ast" {
_ = astgen.GenAst(true)
}
if taskName == "run" {
_ = astgen.GenAst(true)
_ = css.GenerateCss(true)
_ = run.Server(true)
}
if taskName == "template" {
downloadtemplate.DownloadTemplate("./my-app")
}
}
<-done
fmt.Println("Cleanup complete. Exiting.")
}

31
cli/signals.go Normal file
View file

@ -0,0 +1,31 @@
package main
import (
"fmt"
"github.com/maddalax/htmgo/cli/tasks/process"
"os"
"os/signal"
"syscall"
)
func RegisterSignals() chan bool {
// Create a channel to receive OS signals
sigs := make(chan os.Signal, 1)
// Register the channel to receive interrupt and terminate signals
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
done := make(chan bool, 1)
// Run a goroutine to handle signals
go func() {
// Block until a signal is received
sig := <-sigs
fmt.Println()
fmt.Println("Received signal:", sig)
// Perform cleanup
process.KillAll()
// Signal that cleanup is done
done <- true
}()
return done
}

View file

@ -1,4 +1,4 @@
package main
package astgen
import (
"bytes"

View file

@ -1,4 +1,4 @@
package main
package astgen
import (
"bytes"

View file

@ -1,4 +1,4 @@
package main
package astgen
import (
"fmt"
@ -243,6 +243,10 @@ func writePartialsFile() {
return
}
if len(partials) == 0 {
return
}
builder := NewCodeBuilder(nil)
builder.AppendLine(`// Package partials THIS FILE IS GENERATED. DO NOT EDIT.`)
builder.AppendLine("package load")
@ -284,6 +288,7 @@ func formatRoute(path string) string {
}
func writePagesFile() {
builder := NewCodeBuilder(nil)
builder.AppendLine(`// Package pages THIS FILE IS GENERATED. DO NOT EDIT.`)
builder.AppendLine("package pages")
@ -292,6 +297,10 @@ func writePagesFile() {
pages, _ := findPublicFuncsReturningHPage("pages")
if len(pages) == 0 {
return
}
for _, page := range pages {
if page.Import != "" && page.Package != "pages" {
builder.AddImport(page.Import)
@ -336,13 +345,20 @@ func GetModuleName() string {
goModBytes, err := os.ReadFile(modPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading go.mod: %v\n", err)
os.Exit(1)
return ""
}
modName := modfile.ModulePath(goModBytes)
return modName
}
func main() {
func GenAst(exitOnError bool) error {
if GetModuleName() == "" {
if exitOnError {
os.Exit(1)
}
return fmt.Errorf("error getting module name")
}
writePartialsFile()
writePagesFile()
return nil
}

View file

@ -1,4 +1,4 @@
package main
package astgen
// OrderedMap is a generic data structure that maintains the order of keys.
type OrderedMap[K comparable, V any] struct {

View file

@ -1,4 +1,4 @@
package main
package astgen
import (
"fmt"

View file

@ -1,4 +1,4 @@
package main
package astgen
import (
"go/ast"

View file

@ -1,4 +1,4 @@
package main
package copyassets
import (
"fmt"
@ -83,7 +83,7 @@ func copyDir(srcDir, dstDir string) error {
})
}
func main() {
func CopyAssets() {
modulePath := "github.com/maddalax/htmgo/framework"
version, err := getModuleVersion(modulePath)
if err != nil {

10
cli/tasks/css/css.go Normal file
View file

@ -0,0 +1,10 @@
package css
import "github.com/maddalax/htmgo/cli/tasks/process"
func GenerateCss(exitOnError bool) error {
return process.RunMany([]string{
"chmod +x ./assets/css/tailwindcss",
"./assets/css/tailwindcss -i ./assets/css/input.css -o ./assets/dist/main.css -c ./assets/css/tailwind.config.js",
}, exitOnError)
}

View file

@ -1,4 +1,4 @@
package main
package downloadtemplate
import (
"flag"
@ -34,25 +34,23 @@ func deleteAllExceptTemplate(outPath string, excludeDir string) {
}
}
func main() {
func DownloadTemplate(outPath string) {
cwd, _ := os.Getwd()
outPath := flag.String("out", "", "Specify the output path for the new app")
flag.Parse()
*outPath = strings.ReplaceAll(*outPath, "\n", "")
*outPath = strings.ReplaceAll(*outPath, " ", "-")
*outPath = strings.ToLower(*outPath)
outPath = strings.ReplaceAll(outPath, "\n", "")
outPath = strings.ReplaceAll(outPath, " ", "-")
outPath = strings.ToLower(outPath)
if *outPath == "" {
if outPath == "" {
fmt.Println("Please provide a name for your app.")
return
}
excludeDir := "starter-template"
install := exec.Command("git", "clone", "https://github.com/maddalax/htmgo", "--depth=1", *outPath)
install := exec.Command("git", "clone", "https://github.com/maddalax/htmgo", "--depth=1", outPath)
install.Stdout = os.Stdout
install.Stderr = os.Stderr
err := install.Run()
@ -62,9 +60,9 @@ func main() {
return
}
deleteAllExceptTemplate(*outPath, excludeDir)
deleteAllExceptTemplate(outPath, excludeDir)
newDir := filepath.Join(cwd, *outPath)
newDir := filepath.Join(cwd, outPath)
commands := [][]string{
{"cp", "-vaR", fmt.Sprintf("%s/.", excludeDir), "."},
@ -88,8 +86,8 @@ func main() {
fmt.Println("Template downloaded successfully.")
fmt.Println("To start the development server, run the following commands:")
fmt.Printf("cd %s && htmgo run\n", *outPath)
fmt.Printf("cd %s && htmgo run\n", outPath)
fmt.Printf("To build the project, run the following command:\n")
fmt.Printf("cd %s && htmgo build\n", *outPath)
fmt.Printf("cd %s && htmgo build\n", outPath)
}

View file

@ -0,0 +1,145 @@
package process
import (
"errors"
"fmt"
"log"
"os"
"os/exec"
"strings"
"syscall"
"time"
)
var commands = make([]*exec.Cmd, 0)
func AppendRunning(cmd *exec.Cmd) {
commands = append(commands, cmd)
}
func KillAll() {
tries := 0
for {
tries++
allFinished := true
for _, cmd := range commands {
if cmd.Process == nil {
allFinished = false
if tries > 50 {
args := strings.Join(cmd.Args, " ")
log.Printf("process %v is not running after 50 tries, breaking.\n", args)
allFinished = true
break
} else {
time.Sleep(time.Millisecond * 50)
continue
}
}
}
if allFinished {
break
}
}
for _, command := range commands {
pid := command.Process.Pid
err := syscall.Kill(-pid, syscall.SIGKILL)
if err != nil {
continue
}
}
for {
finished := true
for _, c := range commands {
if c.Process == nil {
continue
}
exists, err := PidExists(int32(c.Process.Pid))
if err != nil {
finished = false
}
if exists {
syscall.Kill(-c.Process.Pid, syscall.SIGKILL)
finished = false
}
}
if finished {
break
} else {
fmt.Printf("waiting for all processes to exit\n")
time.Sleep(time.Millisecond * 5)
}
}
commands = make([]*exec.Cmd, 0)
}
func PidExists(pid int32) (bool, error) {
if pid <= 0 {
return false, fmt.Errorf("invalid pid %v", pid)
}
proc, err := os.FindProcess(int(pid))
if err != nil {
return false, err
}
err = proc.Signal(syscall.Signal(0))
if err == nil {
return true, nil
}
if err.Error() == "os: process already finished" {
return false, nil
}
var errno syscall.Errno
ok := errors.As(err, &errno)
if !ok {
return false, err
}
switch errno {
case syscall.ESRCH:
return false, nil
case syscall.EPERM:
return true, nil
}
return false, err
}
func RunOrExit(command string) {
_ = Run(command, true)
}
func RunMany(commands []string, exitOnError bool) error {
for _, command := range commands {
err := Run(command, false)
if err != nil {
if exitOnError {
os.Exit(1)
}
return err
}
}
return nil
}
func Run(command string, exitOnError bool) error {
cmd := exec.Command("bash", "-c", command)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
AppendRunning(cmd)
err := cmd.Run()
if err != nil {
if strings.Contains(err.Error(), "signal: killed") {
return nil
}
if exitOnError {
log.Println(fmt.Sprintf("error: %v", err))
os.Exit(1)
}
return err
}
return nil
}

View file

@ -0,0 +1,115 @@
package reloader
import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/maddalax/htmgo/cli/tasks/astgen"
"github.com/maddalax/htmgo/cli/tasks/css"
"github.com/maddalax/htmgo/cli/tasks/process"
"github.com/maddalax/htmgo/cli/tasks/run"
"strings"
"sync"
)
type Change struct {
name string
}
func NewChange(name string) *Change {
return &Change{name: name}
}
func (c *Change) Name() string {
return c.name
}
func (c *Change) HasAnyPrefix(prefix ...string) bool {
for _, s := range prefix {
if strings.HasPrefix(c.name, s) {
return true
}
}
return false
}
func (c *Change) HasAnySuffix(suffix ...string) bool {
for _, s := range suffix {
if strings.HasSuffix(c.name, s) {
return true
}
}
return false
}
func (c *Change) IsGenerated() bool {
return c.HasAnySuffix("generated.go")
}
func (c *Change) IsGo() bool {
return c.HasAnySuffix(".go")
}
type Tasks struct {
AstGen bool
Css bool
Run bool
}
func OnFileChange(events []*fsnotify.Event) {
tasks := Tasks{}
for _, event := range events {
c := NewChange(event.Name)
if c.IsGenerated() {
continue
}
if c.IsGo() && c.HasAnyPrefix("pages/", "partials/") {
tasks.AstGen = true
}
if c.IsGo() {
tasks.Css = true
tasks.Run = true
}
if c.HasAnySuffix("tailwind.config.js", ".css") {
tasks.Css = true
}
}
deps := make([]func() any, 0)
if tasks.AstGen {
deps = append(deps, func() any {
return astgen.GenAst(false)
})
}
if tasks.Css {
deps = append(deps, func() any {
return css.GenerateCss(false)
})
}
wg := sync.WaitGroup{}
for _, dep := range deps {
wg.Add(1)
go func(dep func() any) {
defer wg.Done()
err := dep()
if err != nil {
fmt.Println(err)
}
}(dep)
}
wg.Wait()
if tasks.Run {
process.KillAll()
_ = run.Server(false)
}
}

View file

@ -0,0 +1,7 @@
package run
import "github.com/maddalax/htmgo/cli/tasks/process"
func Server(exitOnError bool) error {
return process.Run("go run .", exitOnError)
}

73
cli/watcher.go Normal file
View file

@ -0,0 +1,73 @@
package main
import (
"fmt"
"github.com/fsnotify/fsnotify"
"log"
"os"
"path/filepath"
)
func startWatcher(cb func(file []*fsnotify.Event)) {
//debouncer := NewDebouncer(time.Millisecond * 100)
events := make([]*fsnotify.Event, 0)
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from fatal error:", r)
// You can log the error here or take other corrective actions
}
}()
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
panic(err)
}
defer watcher.Close()
// Start listening for events.
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Has(fsnotify.Write) {
events = append(events, &event)
go cb(events)
events = make([]*fsnotify.Event, 0)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
rootDir := "."
// Walk through the root directory and add all subdirectories to the watcher
err = filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Only watch directories
if info.IsDir() {
err = watcher.Add(path)
if err != nil {
log.Println("Error adding directory to watcher:", err)
} else {
log.Println("Watching directory:", path)
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
done := RegisterSignals()
<-done
println("process exited")
}

15
framework/h/events.go Normal file
View file

@ -0,0 +1,15 @@
package h
type HxEvent = string
var (
HxBeforeRequest HxEvent = "hx-on::before-request"
HxAfterRequest HxEvent = "hx-on::after-request"
HxOnMutationError HxEvent = "hx-on::mutation-error"
HxOnLoad HxEvent = "hx-on::load"
HxOnLoadError HxEvent = "hx-on::load-error"
HxRequestTimeout HxEvent = "hx-on::request-timeout"
HxTrigger HxEvent = "hx-on::trigger"
HxRequestStart HxEvent = "hx-on::xhr:loadstart"
HxRequestProgress HxEvent = "hx-on::xhr:progress"
)

View file

@ -4,58 +4,72 @@ import (
"fmt"
)
var HxBeforeRequest = "hx-on::before-request"
var HxAfterRequest = "hx-on::after-request"
var HxOnMutationError = "hx-on::mutation-error"
type LifeCycle struct {
beforeRequest []JsCommand
afterRequest []JsCommand
onMutationError []JsCommand
handlers map[HxEvent][]JsCommand
}
func NewLifeCycle() *LifeCycle {
return &LifeCycle{
beforeRequest: []JsCommand{},
afterRequest: []JsCommand{},
onMutationError: []JsCommand{},
handlers: make(map[HxEvent][]JsCommand),
}
}
func (l *LifeCycle) BeforeRequest(cmd ...JsCommand) *LifeCycle {
l.beforeRequest = append(l.beforeRequest, cmd...)
func (l *LifeCycle) OnEvent(event HxEvent, cmd ...JsCommand) *LifeCycle {
if l.handlers[event] == nil {
l.handlers[event] = []JsCommand{}
}
l.handlers[event] = append(l.handlers[event], cmd...)
return l
}
func (l *LifeCycle) BeforeRequest(cmd ...JsCommand) *LifeCycle {
l.OnEvent(HxBeforeRequest, cmd...)
return l
}
func OnEvent(event HxEvent, cmd ...JsCommand) *LifeCycle {
return NewLifeCycle().OnEvent(event, cmd...)
}
func BeforeRequest(cmd ...JsCommand) *LifeCycle {
return NewLifeCycle().BeforeRequest(cmd...)
}
func AfterRequest(cmd ...JsCommand) *LifeCycle {
return NewLifeCycle().AfterRequest(cmd...)
}
func OnMutationError(cmd ...JsCommand) *LifeCycle {
return NewLifeCycle().OnMutationError(cmd...)
}
func (l *LifeCycle) AfterRequest(cmd ...JsCommand) *LifeCycle {
l.afterRequest = append(l.afterRequest, cmd...)
l.OnEvent(HxAfterRequest, cmd...)
return l
}
func (l *LifeCycle) OnMutationError(cmd ...JsCommand) *LifeCycle {
l.onMutationError = append(l.onMutationError, cmd...)
l.OnEvent(HxOnMutationError, cmd...)
return l
}
func (l *LifeCycle) Render() *Node {
beforeRequest := ""
afterReqest := ""
onMutationError := ""
for _, command := range l.beforeRequest {
beforeRequest += fmt.Sprintf("%s;", command.Command)
}
for _, command := range l.afterRequest {
afterReqest += fmt.Sprintf("%s;", command.Command)
}
for _, command := range l.onMutationError {
onMutationError += fmt.Sprintf("%s;", command.Command)
m := make(map[string]string)
for event, commands := range l.handlers {
m[event] = ""
for _, command := range commands {
m[event] += fmt.Sprintf("%s;", command.Command)
}
}
return Children(
If(beforeRequest != "", Attribute(HxBeforeRequest, beforeRequest)),
If(afterReqest != "", Attribute(HxAfterRequest, afterReqest)),
If(onMutationError != "", Attribute(HxOnMutationError, onMutationError)),
).Render()
children := make([]Renderable, 0)
for event, js := range m {
children = append(children, Attribute(event, js))
}
return Children(children...).Render()
}
type JsCommand struct {
@ -66,10 +80,30 @@ func SetText(text string) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)}
}
func Increment(amount int) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.innerText = parseInt(this.innerText) + %d", amount)}
}
func SetInnerHtml(r Renderable) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.innerHTML = `%s`", Render(r.Render()))}
}
func SetOuterHtml(r Renderable) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.outerHTML = `%s`", Render(r.Render()))}
}
func AddAttribute(name, value string) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.setAttribute('%s', '%s')", name, value)}
}
func SetDisabled(disabled bool) JsCommand {
if disabled {
return AddAttribute("disabled", "true")
} else {
return RemoveAttribute("disabled")
}
}
func RemoveAttribute(name string) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.removeAttribute('%s')", name)}
}

View file

@ -1,8 +0,0 @@
// Package pages THIS FILE IS GENERATED. DO NOT EDIT.
package pages
import "github.com/gofiber/fiber/v2"
import "github.com/maddalax/htmgo/framework/h"
func RegisterPages(f *fiber.App) {
}

View file

@ -1,3 +0,0 @@
module github.com/maddalax/htmgo/framework/tooling/astgen
go 1.23.0

View file

@ -1,5 +0,0 @@
module github.com/maddalax/htmgo/framework/tooling/copyassets
go 1.23.0
require golang.org/x/mod v0.21.0

View file

@ -1,3 +0,0 @@
module github.com/maddalax/htmgo/framework/tooling/downloadtemplate
go 1.23.0

View file

@ -1,3 +0,0 @@
module github.com/maddalax/htmgo/framework/tooling/htmgo
go 1.23.0

View file

@ -1,94 +0,0 @@
package main
import (
_ "embed"
"flag"
"fmt"
"os"
"os/exec"
"strings"
"sync"
)
//go:embed Taskfile.yml
var taskFile string
func main() {
commandMap := make(map[string]*flag.FlagSet)
commands := []string{"template", "run", "watch", "build", "setup", "css", "css-watch", "ast-watch", "go-watch"}
for _, command := range commands {
commandMap[command] = flag.NewFlagSet(command, flag.ExitOnError)
}
if len(os.Args) < 2 {
fmt.Println(fmt.Sprintf("Usage: htmgo [%s]", strings.Join(commands, " | ")))
os.Exit(1)
}
c := commandMap[os.Args[1]]
if c == nil {
fmt.Println(fmt.Sprintf("Usage: htmgo [%s]", strings.Join(commands, " | ")))
os.Exit(1)
return
}
err := c.Parse(os.Args[2:])
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
return
}
// Install the latest version of Task
install := exec.Command("go", "install", "github.com/go-task/task/v3/cmd/task@latest")
err = install.Run()
if err != nil {
fmt.Printf("Error installing task: %v\n", err)
return
}
temp, err := os.CreateTemp("", "Taskfile.yml")
if err != nil {
fmt.Printf("Error creating temporary file: %v\n", err)
return
}
os.WriteFile(temp.Name(), []byte(taskFile), 0644)
taskName := os.Args[1]
if taskName == "watch" {
tasks := []string{"css-watch", "ast-watch", "go-watch"}
wg := sync.WaitGroup{}
for _, task := range tasks {
wg.Add(1)
go func() {
cmd := exec.Command("task", "-t", temp.Name(), task)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Printf("Error running task command: %v\n", err)
}
wg.Done()
}()
}
wg.Wait()
} else {
// Define the command and arguments
cmd := exec.Command("task", "-t", temp.Name(), os.Args[1])
// Set the standard output and error to be the same as the Go program
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Run the command
err = cmd.Run()
if err != nil {
fmt.Printf("Error running task command: %v\n", err)
return
}
}
}

View file

@ -1,117 +0,0 @@
package main
import (
"bytes"
"fmt"
"github.com/fsnotify/fsnotify"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
)
func main() {
once := false
if len(os.Args) > 1 {
once = os.Args[1] == "--once"
}
command := ""
for i, arg := range os.Args {
if arg == "--command" {
command = os.Args[i+1]
}
}
if command == "" {
panic("command is required")
}
if once {
runCommand(command)
return
}
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from fatal error:", r)
// You can log the error here or take other corrective actions
}
}()
runCommand(command)
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
panic(err)
}
defer watcher.Close()
// Start listening for events.
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if strings.HasSuffix(event.Name, "generated.go") {
continue
}
if event.Has(fsnotify.Write) {
success := runCommand(command)
if success {
log.Println(fmt.Sprintf("file changed. code generation successful"))
} else {
log.Println(fmt.Sprintf("file changed. code generation failed"))
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
cwd, _ := os.Getwd()
pagesDir := filepath.Join(cwd, "pages")
partialsDir := filepath.Join(cwd, "partials")
toWatch := []string{pagesDir, partialsDir}
for _, watch := range toWatch {
err = watcher.Add(watch)
if err != nil {
panic(err)
}
}
// Block main goroutine forever.
<-make(chan struct{})
}
func runCommand(command string) bool {
// Create a new command
cmd := exec.Command("bash", "-c", command)
// Capture stdout and stderr in buffers
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
// Run the command
err := cmd.Run()
if err != nil {
log.Println(fmt.Sprintf("error: %v", err))
println(stderr.String())
return false
} else {
println(out.String())
return true
}
}

View file

@ -21,9 +21,9 @@ func main() {
now := time.Now()
err := ctx.Next()
duration := time.Since(now)
ctx.Set("X-Response-Time", duration.String())
ctx.Set("X-Response-Times", duration.String())
// Log or print the request method, URL, and duration
log.Printf("Request: %s %s took %dms", ctx.Method(), ctx.OriginalURL(), duration.Milliseconds())
log.Printf("Requests: %s %s took %dms", ctx.Method(), ctx.OriginalURL(), duration.Milliseconds())
return err
})

View file

@ -8,30 +8,38 @@ import (
func IndexPage(c *fiber.Ctx) *h.Page {
return h.NewPage(h.Html(
h.Class("bg-slate-400 flex flex-col items-center h-full w-full"),
h.Class("bg-blue-400 flex flex-col items-center h-full w-full"),
h.Head(
h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"),
),
h.Body(
h.Class("flex flex-col gap-3"),
h.Class("flex flex-col gap-4"),
h.Div(
h.Class("flex flex-col items-center justify-center gap-6 p-12 text-center"),
h.H1(
h.Class("text-4xl sm:text-5xl font-bold max-w-3xl"),
h.Text("Welcome to htmgo"),
h.Text("Welcome to my fast!!"),
),
h.P(
h.Class("text-lg sm:text-xl max-w-2xl"),
h.Text("Combine the simplicity of Go with the power of HTMX for dynamic, JavaScript-light web development."),
h.Class("text-lg sm:text-xl max-w-1xl"),
),
h.Div(
h.Button(h.Class("btn bg-blue-500 p-4 rounded text-white"),
h.Text("Click here to load a partial"),
h.GetPartial(partials.SamplePartial),
),
Button(),
),
),
),
))
}
func Button() h.Renderable {
return h.Button(h.Class("btn bg-slate-500 p-4 rounded text-white"),
h.Text("Click here use my ytes"),
h.AfterRequest(
h.SetDisabled(true),
h.RemoveClass("bg-red-600"),
h.AddClass("bg-gray-500"),
),
h.GetPartial(partials.SamplePartial),
)
}

View file

@ -6,9 +6,13 @@ import (
)
func SamplePartial(ctx *fiber.Ctx) *h.Partial {
return h.NewPartial(h.Div(h.P(h.Text("This is a sample partials."))))
return h.NewPartial(h.Div(h.P(h.Text(" asdas"))))
}
func NewPartial(ctx *fiber.Ctx) *h.Partial {
return h.NewPartial(h.Div(h.P(h.Text("This is a new pardtiasl."))))
return h.NewPartial(h.Div(h.P(h.Text("This sadsl."))))
}
func NewPartial2(ctx *fiber.Ctx) *h.Partial {
return h.NewPartial(h.Div(h.P(h.Text("This sasdsadasdwl."))))
}

View file

@ -13,6 +13,9 @@ func GetPartialFromContext(ctx *fiber.Ctx) *h.Partial {
if path == "NewPartial" || path == "/starter-template/partials.NewPartial" {
return partials.NewPartial(ctx)
}
if path == "NewPartial2" || path == "/starter-template/partials.NewPartial2" {
return partials.NewPartial2(ctx)
}
return nil
}