commit
8503dffa4e
12 changed files with 188 additions and 16 deletions
|
|
@ -11,3 +11,5 @@ require (
|
||||||
golang.org/x/sys v0.25.0
|
golang.org/x/sys v0.25.0
|
||||||
golang.org/x/tools v0.25.0
|
golang.org/x/tools v0.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||||
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/maddalax/htmgo/cli/htmgo/tasks/process"
|
"github.com/maddalax/htmgo/cli/htmgo/tasks/process"
|
||||||
|
"github.com/maddalax/htmgo/framework/config"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -17,6 +18,10 @@ func HasFileFromRoot(file string) bool {
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetConfig() *config.ProjectConfig {
|
||||||
|
return config.FromConfigFile(process.GetWorkingDir())
|
||||||
|
}
|
||||||
|
|
||||||
func CreateHtmgoDir() {
|
func CreateHtmgoDir() {
|
||||||
if !HasFileFromRoot("__htmgo") {
|
if !HasFileFromRoot("__htmgo") {
|
||||||
CreateDirFromRoot("__htmgo")
|
CreateDirFromRoot("__htmgo")
|
||||||
|
|
|
||||||
31
cli/htmgo/internal/dirutil/glob.go
Normal file
31
cli/htmgo/internal/dirutil/glob.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package dirutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func matchesAny(patterns []string, path string) bool {
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
matched, err := doublestar.Match(pattern, path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error matching pattern: %v\n", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsGlobExclude(path string, excludePatterns []string) bool {
|
||||||
|
return matchesAny(excludePatterns, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsGlobMatch(path string, patterns []string, excludePatterns []string) bool {
|
||||||
|
if matchesAny(excludePatterns, path) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return matchesAny(patterns, path)
|
||||||
|
}
|
||||||
|
|
@ -92,7 +92,7 @@ func CopyAssets() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dirutil.HasFileFromRoot("tailwind.config.js") {
|
if dirutil.GetConfig().Tailwind && !dirutil.HasFileFromRoot("tailwind.config.js") {
|
||||||
err = dirutil.CopyFile(
|
err = dirutil.CopyFile(
|
||||||
filepath.Join(assetCssDir, "tailwind.config.js"),
|
filepath.Join(assetCssDir, "tailwind.config.js"),
|
||||||
filepath.Join(process.GetWorkingDir(), "tailwind.config.js"),
|
filepath.Join(process.GetWorkingDir(), "tailwind.config.js"),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsTailwindEnabled() bool {
|
func IsTailwindEnabled() bool {
|
||||||
return dirutil.HasFileFromRoot("tailwind.config.js")
|
return dirutil.GetConfig().Tailwind && dirutil.HasFileFromRoot("tailwind.config.js")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Setup() bool {
|
func Setup() bool {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/maddalax/htmgo/cli/htmgo/internal"
|
"github.com/maddalax/htmgo/cli/htmgo/internal"
|
||||||
|
"github.com/maddalax/htmgo/cli/htmgo/internal/dirutil"
|
||||||
"github.com/maddalax/htmgo/cli/htmgo/tasks/module"
|
"github.com/maddalax/htmgo/cli/htmgo/tasks/module"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
@ -13,11 +14,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ignoredDirs = []string{".git", ".idea", "node_modules", "vendor"}
|
|
||||||
|
|
||||||
func startWatcher(cb func(version string, file []*fsnotify.Event)) {
|
func startWatcher(cb func(version string, file []*fsnotify.Event)) {
|
||||||
events := make([]*fsnotify.Event, 0)
|
events := make([]*fsnotify.Event, 0)
|
||||||
debouncer := internal.NewDebouncer(500 * time.Millisecond)
|
debouncer := internal.NewDebouncer(500 * time.Millisecond)
|
||||||
|
config := dirutil.GetConfig()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
|
@ -35,23 +35,22 @@ func startWatcher(cb func(version string, file []*fsnotify.Event)) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event, ok := <-watcher.Events:
|
case event, ok := <-watcher.Events:
|
||||||
slog.Debug("event:", slog.String("name", event.Name), slog.String("op", event.Op.String()))
|
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Has(fsnotify.Remove) {
|
if event.Has(fsnotify.Remove) {
|
||||||
info, err := os.Stat(event.Name)
|
if dirutil.IsGlobMatch(event.Name, config.WatchFiles, config.WatchIgnore) {
|
||||||
if err != nil {
|
watcher.Remove(event.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
|
||||||
_ = watcher.Remove(event.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Has(fsnotify.Create) {
|
if event.Has(fsnotify.Create) {
|
||||||
|
if dirutil.IsGlobMatch(event.Name, config.WatchFiles, config.WatchIgnore) {
|
||||||
|
watcher.Add(event.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
info, err := os.Stat(event.Name)
|
info, err := os.Stat(event.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error getting file info:", slog.String("path", event.Name), slog.String("error", err.Error()))
|
slog.Error("Error getting file info:", slog.String("path", event.Name), slog.String("error", err.Error()))
|
||||||
|
|
@ -68,6 +67,9 @@ func startWatcher(cb func(version string, file []*fsnotify.Event)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Has(fsnotify.Write) || event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) {
|
if event.Has(fsnotify.Write) || event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) {
|
||||||
|
if !dirutil.IsGlobMatch(event.Name, config.WatchFiles, config.WatchIgnore) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
events = append(events, &event)
|
events = append(events, &event)
|
||||||
debouncer.Do(func() {
|
debouncer.Do(func() {
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
|
|
@ -82,6 +84,7 @@ func startWatcher(cb func(version string, file []*fsnotify.Event)) {
|
||||||
events = make([]*fsnotify.Event, 0)
|
events = make([]*fsnotify.Event, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
case err, ok := <-watcher.Errors:
|
case err, ok := <-watcher.Errors:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
|
@ -107,11 +110,10 @@ func startWatcher(cb func(version string, file []*fsnotify.Event)) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Ignore directories in the ignoredDirs list
|
// Ignore directories in the ignoredDirs list
|
||||||
for _, ignoredDir := range ignoredDirs {
|
if dirutil.IsGlobExclude(path, config.WatchIgnore) {
|
||||||
if ignoredDir == info.Name() {
|
return filepath.SkipDir
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only watch directories
|
// Only watch directories
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
err = watcher.Add(path)
|
err = watcher.Add(path)
|
||||||
|
|
@ -123,6 +125,7 @@ func startWatcher(cb func(version string, file []*fsnotify.Event)) {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
framework/config/project.go
Normal file
58
framework/config/project.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectConfig struct {
|
||||||
|
Tailwind bool `yaml:"tailwind"`
|
||||||
|
WatchIgnore []string `yaml:"watch_ignore"`
|
||||||
|
WatchFiles []string `yaml:"watch_files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultProjectConfig() *ProjectConfig {
|
||||||
|
return &ProjectConfig{
|
||||||
|
Tailwind: true,
|
||||||
|
WatchIgnore: []string{
|
||||||
|
"node_modules", ".git", ".idea", "assets/dist",
|
||||||
|
},
|
||||||
|
WatchFiles: []string{
|
||||||
|
"**/*.go", "**/*.html", "**/*.css", "**/*.js", "**/*.json", "**/*.yaml", "**/*.yml", "**/*.md",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *ProjectConfig) EnhanceWithDefaults() *ProjectConfig {
|
||||||
|
defaultCfg := DefaultProjectConfig()
|
||||||
|
if len(cfg.WatchFiles) == 0 {
|
||||||
|
cfg.WatchFiles = defaultCfg.WatchFiles
|
||||||
|
}
|
||||||
|
if len(cfg.WatchIgnore) == 0 {
|
||||||
|
cfg.WatchIgnore = defaultCfg.WatchIgnore
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromConfigFile(workingDir string) *ProjectConfig {
|
||||||
|
defaultCfg := DefaultProjectConfig()
|
||||||
|
names := []string{"htmgo.yaml", "htmgo.yml", "_htmgo.yaml", "_htmgo.yml"}
|
||||||
|
for _, name := range names {
|
||||||
|
filePath := path.Join(workingDir, name)
|
||||||
|
if _, err := os.Stat(filePath); err == nil {
|
||||||
|
cfg := &ProjectConfig{}
|
||||||
|
bytes, err := os.ReadFile(filePath)
|
||||||
|
if err == nil {
|
||||||
|
err = yaml.Unmarshal(bytes, cfg)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Error parsing config file", slog.String("file", filePath), slog.String("error", err.Error()))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return cfg.EnhanceWithDefaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultCfg
|
||||||
|
}
|
||||||
50
framework/config/project_test.go
Normal file
50
framework/config/project_test.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefaultProjectConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cfg := DefaultProjectConfig()
|
||||||
|
assert.Equal(t, true, cfg.Tailwind)
|
||||||
|
assert.Equal(t, 4, len(cfg.WatchIgnore))
|
||||||
|
assert.Equal(t, 8, len(cfg.WatchFiles))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoConfigFileUsesDefault(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cfg := FromConfigFile("non-existing-dir")
|
||||||
|
assert.Equal(t, true, cfg.Tailwind)
|
||||||
|
assert.Equal(t, 4, len(cfg.WatchIgnore))
|
||||||
|
assert.Equal(t, 8, len(cfg.WatchFiles))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartialConfigMerges(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dir := writeConfigFile(t, "tailwind: false")
|
||||||
|
cfg := FromConfigFile(dir)
|
||||||
|
assert.Equal(t, false, cfg.Tailwind)
|
||||||
|
assert.Equal(t, 4, len(cfg.WatchIgnore))
|
||||||
|
assert.Equal(t, 8, len(cfg.WatchFiles))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldNotSetTailwindTrue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dir := writeConfigFile(t, "someValue: true")
|
||||||
|
cfg := FromConfigFile(dir)
|
||||||
|
assert.Equal(t, false, cfg.Tailwind)
|
||||||
|
assert.Equal(t, 4, len(cfg.WatchIgnore))
|
||||||
|
assert.Equal(t, 8, len(cfg.WatchFiles))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeConfigFile(t *testing.T, content string) string {
|
||||||
|
temp := os.TempDir()
|
||||||
|
os.Mkdir(temp, 0755)
|
||||||
|
err := os.WriteFile(path.Join(temp, "htmgo.yml"), []byte(content), 0644)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
return temp
|
||||||
|
}
|
||||||
10
htmgo-site/htmgo.yml
Normal file
10
htmgo-site/htmgo.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# htmgo configuration
|
||||||
|
|
||||||
|
# if tailwindcss is enabled, htmgo will automatically compile your tailwind and output it to assets/dist
|
||||||
|
tailwind: true
|
||||||
|
|
||||||
|
# which directories to ignore when watching for changes, supports glob patterns through https://github.com/bmatcuk/doublestar
|
||||||
|
watch_ignore: [".git", "node_modules", "dist/*"]
|
||||||
|
|
||||||
|
# files to watch for changes, supports glob patterns through https://github.com/bmatcuk/doublestar
|
||||||
|
watch_files: ["**/*.go", "**/*.css", "**/*.md"]
|
||||||
|
|
@ -45,7 +45,8 @@ func PageWithNav(ctx *h.RequestContext, children ...h.Ren) *h.Element {
|
||||||
return RootPage(ctx,
|
return RootPage(ctx,
|
||||||
h.Fragment(
|
h.Fragment(
|
||||||
partials.NavBar(ctx, partials.NavBarProps{
|
partials.NavBar(ctx, partials.NavBarProps{
|
||||||
Expanded: false,
|
Expanded: false,
|
||||||
|
ShowPreRelease: true,
|
||||||
}),
|
}),
|
||||||
h.Div(
|
h.Div(
|
||||||
children...,
|
children...,
|
||||||
|
|
|
||||||
10
templates/starter/htmgo.yml
Normal file
10
templates/starter/htmgo.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# htmgo configuration
|
||||||
|
|
||||||
|
# if tailwindcss is enabled, htmgo will automatically compile your tailwind and output it to assets/dist
|
||||||
|
tailwind: true
|
||||||
|
|
||||||
|
# which directories to ignore when watching for changes, supports glob patterns through https://github.com/bmatcuk/doublestar
|
||||||
|
watch_ignore: [".git", "node_modules", "dist/*"]
|
||||||
|
|
||||||
|
# files to watch for changes, supports glob patterns through https://github.com/bmatcuk/doublestar
|
||||||
|
watch_files: ["**/*.go", "**/*.css", "**/*.md"]
|
||||||
Loading…
Reference in a new issue