template.go 1.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. package templates
  2. import (
  3. "bytes"
  4. "embed"
  5. "encoding/json"
  6. "errors"
  7. "io"
  8. "math"
  9. "sync"
  10. "github.com/agnivade/levenshtein"
  11. )
  12. //go:embed index.json
  13. var indexBytes []byte
  14. //go:embed *.gotmpl
  15. var templatesFS embed.FS
  16. var templatesOnce = sync.OnceValues(func() ([]*Template, error) {
  17. var templates []*Template
  18. if err := json.Unmarshal(indexBytes, &templates); err != nil {
  19. return nil, err
  20. }
  21. for _, t := range templates {
  22. bts, err := templatesFS.ReadFile(t.Name + ".gotmpl")
  23. if err != nil {
  24. return nil, err
  25. }
  26. // normalize line endings
  27. t.Bytes = bytes.ReplaceAll(bts, []byte("\r\n"), []byte("\n"))
  28. }
  29. return templates, nil
  30. })
  31. type Template struct {
  32. Name string `json:"name"`
  33. Template string `json:"template"`
  34. Bytes []byte
  35. }
  36. func (t Template) Reader() io.Reader {
  37. return bytes.NewReader(t.Bytes)
  38. }
  39. func NamedTemplate(s string) (*Template, error) {
  40. templates, err := templatesOnce()
  41. if err != nil {
  42. return nil, err
  43. }
  44. var template *Template
  45. score := math.MaxInt
  46. for _, t := range templates {
  47. if s := levenshtein.ComputeDistance(s, t.Template); s < score {
  48. score = s
  49. template = t
  50. }
  51. }
  52. if score < 100 {
  53. return template, nil
  54. }
  55. return nil, errors.New("no matching template found")
  56. }