render takes a map of templates/values and renders them.
(tpls map[string]renderable)
| 257 | |
| 258 | // render takes a map of templates/values and renders them. |
| 259 | func (e Engine) render(tpls map[string]renderable) (rendered map[string]string, err error) { |
| 260 | // Basically, what we do here is start with an empty parent template and then |
| 261 | // build up a list of templates -- one for each file. Once all of the templates |
| 262 | // have been parsed, we loop through again and execute every template. |
| 263 | // |
| 264 | // The idea with this process is to make it possible for more complex templates |
| 265 | // to share common blocks, but to make the entire thing feel like a file-based |
| 266 | // template engine. |
| 267 | defer func() { |
| 268 | if r := recover(); r != nil { |
| 269 | err = fmt.Errorf("rendering template failed: %v", r) |
| 270 | } |
| 271 | }() |
| 272 | t := template.New("gotpl") |
| 273 | if e.Strict { |
| 274 | t.Option("missingkey=error") |
| 275 | } else { |
| 276 | // Not that zero will attempt to add default values for types it knows, |
| 277 | // but will still emit <no value> for others. We mitigate that later. |
| 278 | t.Option("missingkey=zero") |
| 279 | } |
| 280 | |
| 281 | e.initFunMap(t) |
| 282 | |
| 283 | // We want to parse the templates in a predictable order. The order favors |
| 284 | // higher-level (in file system) templates over deeply nested templates. |
| 285 | keys := sortTemplates(tpls) |
| 286 | |
| 287 | for _, filename := range keys { |
| 288 | r := tpls[filename] |
| 289 | if _, err := t.New(filename).Parse(r.tpl); err != nil { |
| 290 | return map[string]string{}, cleanupParseError(filename, err) |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | rendered = make(map[string]string, len(keys)) |
| 295 | for _, filename := range keys { |
| 296 | // Don't render partials. We don't care out the direct output of partials. |
| 297 | // They are only included from other templates. |
| 298 | if strings.HasPrefix(path.Base(filename), "_") { |
| 299 | continue |
| 300 | } |
| 301 | // At render time, add information about the template that is being rendered. |
| 302 | vals := tpls[filename].vals |
| 303 | vals["Template"] = common.Values{"Name": filename, "BasePath": tpls[filename].basePath} |
| 304 | var buf strings.Builder |
| 305 | if err := t.ExecuteTemplate(&buf, filename, vals); err != nil { |
| 306 | return map[string]string{}, reformatExecErrorMsg(filename, err) |
| 307 | } |
| 308 | |
| 309 | // Work around the issue where Go will emit "<no value>" even if Options(missing=zero) |
| 310 | // is set. Since missing=error will never get here, we do not need to handle |
| 311 | // the Strict case. |
| 312 | rendered[filename] = strings.ReplaceAll(buf.String(), "<no value>", "") |
| 313 | } |
| 314 | |
| 315 | return rendered, nil |
| 316 | } |