package site import ( "fmt" "net/http" "net/url" "path" "path/filepath" "strings" ) // GET / func (s *Server) serveTemplates(w http.ResponseWriter, r *http.Request) { ctx := r.Context() name, pathParams := getPageTemplateNameForURL(r.URL) b, err := s.templater.ExecutePage( name, "Request", r, // add services here "RawEvents", s.rawEvents.WithContext(ctx), "URLCalc", newURLCalculator(r.URL), "PathParams", pathParams, "Accounts", s.accts.WithContext(ctx), "Etsy", s.etsy.WithContext(ctx), ) if err != nil { // TODO: handle 'not found' as a 404? fmt.Println("[ERROR]: failed to load or parse layout template:", err) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } w.Write(b) } // TODO: clean this up... // TODO: somehow tell what the path params are and pass them up. // - then consider pushing this functionality into the template library. // // getPageTemplateNameForURL eliminate any trailing .html or /, and checks for any // file with path parameters in the name, eg '{abc}.html.tmpl', prefering exact filename matches. func getPageTemplateNameForURL(u *url.URL) (name string, params map[string]string) { fp := strings.TrimPrefix(strings.TrimSuffix(strings.TrimSuffix(u.Path, ".html"), "/"), "/") if fp == "" { // "/" maps to "/index" fp = "index" } fpParts := strings.Split(fp, "/") res := getMatchingGlobPatternsCapturingFilepathIncludingParametrizedFilepaths(fpParts) for _, combs := range res { const pageBodiesPrefix = "internal/site/templates/page_bodies" pattern := path.Join(pageBodiesPrefix, path.Join(combs...)) + ".html.tmpl" matches, _ := filepath.Glob(pattern) if len(matches) == 0 { pattern := path.Join(pageBodiesPrefix, path.Join(combs...), "index") + ".html.tmpl" matches, _ = filepath.Glob(pattern) } if len(matches) > 0 { match := matches[0] name = strings.TrimPrefix(strings.TrimSuffix(match, ".html.tmpl"), pageBodiesPrefix+"/") patternParts := strings.Split(name, "/") params = make(map[string]string) for i, pp := range patternParts { if strings.HasPrefix(pp, "{") && strings.HasSuffix(pp, "}") { params[pp[1:len(pp)-1]] = fpParts[i] } } return name, params } } return fp, nil } func getMatchingGlobPatternsCapturingFilepathIncludingParametrizedFilepaths(filepathParts []string) [][]string { switch len(filepathParts) { case 0: return nil case 1: return [][]string{ []string{filepathParts[0]}, []string{"{*}"}, } default: tailCombs := getMatchingGlobPatternsCapturingFilepathIncludingParametrizedFilepaths(filepathParts[1:]) combs := make([][]string, 2*len(tailCombs)) for i, c := range tailCombs { combs[i*2] = append([]string{filepathParts[0]}, c...) combs[i*2+1] = append([]string{"{*}"}, c...) } return combs } } type URLCalculator struct { url *url.URL } func newURLCalculator(u *url.URL) URLCalculator { cpy := *u return URLCalculator{ url: &cpy, } } func (c URLCalculator) SetQueryParam(k string, v any) string { u := *c.url q := u.Query() q.Set(k, fmt.Sprint(v)) u.RawQuery = q.Encode() return u.String() }