package templating import ( "fmt" "html/template" "io" "log" "net/http" "officeHours/models" "os" "time" ) // parsed templates available for execution var templates map[string]*template.Template = map[string]*template.Template{} // Initialise and parse templates. // // Should only be called once. // // Since this is something which may error and feels like // it should not be done automatically on import, // put it into a function. func InitTemplates() error { const templateDir = "templating/templates/" var funcs = template.FuncMap{ "DayName": models.DayName, "divide": func(i int, j int) int { return i / j }, } var emptyTemplate = template.New("").Funcs(funcs) var baseTemplate, err = template.ParseFiles(templateDir + "base.html") if err != nil { return fmt.Errorf("parsing base template failed:\n%w", err) } baseTemplate.Funcs(funcs) type toCache struct { filename string standalone bool } var toParse = map[string]toCache{ // full html templates "addFailure": {"addFailure.html", false}, "addMask": {"addMask.html", false}, "addSuccess": {"addSuccess.html", false}, "deleteSuccess": {"deleteSuccess.html", false}, "executeFailure": {"executeFailure.html", false}, "executeSuccess": {"executeSuccess.html", false}, "index": {"index.html", false}, "requestNotFound": {"requestNotFound.html", false}, // standalone templates "confirmationMail": {"confirmationMail", true}, "officeHourTable": {"officeHourTable.html", true}, "td": {"td.html", true}, } // parse templates and add to global mapping for key, tmpl := range toParse { if _, exists := templates[key]; exists { return fmt.Errorf("template '%s' already parsed", key) } fullName := templateDir + tmpl.filename // check that template file info, err := os.Stat(fullName) if err != nil { return fmt.Errorf("adding template %s failed:\n%w", tmpl.filename, err) } if info.IsDir() { return fmt.Errorf("adding template %s failed: is a directory", tmpl.filename) } // parse var parsed *template.Template if tmpl.standalone { parsed, err = emptyTemplate.Clone() parsed = parsed.New(tmpl.filename) } else { parsed, err = baseTemplate.Clone() } if err != nil { return fmt.Errorf("cloning base template failed:\n%w", err) } parsed, err = parsed.ParseFiles(fullName) if err != nil { return fmt.Errorf("parsing template %s failed:\n%w", tmpl.filename, err) } templates[key] = parsed } return nil } // Execute a template and write it to the given writer. // // Parameters: // - name: name of the template to execute // - data: passed through to the template func WriteTemplate(w io.Writer, name string, data any) error { tmpl, exists := templates[name] if !exists { return fmt.Errorf("template %s not available", name) } tmpl, err := tmpl.Clone() if err != nil { return fmt.Errorf("cloning template failed:\n%w", err) } err = tmpl.Execute(w, data) if err != nil { return fmt.Errorf("template execution failed:\n%w", err) } return nil } // Execute a template and write it to a http writer. // // Similar to WriteTemplate, but in error case this adds a corresponding // status code and writes an error message to the writer. // // Typically, this is the final action in handling an http request. // // Parameters: // - name: name of the template to execute // - data: passed through to the template func ServeTemplate(w http.ResponseWriter, name string, data any) { // TODO: make this return an error, handle on top of every request handler err := WriteTemplate(w, name, data) if err != nil { err = fmt.Errorf("writing template failed:\n%w", err) log.Println(err.Error()) w.WriteHeader(http.StatusInternalServerError) io.WriteString(w, `Internal Server Error. Du kannst uns helfen, indem du folgende Fehlermeldung per Mail an sprechstunden@mathebau.de sendest: Fehler um\n `+time.Now().String()+"\n"+err.Error()) return } }