func init() { blog_config, err := config.ReadJsonFile("blarg_config.json") if err != nil { panic(err) } root := config.Stringify(blog_config["blog_root"]) // test for default m := pat.New() handle_method := func(method string, urlpattern string, handler http.HandlerFunc) { m.Add(method, root+urlpattern, handler) } handle := func(urlpattern string, handler http.HandlerFunc) { m.Get(root+urlpattern, handler) } handle("list/:page/:invalid", http.NotFound) handle("list/:page", layout.IndexPageHandler(blog_config, "list")) handle("list/", layout.IndexListHandler(blog_config, "list")) handle("index/", layout.IndexListHandler(blog_config, "list")) handle("article/:name/:invalid", http.NotFound) handle("article/:name", layout.GetArticle(blog_config, "article")) handle("article/", http.NotFound) handle("label/:label/:page/:invalid", http.NotFound) handle("label/:label/:page", layout.LabelPage(blog_config, "label")) handle("label/:label", layout.LabelList(blog_config, "label")) handle("label/", http.NotFound) handle("admin/edit/:name/:invalid", http.NotFound) handle("admin/edit/:name", layout.EditPost(blog_config)) handle("admin/edit/", layout.EditPost(blog_config)) handle("admin/dump/all.json/:invalid", http.NotFound) handle("admin/dump/all.json", layout.JSONAllEntries(blog_config)) handle("admin/dump/:invalid", http.NotFound) handle("admin/gettext/:name/:invalid", http.NotFound) handle("admin/gettext/:name", layout.GetPostText(blog_config)) handle_method("POST", "admin/render/:invalid", http.NotFound) handle_method("POST", "admin/render/", layout.RenderPost(blog_config)) // pat seems to interfere with the blobstore's MIME parsing http.HandleFunc(root+"admin/post", layout.Save(blog_config)) handle("sitemap.xml", layout.GetSitemap(blog_config)) m.Get("/index.rss", http.HandlerFunc(layout.GetRSS(blog_config))) // matching on / will match all URLs // so you have to catch invalid top-level URLs first handle(":invalid/", http.NotFound) handle("", layout.IndexListHandler(blog_config, "list")) http.Handle(root, m) }
func entrybar(blog_config map[string]interface{}, w http.ResponseWriter, req *http.Request) { template_dir := "templates/" appcontext := appengine.NewContext(req) myurl, err := realhostname(req, appcontext) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } scheme := "http://" if req.TLS != nil { scheme = "https://" } urlprefix := scheme + myurl + config.Stringify(blog_config["blog_root"]) postchan := make(chan post.Post, 16) errchan := make(chan error) go post.ExecuteQuery(appcontext, post.GetAllPosts(), -1, -1, func(post.Post) bool { return true }, postchan, errchan) for p := range postchan { context := map[string]interface{}{"url": urlprefix + "article/" + p.StickyUrl, "lastmod_date": p.Postdate.Format("2006-01-02")} total_con := config.Stringify_map(config.Merge(blog_config, context)) c := mustache.RenderFile(template_dir+"edit_entrybar.html.mustache", total_con) io.WriteString(w, c) } err, ok := <-errchan if ok { http.Error(w, err.Error(), http.StatusInternalServerError) return } }
func labels(tags []string, blog_config map[string]interface{}) string { root := config.Stringify(blog_config["blog_root"]) labels := bytes.NewBufferString("") for _, l := range tags { if l != "visible" { fmt.Fprintf(labels, "<a href=\"%slabel/%s\">%s</a> ", root, l, l) } } return string(labels.Bytes()) }
func std_layout(blog_config map[string]interface{}, f func(w http.ResponseWriter, req *http.Request)) func(w http.ResponseWriter, req *http.Request) { p := func(w http.ResponseWriter, req *http.Request) { appcontext := appengine.NewContext(req) myurl, err := realhostname(req, appcontext) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } scheme := "http://" if req.TLS != nil { scheme = "https://" } context := map[string]interface{}{"app_url": scheme + myurl + config.Stringify(blog_config["blog_root"])} bloginfo := config.Stringify_map(config.Merge(blog_config, context)) start := time.Now() template_dir := "templates/" h := mustache.RenderFile(template_dir+"header.html.mustache", bloginfo) io.WriteString(w, h) f(w, req) sidebar(w, req, blog_config) delta := time.Since(start).Seconds() timing := map[string]interface{}{"timing": fmt.Sprintf("%0.2fs", delta)} if delta > 0.100 { timing["slow_code"] = "true" } u := user.Current(appcontext) if u != nil { logout, err := user.LogoutURL(appcontext, "/") if err != nil { http.Error(w, "error generating logout URL!", http.StatusInternalServerError) appcontext.Errorf("user.LogoutURL: %v", err) return } timing["me"] = fmt.Sprintf("%s", u) timing["logout"] = logout } bloginfo = config.Stringify_map(config.Merge(blog_config, timing)) f := mustache.RenderFile(template_dir+"footer.html.mustache", bloginfo) io.WriteString(w, f) } return p }
func getLimit(blog_config map[string]interface{}) (int, error) { var limit int if limtext, ok := blog_config["post_limit"]; ok { _, err := fmt.Sscanf(config.Stringify(limtext), "%d", &limit) if err != nil { return 0, err } } else { limit = 20 } return limit, nil }
func GetSitemap(blog_config map[string]interface{}) func(w http.ResponseWriter, req *http.Request) { template_dir := "templates/" l := func(w http.ResponseWriter, req *http.Request) { appcontext := appengine.NewContext(req) myurl, err := realhostname(req, appcontext) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } scheme := "http://" if req.TLS != nil { scheme = "https://" } urlprefix := scheme + myurl + config.Stringify(blog_config["blog_root"]) postchan := make(chan post.Post, 16) errchan := make(chan error) go post.ExecuteQuery(appcontext, post.GetAllPosts(), -1, -1, post.NullFilter, postchan, errchan) entries := bytes.NewBufferString("") me_context := map[string]interface{}{"url": urlprefix, "lastmod_date": post.GetLatestDate(appcontext).Format("2006-01-02")} me_total_con := config.Stringify_map(config.Merge(blog_config, me_context)) me_c := mustache.RenderFile(template_dir+"sitemap_entry.mustache", me_total_con) io.WriteString(entries, me_c) for p := range postchan { context := map[string]interface{}{"url": urlprefix + "article/" + p.StickyUrl, "lastmod_date": p.Postdate.Format("2006-01-02")} total_con := config.Stringify_map(config.Merge(blog_config, context)) c := mustache.RenderFile(template_dir+"sitemap_entry.mustache", total_con) io.WriteString(entries, c) } err, ok := <-errchan if ok { http.Error(w, err.Error(), http.StatusInternalServerError) return } context := map[string]interface{}{"content": entries} total_con := config.Stringify_map(config.Merge(blog_config, context)) c := mustache.RenderFile(template_dir+"sitemap.mustache", total_con) io.WriteString(w, c) } return l }
func list(w http.ResponseWriter, req *http.Request, blog_config map[string]interface{}, url_stem string, offset int, limit int, keygen func(appengine.Context) ([]*datastore.Key, error)) { appcontext := appengine.NewContext(req) template_dir := "templates/" postchan := make(chan post.FullPost, 16) errchan := make(chan error) keys, err := keygen(appcontext) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } idx := len(keys) pages := idx / limit if (idx % limit) > 0 { pages += 1 } curpage := offset / limit if idx < 1 { io.WriteString(w, fmt.Sprintf("<div class=\"entry\"><p>no posts found :(</p></div>")) } else { //go post.ExecuteQuery(appcontext, query, offset, limit, postchan, errchan) go post.GetPosts(appcontext, keys, offset, limit, postchan, errchan) myurl, err := realhostname(req, appcontext) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } scheme := "http://" if req.TLS != nil { scheme = "https://" } urlprefix := scheme + myurl + config.Stringify(blog_config["blog_root"]) for p := range postchan { con := bytes.NewBuffer(blackfriday.MarkdownCommon(bytes.NewBufferString(p.Content).Bytes())).String() context := map[string]interface{}{"c": con, "labels": labels(p.Post.Tags, blog_config), "link_to_entry": urlprefix + "article/" + p.Post.StickyUrl, "post_date": p.Post.Postdate.Format("Jan 02 2006"), "post_title": p.Post.Title} total_con := config.Stringify_map(config.Merge(blog_config, context)) c := mustache.RenderFile(template_dir+"list_entry.html.mustache", total_con) io.WriteString(w, c) } err, ok := <-errchan if ok { http.Error(w, err.Error(), http.StatusInternalServerError) return } context := map[string]interface{}{} root := config.Stringify(blog_config["blog_root"]) context["prev_page"] = "<< prev" context["next_page"] = "next >>" if pages > 1 { context["pb"] = "true" } if curpage > 0 { context["prev_page"] = fmt.Sprintf("<a href=\"%v%v/%v\"><< prev</a>", root, url_stem, curpage-1) } if curpage < (pages - 1) { context["next_page"] = fmt.Sprintf("<a href=\"%v%v/%v\">next >></a>", root, url_stem, curpage+1) } total_con := config.Stringify_map(config.Merge(blog_config, context)) c := mustache.RenderFile(template_dir+"list.html.mustache", total_con) io.WriteString(w, c) } }
func GetRSS(blog_config map[string]interface{}) func(w http.ResponseWriter, req *http.Request) { template_dir := "templates/" l := func(w http.ResponseWriter, req *http.Request) { limit, err := getLimit(blog_config) if err != nil { panic(err) } appcontext := appengine.NewContext(req) myurl, err := realhostname(req, appcontext) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } scheme := "http://" if req.TLS != nil { scheme = "https://" } urlprefix := scheme + myurl + config.Stringify(blog_config["blog_root"]) keys, err := post.GetPostsMatchingTag(appcontext, "visible") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } idx := len(keys) offset := 0 entries := new(bytes.Buffer) if idx >= 1 { postchan := make(chan post.FullPost, 16) errchan := make(chan error) go post.GetPosts(appcontext, keys, offset, limit, postchan, errchan) for p := range postchan { con := bytes.NewBuffer(blackfriday.MarkdownCommon(bytes.NewBufferString(p.Content).Bytes())).String() context := map[string]interface{}{"c": con, "link_to_entry": urlprefix + "article/" + p.Post.StickyUrl, "post_date": p.Post.Postdate.Format("Jan 02 2006"), "post_title": p.Post.Title} total_con := config.Stringify_map(config.Merge(blog_config, context)) c := mustache.RenderFile(template_dir+"rss_entry.html.mustache", total_con) io.WriteString(entries, c) } err, ok := <-errchan if ok { http.Error(w, err.Error(), http.StatusInternalServerError) return } } context := map[string]interface{}{ "lastmod_date": post.GetLatestDate(appcontext).Format("2006-01-02"), "content": entries, } total_con := config.Stringify_map(config.Merge(blog_config, context)) c := mustache.RenderFile(template_dir+"rss.mustache", total_con) io.WriteString(w, c) } return l }
func GetArticle(blog_config map[string]interface{}, url_stem string) func(w http.ResponseWriter, req *http.Request) { template_dir := "templates/" l := func(w http.ResponseWriter, req *http.Request) { appcontext := appengine.NewContext(req) query := post.GetPostsMatchingUrl(req.URL.Query().Get(":name")) idx, err := post.GetCount(appcontext, query) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if idx < 1 { http.Error(w, err.Error(), http.StatusNotFound) io.WriteString(w, fmt.Sprintf("<div class=\"entry\"><p>no posts found :(</p></div>")) return } else { myurl, err := realhostname(req, appcontext) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } scheme := "http://" if req.TLS != nil { scheme = "https://" } urlprefix := scheme + myurl + config.Stringify(blog_config["blog_root"]) var ps []post.Post keys, err := query.GetAll(appcontext, &ps) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } p := ps[0] tags, err := post.GetTagSlice(appcontext, keys[0]) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } content, err := post.GetPostContent(appcontext, p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) appcontext.Errorf("post.GetPostContent: %v", err) return } con := bytes.NewBuffer(blackfriday.MarkdownCommon(bytes.NewBufferString(content).Bytes())).String() context := map[string]interface{}{"c": con, "labels": labels(tags, blog_config), "link_to_entry": urlprefix + "article/" + p.StickyUrl, "comment_display": "none", "post_date": p.Postdate.Format("Oct 02 2006"), "post_title": p.Title} total_con := config.Stringify_map(config.Merge(blog_config, context)) c := mustache.RenderFile(template_dir+"entry.html.mustache", total_con) io.WriteString(w, c) } } return std_layout(blog_config, l) }