summaryrefslogtreecommitdiff
path: root/vendor/github.com/a-h/templ/watchmode.go
blob: a94dab28869db272b36883e239dc7bc54292113b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package templ

import (
	"errors"
	"fmt"
	"io"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

// WriteWatchModeString is used when rendering templates in development mode.
// the generator would have written non-go code to the _templ.txt file, which
// is then read by this function and written to the output.
//
// Deprecated: since templ v0.3.x generated code uses WriteString.
func WriteWatchModeString(w io.Writer, lineNum int) error {
	_, path, _, _ := runtime.Caller(1)
	if !strings.HasSuffix(path, "_templ.go") {
		return errors.New("templ: WriteWatchModeString can only be called from _templ.go")
	}
	txtFilePath := strings.Replace(path, "_templ.go", "_templ.txt", 1)

	literals, err := getWatchedStrings(txtFilePath)
	if err != nil {
		return fmt.Errorf("templ: failed to cache strings: %w", err)
	}

	if lineNum > len(literals) {
		return fmt.Errorf("templ: failed to find line %d in %s", lineNum, txtFilePath)
	}

	s, err := strconv.Unquote(`"` + literals[lineNum-1] + `"`)
	if err != nil {
		return err
	}
	_, err = io.WriteString(w, s)
	return err
}

var (
	watchModeCache  = map[string]watchState{}
	watchStateMutex sync.Mutex
)

type watchState struct {
	modTime time.Time
	strings []string
}

func getWatchedStrings(txtFilePath string) ([]string, error) {
	watchStateMutex.Lock()
	defer watchStateMutex.Unlock()

	state, cached := watchModeCache[txtFilePath]
	if !cached {
		return cacheStrings(txtFilePath)
	}

	if time.Since(state.modTime) < time.Millisecond*100 {
		return state.strings, nil
	}

	info, err := os.Stat(txtFilePath)
	if err != nil {
		return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
	}

	if !info.ModTime().After(state.modTime) {
		return state.strings, nil
	}

	return cacheStrings(txtFilePath)
}

func cacheStrings(txtFilePath string) ([]string, error) {
	txtFile, err := os.Open(txtFilePath)
	if err != nil {
		return nil, fmt.Errorf("templ: failed to open %s: %w", txtFilePath, err)
	}
	defer func() {
		_ = txtFile.Close()
	}()

	info, err := txtFile.Stat()
	if err != nil {
		return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
	}

	all, err := io.ReadAll(txtFile)
	if err != nil {
		return nil, fmt.Errorf("templ: failed to read %s: %w", txtFilePath, err)
	}

	literals := strings.Split(string(all), "\n")
	watchModeCache[txtFilePath] = watchState{
		modTime: info.ModTime(),
		strings: literals,
	}

	return literals, nil
}