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 ( import (
"bytes" "bytes"

View file

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

View file

@ -1,4 +1,4 @@
package main package astgen
import ( import (
"fmt" "fmt"
@ -243,6 +243,10 @@ func writePartialsFile() {
return return
} }
if len(partials) == 0 {
return
}
builder := NewCodeBuilder(nil) builder := NewCodeBuilder(nil)
builder.AppendLine(`// Package partials THIS FILE IS GENERATED. DO NOT EDIT.`) builder.AppendLine(`// Package partials THIS FILE IS GENERATED. DO NOT EDIT.`)
builder.AppendLine("package load") builder.AppendLine("package load")
@ -284,6 +288,7 @@ func formatRoute(path string) string {
} }
func writePagesFile() { func writePagesFile() {
builder := NewCodeBuilder(nil) builder := NewCodeBuilder(nil)
builder.AppendLine(`// Package pages THIS FILE IS GENERATED. DO NOT EDIT.`) builder.AppendLine(`// Package pages THIS FILE IS GENERATED. DO NOT EDIT.`)
builder.AppendLine("package pages") builder.AppendLine("package pages")
@ -292,6 +297,10 @@ func writePagesFile() {
pages, _ := findPublicFuncsReturningHPage("pages") pages, _ := findPublicFuncsReturningHPage("pages")
if len(pages) == 0 {
return
}
for _, page := range pages { for _, page := range pages {
if page.Import != "" && page.Package != "pages" { if page.Import != "" && page.Package != "pages" {
builder.AddImport(page.Import) builder.AddImport(page.Import)
@ -336,13 +345,20 @@ func GetModuleName() string {
goModBytes, err := os.ReadFile(modPath) goModBytes, err := os.ReadFile(modPath)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "error reading go.mod: %v\n", err) fmt.Fprintf(os.Stderr, "error reading go.mod: %v\n", err)
os.Exit(1) return ""
} }
modName := modfile.ModulePath(goModBytes) modName := modfile.ModulePath(goModBytes)
return modName return modName
} }
func main() { func GenAst(exitOnError bool) error {
if GetModuleName() == "" {
if exitOnError {
os.Exit(1)
}
return fmt.Errorf("error getting module name")
}
writePartialsFile() writePartialsFile()
writePagesFile() 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. // OrderedMap is a generic data structure that maintains the order of keys.
type OrderedMap[K comparable, V any] struct { type OrderedMap[K comparable, V any] struct {

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package main package copyassets
import ( import (
"fmt" "fmt"
@ -83,7 +83,7 @@ func copyDir(srcDir, dstDir string) error {
}) })
} }
func main() { func CopyAssets() {
modulePath := "github.com/maddalax/htmgo/framework" modulePath := "github.com/maddalax/htmgo/framework"
version, err := getModuleVersion(modulePath) version, err := getModuleVersion(modulePath)
if err != nil { 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 ( import (
"flag" "flag"
@ -34,25 +34,23 @@ func deleteAllExceptTemplate(outPath string, excludeDir string) {
} }
} }
func main() { func DownloadTemplate(outPath string) {
cwd, _ := os.Getwd() cwd, _ := os.Getwd()
outPath := flag.String("out", "", "Specify the output path for the new app")
flag.Parse() flag.Parse()
*outPath = strings.ReplaceAll(*outPath, "\n", "") outPath = strings.ReplaceAll(outPath, "\n", "")
*outPath = strings.ReplaceAll(*outPath, " ", "-") outPath = strings.ReplaceAll(outPath, " ", "-")
*outPath = strings.ToLower(*outPath) outPath = strings.ToLower(outPath)
if *outPath == "" { if outPath == "" {
fmt.Println("Please provide a name for your app.") fmt.Println("Please provide a name for your app.")
return return
} }
excludeDir := "starter-template" 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.Stdout = os.Stdout
install.Stderr = os.Stderr install.Stderr = os.Stderr
err := install.Run() err := install.Run()
@ -62,9 +60,9 @@ func main() {
return return
} }
deleteAllExceptTemplate(*outPath, excludeDir) deleteAllExceptTemplate(outPath, excludeDir)
newDir := filepath.Join(cwd, *outPath) newDir := filepath.Join(cwd, outPath)
commands := [][]string{ commands := [][]string{
{"cp", "-vaR", fmt.Sprintf("%s/.", excludeDir), "."}, {"cp", "-vaR", fmt.Sprintf("%s/.", excludeDir), "."},
@ -88,8 +86,8 @@ func main() {
fmt.Println("Template downloaded successfully.") fmt.Println("Template downloaded successfully.")
fmt.Println("To start the development server, run the following commands:") 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("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" "fmt"
) )
var HxBeforeRequest = "hx-on::before-request"
var HxAfterRequest = "hx-on::after-request"
var HxOnMutationError = "hx-on::mutation-error"
type LifeCycle struct { type LifeCycle struct {
beforeRequest []JsCommand handlers map[HxEvent][]JsCommand
afterRequest []JsCommand
onMutationError []JsCommand
} }
func NewLifeCycle() *LifeCycle { func NewLifeCycle() *LifeCycle {
return &LifeCycle{ return &LifeCycle{
beforeRequest: []JsCommand{}, handlers: make(map[HxEvent][]JsCommand),
afterRequest: []JsCommand{},
onMutationError: []JsCommand{},
} }
} }
func (l *LifeCycle) BeforeRequest(cmd ...JsCommand) *LifeCycle { func (l *LifeCycle) OnEvent(event HxEvent, cmd ...JsCommand) *LifeCycle {
l.beforeRequest = append(l.beforeRequest, cmd...) if l.handlers[event] == nil {
l.handlers[event] = []JsCommand{}
}
l.handlers[event] = append(l.handlers[event], cmd...)
return l 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 { func (l *LifeCycle) AfterRequest(cmd ...JsCommand) *LifeCycle {
l.afterRequest = append(l.afterRequest, cmd...) l.OnEvent(HxAfterRequest, cmd...)
return l return l
} }
func (l *LifeCycle) OnMutationError(cmd ...JsCommand) *LifeCycle { func (l *LifeCycle) OnMutationError(cmd ...JsCommand) *LifeCycle {
l.onMutationError = append(l.onMutationError, cmd...) l.OnEvent(HxOnMutationError, cmd...)
return l return l
} }
func (l *LifeCycle) Render() *Node { func (l *LifeCycle) Render() *Node {
beforeRequest := "" m := make(map[string]string)
afterReqest := ""
onMutationError := "" for event, commands := range l.handlers {
for _, command := range l.beforeRequest { m[event] = ""
beforeRequest += fmt.Sprintf("%s;", command.Command) for _, command := range commands {
} m[event] += 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)
} }
return Children( children := make([]Renderable, 0)
If(beforeRequest != "", Attribute(HxBeforeRequest, beforeRequest)),
If(afterReqest != "", Attribute(HxAfterRequest, afterReqest)), for event, js := range m {
If(onMutationError != "", Attribute(HxOnMutationError, onMutationError)), children = append(children, Attribute(event, js))
).Render() }
return Children(children...).Render()
} }
type JsCommand struct { type JsCommand struct {
@ -66,10 +80,30 @@ func SetText(text string) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)} 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 { func AddAttribute(name, value string) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.setAttribute('%s', '%s')", name, value)} 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 { func RemoveAttribute(name string) JsCommand {
return JsCommand{Command: fmt.Sprintf("this.removeAttribute('%s')", name)} 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() now := time.Now()
err := ctx.Next() err := ctx.Next()
duration := time.Since(now) 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 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 return err
}) })

View file

@ -8,30 +8,38 @@ import (
func IndexPage(c *fiber.Ctx) *h.Page { func IndexPage(c *fiber.Ctx) *h.Page {
return h.NewPage(h.Html( 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.Head(
h.Link("/public/main.css", "stylesheet"), h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"), h.Script("/public/htmgo.js"),
), ),
h.Body( h.Body(
h.Class("flex flex-col gap-3"), h.Class("flex flex-col gap-4"),
h.Div( h.Div(
h.Class("flex flex-col items-center justify-center gap-6 p-12 text-center"), h.Class("flex flex-col items-center justify-center gap-6 p-12 text-center"),
h.H1( h.H1(
h.Class("text-4xl sm:text-5xl font-bold max-w-3xl"), 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.P(
h.Class("text-lg sm:text-xl max-w-2xl"), h.Class("text-lg sm:text-xl max-w-1xl"),
h.Text("Combine the simplicity of Go with the power of HTMX for dynamic, JavaScript-light web development."),
), ),
h.Div( h.Div(
h.Button(h.Class("btn bg-blue-500 p-4 rounded text-white"), Button(),
h.Text("Click here to load a partial"),
h.GetPartial(partials.SamplePartial),
),
), ),
), ),
), ),
)) ))
} }
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 { 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 { 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" { if path == "NewPartial" || path == "/starter-template/partials.NewPartial" {
return partials.NewPartial(ctx) return partials.NewPartial(ctx)
} }
if path == "NewPartial2" || path == "/starter-template/partials.NewPartial2" {
return partials.NewPartial2(ctx)
}
return nil return nil
} }