|
| 1 | +package cataloghtml |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + _ "embed" |
| 6 | + "html/template" |
| 7 | + "os" |
| 8 | + "path" |
| 9 | + "path/filepath" |
| 10 | + "reflect" |
| 11 | + |
| 12 | + "github.com/warpfork/warpforge/pkg/workspace" |
| 13 | + "github.com/warpfork/warpforge/wfapi" |
| 14 | +) |
| 15 | + |
| 16 | +var ( |
| 17 | + //go:embed catalogIndex.tmpl.html |
| 18 | + catalogIndexTemplate string |
| 19 | + |
| 20 | + //go:embed catalogModule.tmpl.html |
| 21 | + catalogModuleTemplate string |
| 22 | + |
| 23 | + //go:embed catalogRelease.tmpl.html |
| 24 | + catalogReleaseTemplate string |
| 25 | + |
| 26 | + //go:embed css.css |
| 27 | + cssBody []byte |
| 28 | + |
| 29 | + // FUTURE: consider the use of `embed.FS` and `template.ParseFS()`, if there grow to be many files here. |
| 30 | + // It has slightly less compile-time safety checks on filenames, though. |
| 31 | +) |
| 32 | + |
| 33 | +type SiteConfig struct { |
| 34 | + Ctx context.Context |
| 35 | + |
| 36 | + // Data Access Broker for getting Catalog info. |
| 37 | + // Some functions pass around data in memory, |
| 38 | + // but sometimes those objects just contain CIDs, which we'll need to go load. |
| 39 | + // This has helper functions that do the loading. |
| 40 | + // Arguably should be a parameter, but would end up in almost every single function, so, eh. |
| 41 | + Cat_dab workspace.Catalog |
| 42 | + |
| 43 | + // A plain string for output path prefix is used because golang still lacks |
| 44 | + // an interface for filesystem *writing* -- io/fs is only reading. Sigh. |
| 45 | + OutputPath string |
| 46 | + |
| 47 | + // Set to "/" if you'll be publishing at the root of a subdomain. |
| 48 | + URLPrefix string |
| 49 | +} |
| 50 | + |
| 51 | +func (cfg SiteConfig) tfuncs() map[string]interface{} { |
| 52 | + return map[string]interface{}{ |
| 53 | + "string": func(x interface{}) string { |
| 54 | + // Very small helper function to stringify things. |
| 55 | + // This is useful for things that are literally typedefs of string but the template package isn't smart enough to be calm about unboxing it. |
| 56 | + // (It also does return something for values of non-string types, but not something very useful.) |
| 57 | + return reflect.ValueOf(x).String() |
| 58 | + }, |
| 59 | + "url": func(parts ...string) string { |
| 60 | + return path.Join(append([]string{cfg.URLPrefix}, parts...)...) |
| 61 | + }, |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +// CatalogAndChildrenToHtml performs CatalogToHtml, and also |
| 66 | +// procedes to invoke the html'ing of all modules within. |
| 67 | +// Additionally, it does all the other "once" things |
| 68 | +// (namely, outputs a copy of the css). |
| 69 | +// |
| 70 | +// Errors: |
| 71 | +// |
| 72 | +// - warpforge-error-io -- in case of errors writing out the new html content. |
| 73 | +// - warpforge-error-internal -- in case of templating errors. |
| 74 | +// - warpforge-error-catalog-invalid -- in case the catalog data is invalid. |
| 75 | +// - warpforge-error-catalog-parse -- in case the catalog data failed to parse entirely. |
| 76 | +func (cfg SiteConfig) CatalogAndChildrenToHtml() error { |
| 77 | + // Emit catalog index. |
| 78 | + if err := cfg.CatalogToHtml(); err != nil { |
| 79 | + return err |
| 80 | + } |
| 81 | + // Emit the "once" stuff. |
| 82 | + if err := os.WriteFile(filepath.Join(cfg.OutputPath, "css.css"), cssBody, 0644); err != nil { |
| 83 | + return wfapi.ErrorIo("couldn't open file for css as part of cataloghtml emission", nil, err) |
| 84 | + } |
| 85 | + // Emit all modules within. |
| 86 | + modNames := cfg.Cat_dab.Modules() |
| 87 | + for _, modName := range modNames { |
| 88 | + catMod, err := cfg.Cat_dab.GetModule(wfapi.CatalogRef{modName, "", ""}) |
| 89 | + if err != nil { |
| 90 | + return err |
| 91 | + } |
| 92 | + if err := cfg.CatalogModuleAndChildrenToHtml(*catMod); err != nil { |
| 93 | + return err |
| 94 | + } |
| 95 | + } |
| 96 | + return nil |
| 97 | +} |
| 98 | + |
| 99 | +// doTemplate does the common bits of making files, processing the template, |
| 100 | +// and getting the output where it needs to go. |
| 101 | +// |
| 102 | +// Errors: |
| 103 | +// |
| 104 | +// - warpforge-error-io -- in case of errors writing out the new html content. |
| 105 | +// - warpforge-error-internal -- in case of templating errors. |
| 106 | +func (cfg SiteConfig) doTemplate(outputPath string, tmpl string, data interface{}) error { |
| 107 | + if err := os.MkdirAll(filepath.Dir(outputPath), 0775); err != nil { |
| 108 | + return wfapi.ErrorIo("couldn't mkdir during cataloghtml emission", nil, err) |
| 109 | + } |
| 110 | + f, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0664) |
| 111 | + if err != nil { |
| 112 | + return wfapi.ErrorIo("couldn't open file for writing during cataloghtml emission", nil, err) |
| 113 | + } |
| 114 | + defer f.Close() |
| 115 | + |
| 116 | + t := template.Must(template.New("main").Funcs(cfg.tfuncs()).Parse(tmpl)) |
| 117 | + if err := t.Execute(f, data); err != nil { |
| 118 | + return wfapi.ErrorInternal("templating failed", err) |
| 119 | + } |
| 120 | + return nil |
| 121 | +} |
| 122 | + |
| 123 | +// CatalogToHtml generates a root page that links to all the modules. |
| 124 | +// |
| 125 | +// This function has no parameters because it uses the DAB in the SiteConfig entirely. |
| 126 | +// |
| 127 | +// Errors: |
| 128 | +// |
| 129 | +// - warpforge-error-io -- in case of errors writing out the new html content. |
| 130 | +// - warpforge-error-internal -- in case of templating errors. |
| 131 | +func (cfg SiteConfig) CatalogToHtml() error { |
| 132 | + // Future: It's perhaps a bit odd that this uses the workspace.Catalog object instead of the API object. We probably haven't hammered out appropriate data access helpers yet. |
| 133 | + return cfg.doTemplate( |
| 134 | + filepath.Join(cfg.OutputPath, "index.html"), |
| 135 | + catalogIndexTemplate, |
| 136 | + cfg.Cat_dab.Modules(), |
| 137 | + ) |
| 138 | +} |
| 139 | + |
| 140 | +// CatalogModuleAndChildrenToHtml performs CatalogModuleToHtml, and also |
| 141 | +// procedes to invoke the html'ing of all releases within. |
| 142 | +// |
| 143 | +// Errors: |
| 144 | +// |
| 145 | +// - warpforge-error-io -- in case of errors writing out the new html content. |
| 146 | +// - warpforge-error-internal -- in case of templating errors. |
| 147 | +// - warpforge-error-catalog-invalid -- in case the catalog data is invalid. |
| 148 | +// - warpforge-error-catalog-parse -- in case the catalog data failed to parse entirely. |
| 149 | +func (cfg SiteConfig) CatalogModuleAndChildrenToHtml(catMod wfapi.CatalogModule) error { |
| 150 | + if err := cfg.CatalogModuleToHtml(catMod); err != nil { |
| 151 | + return err |
| 152 | + } |
| 153 | + for _, releaseName := range catMod.Releases.Keys { |
| 154 | + rel, err := cfg.Cat_dab.GetRelease(wfapi.CatalogRef{catMod.Name, releaseName, ""}) |
| 155 | + if err != nil { |
| 156 | + return err |
| 157 | + } |
| 158 | + if err := cfg.ReleaseToHtml(catMod, *rel); err != nil { |
| 159 | + return err |
| 160 | + } |
| 161 | + } |
| 162 | + return nil |
| 163 | +} |
| 164 | + |
| 165 | +// CatalogModuleToHtml generates a page for a module which enumerates |
| 166 | +// and links to all the releases within it, |
| 167 | +// as well as enumerates all the metadata attached to the catalog module. |
| 168 | +// |
| 169 | +// Errors: |
| 170 | +// |
| 171 | +// - warpforge-error-io -- in case of errors writing out the new html content. |
| 172 | +// - warpforge-error-internal -- in case of templating errors. |
| 173 | +func (cfg SiteConfig) CatalogModuleToHtml(catMod wfapi.CatalogModule) error { |
| 174 | + return cfg.doTemplate( |
| 175 | + filepath.Join(cfg.OutputPath, string(catMod.Name), "_module.html"), |
| 176 | + catalogModuleTemplate, |
| 177 | + catMod, |
| 178 | + ) |
| 179 | +} |
| 180 | + |
| 181 | +// CatalogModuleToHtml generates a page for a release within a catalog module |
| 182 | +// which enumerates all the items within it, |
| 183 | +// as well as enumerates all the metadata attached to the release. |
| 184 | +// |
| 185 | +// Possible but not-yet-implemented future features of this output might include: |
| 186 | +// linking better to metadata that references other documents (such as Replays); |
| 187 | +// links to neighboring (e.g. forward and previous) releases; etc. |
| 188 | +// |
| 189 | +// Errors: |
| 190 | +// |
| 191 | +// - warpforge-error-io -- in case of errors writing out the new html content. |
| 192 | +// - warpforge-error-internal -- in case of templating errors. |
| 193 | +func (cfg SiteConfig) ReleaseToHtml(catMod wfapi.CatalogModule, rel wfapi.CatalogRelease) error { |
| 194 | + return cfg.doTemplate( |
| 195 | + filepath.Join(cfg.OutputPath, string(catMod.Name), "_releases", string(rel.ReleaseName)+".html"), |
| 196 | + catalogReleaseTemplate, |
| 197 | + map[string]interface{}{ |
| 198 | + "Module": catMod, |
| 199 | + "Release": rel, |
| 200 | + }, |
| 201 | + ) |
| 202 | +} |
0 commit comments