blob: 33f8d8d02b729985319c6ae9ad77daae37da22f0 [file] [log] [blame]
gio5e49bb62024-07-20 10:43:19 +04001package welcome
2
3import (
4 "fmt"
5 "io"
6 "io/fs"
giob54db242024-07-30 18:49:33 +04007 "sort"
gio5e49bb62024-07-20 10:43:19 +04008 "strings"
9 "text/template"
10
11 "github.com/giolekva/pcloud/core/installer"
12 "github.com/giolekva/pcloud/core/installer/soft"
13)
14
15const tmplSuffix = ".gotmpl"
16
17type AppTmplStore interface {
gio8fae3af2024-07-25 13:43:31 +040018 Types() []string
gio5e49bb62024-07-20 10:43:19 +040019 Find(appType string) (AppTmpl, error)
20}
21
22type appTmplStoreFS struct {
23 tmpls map[string]AppTmpl
24}
25
26func NewAppTmplStoreFS(fsys fs.FS) (AppTmplStore, error) {
27 entries, err := fs.ReadDir(fsys, ".")
28 if err != nil {
29 return nil, err
30 }
31 apps := map[string]AppTmpl{}
32 for _, e := range entries {
33 if !e.IsDir() {
34 continue
35 }
36 app, err := NewAppTmplFS(fsys, e.Name())
37 if err != nil {
38 return nil, err
39 }
40 apps[e.Name()] = app
41 }
42 return &appTmplStoreFS{apps}, nil
43}
44
gio8fae3af2024-07-25 13:43:31 +040045func (s *appTmplStoreFS) Types() []string {
46 var ret []string
47 for t := range s.tmpls {
48 ret = append(ret, t)
49 }
giob54db242024-07-30 18:49:33 +040050 sort.Slice(ret, func(i, j int) bool {
51 a := strings.SplitN(ret[i], ":", 2)
52 b := strings.SplitN(ret[j], ":", 2)
53 langCmp := strings.Compare(a[0], b[0])
54 if langCmp != 0 {
55 return langCmp < 0
56 }
57 // TODO(gio): compare semver?
58 return strings.Compare(a[1], b[1]) > 0
59 })
gio8fae3af2024-07-25 13:43:31 +040060 return ret
61}
62
gio5e49bb62024-07-20 10:43:19 +040063func (s *appTmplStoreFS) Find(appType string) (AppTmpl, error) {
64 if app, ok := s.tmpls[appType]; ok {
65 return app, nil
66 } else {
67 return nil, fmt.Errorf("not found")
68 }
69}
70
71type AppTmpl interface {
72 Render(network installer.Network, subdomain string, out soft.RepoFS) error
73}
74
75type appTmplFS struct {
76 files map[string][]byte
77 tmpls map[string]*template.Template
78}
79
80func NewAppTmplFS(fsys fs.FS, root string) (AppTmpl, error) {
81 files := map[string][]byte{}
82 tmpls := map[string]*template.Template{}
83 if err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
84 if err != nil {
85 return err
86 }
87 if d.IsDir() {
88 return nil
89 }
90 contents, err := fs.ReadFile(fsys, path)
91 if err != nil {
92 return err
93 }
94 p, _ := strings.CutPrefix(path, root)
95 if !strings.HasSuffix(p, tmplSuffix) {
96 files[p] = contents
97 return nil
98 }
99 tmpl, err := template.New(path).Parse(string(contents))
100 if err != nil {
101 return err
102 }
103 np, _ := strings.CutSuffix(p, tmplSuffix)
104 tmpls[np] = tmpl
105 return nil
106 }); err != nil {
107 return nil, err
108 }
109 return &appTmplFS{files, tmpls}, nil
110}
111
112func (a *appTmplFS) Render(network installer.Network, subdomain string, out soft.RepoFS) error {
113 for path, tmpl := range a.tmpls {
114 f, err := out.Writer(path)
115 if err != nil {
116 return err
117 }
118 defer f.Close()
119 if err := tmpl.Execute(f, map[string]any{
120 "Network": network,
121 "Subdomain": subdomain,
122 }); err != nil {
123 return err
124 }
125 }
126 for path, contents := range a.files {
127 f, err := out.Writer(path)
128 if err != nil {
129 return err
130 }
131 defer f.Close()
132 if _, err := io.WriteString(f, string(contents)); err != nil {
133 return err
134 }
135 }
136 return nil
137}