diff options
author | thinkpadmaster <a.scerba02@gmail.com> | 2023-07-13 23:34:25 -0500 |
---|---|---|
committer | thinkpadmaster <a.scerba02@gmail.com> | 2023-07-13 23:34:25 -0500 |
commit | e864807341990f5c72e198d96740983bf7671584 (patch) | |
tree | d0c9dd12da81272e7bd30cae4643bacdc030d47b | |
parent | 8106f531198952489de53c93dd3c955e8ea4d78f (diff) |
Move to multifile system and prepare for new file format
-rw-r--r-- | errors.go | 23 | ||||
-rw-r--r-- | handle.go | 66 | ||||
-rw-r--r-- | load.go | 131 | ||||
-rw-r--r-- | main.go | 49 | ||||
-rw-r--r-- | render.go | 20 | ||||
-rw-r--r-- | site.go | 228 |
6 files changed, 289 insertions, 228 deletions
diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..9406a9a --- /dev/null +++ b/errors.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "net/http" + "runtime/debug" +) + +func (app *application) serverError(w http.ResponseWriter, err error) { + trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack()) + app.errorLog.Output(2, trace) + + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) +} + +func (app *application) clientError(w http.ResponseWriter, status int) { + app.errorLog.Printf("Clent error: %d\n", status) + http.Error(w, http.StatusText(status), status) +} + +func (app *application) notFound(w http.ResponseWriter) { + app.clientError(w, http.StatusNotFound) +} diff --git a/handle.go b/handle.go new file mode 100644 index 0000000..20fa259 --- /dev/null +++ b/handle.go @@ -0,0 +1,66 @@ +package main + +import ( + "net/http" + "strings" +) + +func (app *application) home(w http.ResponseWriter, r *http.Request) { + path := strings.Split(r.URL.Path, "/") + if path[1] != "" { + app.notFound(w) + return + } else { + p, err := app.loadPosts("html/projects", 3) + if err != nil { + app.serverError(w, err) + return + } + + err = renderTemplate(w, "main/index", p) + if err != nil { + app.serverError(w, err) + return + } + } +} + +func (app *application) about(w http.ResponseWriter, r *http.Request) { + err := renderTemplate(w, "main/about", nil) + if err != nil { + app.serverError(w, err) + return + } +} + +func (app *application) aggregate(w http.ResponseWriter, r *http.Request) { + p, err := app.loadPosts("html"+strings.TrimSuffix(r.URL.Path, "/"), -1) + if err != nil { + app.notFound(w) + } + + renderTemplate(w, "main/"+strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/"), p) +} + +func (app *application) post(w http.ResponseWriter, r *http.Request) { + path := strings.Split(r.URL.Path, "/") + if path[2] == "" { + app.aggregate(w, r) + } else { + post, err := app.readFile("html" + strings.TrimSuffix(r.URL.Path, "/") + ".tmpl.html") + if err != nil { + app.notFound(w) + return + } + + var posts []*Post + posts = append(posts, post) + p := &Posts{Contents: posts} + + err = renderTemplate(w, path[1]+"/"+path[2], p) + if err != nil { + app.serverError(w, err) + return + } + } +} @@ -0,0 +1,131 @@ +package main + +import ( + "os" + "regexp" + "sort" + "strings" +) + +type Post struct { + FileName string + Title string + Date string + Tags []string + Image string +} + +type Posts struct { + Contents []*Post +} + +func (p Post) containsTag(filterTag string) bool { + if filterTag == "" { + return true + } + + for _, tag := range p.Tags { + if filterTag == tag { + return true + } + } + + return false +} + +func (app *application) loadPosts(location string, postCount int) (p *Posts, err error) { + if postCount == 0 || postCount < -1 { + return nil, os.ErrInvalid + } + + var posts []*Post + + files, err := os.ReadDir(location) + if err != nil { + return nil, err + } + + for _, file := range files { + if !file.IsDir() && strings.HasSuffix(file.Name(), ".tmpl.html") { + newPost, err := app.readFile(location + "/" + file.Name()) + if err != nil { + return nil, err + } + /* + // filtering by tag + if !newPost.containsTag(filterTag) { + continue + } */ + + posts = append(posts, newPost) + } + } + + sort.Slice(posts, func(i, j int) bool { + return posts[i].Date > posts[j].Date + }) + + if postCount == -1 { + return &Posts{Contents: posts}, nil + } else if postCount < len(posts) { + return &Posts{Contents: posts[:postCount]}, nil + } else { + return &Posts{Contents: posts}, nil + } +} + +func (app *application) readFile(location string) (p *Post, err error) { + fileContent, err := os.ReadFile(location) + if err != nil { + return nil, err + } + + var tmp *Post = new(Post) + + fileName := strings.TrimSuffix(strings.Split(location, "/")[2], ".tmpl.html") + + // title + title := strings.ReplaceAll(fileName, "_", " ") + + // date + datePattern := regexp.MustCompile(`<time datetime="(\d{4}-\d{2}-\d{2})">`) + dateMatching := datePattern.FindStringSubmatch(string(fileContent)) + + var date string + if len(dateMatching) > 1 { + date = dateMatching[1] + } else { + date = "" + } + + // tags + tagsPattern := regexp.MustCompile(`{{define "keywords"}}([\w\s]+){{end}}`) + tagsMatching := tagsPattern.FindStringSubmatch(string(fileContent)) + + var tags []string + if len(tagsMatching) > 1 { + tags = strings.Fields(tagsMatching[1]) + } else { + tags = []string{} + } + + // thumbnail image + imagePattern := regexp.MustCompile(`<img class="mainImage" src="(.+)"( alt="(.+)")*>`) + imageMatching := imagePattern.FindStringSubmatch(string(fileContent)) + + var image string + if len(imageMatching) > 1 { + image = imageMatching[0] + } else { + image = "" + } + + tmp.FileName = fileName + tmp.Title = title + tmp.Date = date + tmp.Tags = tags + tmp.Image = image + + return tmp, nil + +} @@ -0,0 +1,49 @@ +package main + +import ( + "log" + "net/http" + "os" +) + +type application struct { + errorLog *log.Logger + infoLog *log.Logger +} + +func (app *application) httpsRedirect(w http.ResponseWriter, req *http.Request) { + // remove/add not default ports from req.Host + target := "https://" + req.Host + req.URL.Path + if len(req.URL.RawQuery) > 0 { + target += "?" + req.URL.RawQuery + } + app.infoLog.Printf("redirect to: %s", target) + http.Redirect(w, req, target, + // see comments below and consider the codes 308, 302, or 301 + http.StatusMovedPermanently) +} + +func main() { + infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime) + errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile) + + app := &application{ + errorLog: errorLog, + infoLog: infoLog, + } + + mux := http.NewServeMux() + + fs := http.FileServer(http.Dir("./static")) + mux.Handle("/static/", http.StripPrefix("/static/", fs)) + + mux.HandleFunc("/projects/", app.post) + mux.HandleFunc("/blog/", app.post) + mux.HandleFunc("/about/", app.about) + mux.HandleFunc("/", app.home) + + infoLog.Println("Starting server...") + //go http.ListenAndServe(":80", http.HandlerFunc(app.httpsRedirect)) + //errorLog.Fatal(http.ListenAndServeTLS(":443", "/etc/letsencrypt/live/alexscerba.com/fullchain.pem", "/etc/letsencrypt/live/alexscerba.com/privkey.pem", nil)) + errorLog.Fatal(http.ListenAndServe(":4000", mux)) // for local dev because I'm lazy +} diff --git a/render.go b/render.go new file mode 100644 index 0000000..fe6c2f1 --- /dev/null +++ b/render.go @@ -0,0 +1,20 @@ +package main + +import ( + "net/http" + "text/template" +) + +func renderTemplate(w http.ResponseWriter, tmplPath string, p *Posts) (err error) { + t, err := template.ParseFiles("html/master.tmpl.html", "html/"+tmplPath+".tmpl.html") + if err != nil { + return err + } + + err = t.Execute(w, p) + if err != nil { + return err + } + + return nil +} diff --git a/site.go b/site.go deleted file mode 100644 index ca919f8..0000000 --- a/site.go +++ /dev/null @@ -1,228 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "log" - "net/http" - "os" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "text/template" -) - -type Post struct { - Time int - Meta []byte - Thumbnail []byte - Content []byte -} - -type Posts struct { - Contents []*Post -} - -func loadPosts(location string, postCount int) (p *Posts, err error) { - if postCount == 0 || postCount < -1 { - return nil, os.ErrInvalid - } - - var tmpPosts []*Post - - if postCount == 1 { - tmpPosts = append(tmpPosts, readFile(location+".html")) - } else { - var depthCount = 0 - - // TODO - Use less computationally heavy opperation for scalability - err = filepath.Walk(location, func(path string, _ os.FileInfo, _ error) error { - var directory, err = regexp.MatchString("^data/(projects|blog)$", path) - if err != nil { - return err - } - - if directory { - depthCount++ - } else if postCount == -1 || depthCount < postCount+1 { - tmp := readFile(path) - tmpPosts = append(tmpPosts, tmp) - depthCount++ - - } else { - depthCount++ - } - - return nil - }) - - if err != nil { - return nil, err - } - - sort.Slice(tmpPosts, func(i, j int) bool { - return tmpPosts[i].Time > tmpPosts[j].Time - }) - - } - - return &Posts{Contents: tmpPosts}, nil -} - -func readFile(location string) (p *Post) { - file, err := os.Open(location) - if err != nil { - log.Fatal(err) - } - - scanner := bufio.NewScanner(file) - - var section = 0 - var tmp *Post = new(Post) - -LineLoop: - for scanner.Scan() { - var line = scanner.Text() - var lineByte = []byte(line + "\n") - - if line == "{{br}}" { // this is not happy with any space or tab characters that follow, - section++ // so make sure to check if there's whitespace after a {{br}} if - goto LineLoop // there are any errors. I'll fix this later...TM - } - - switch section { - case 0: - time, err := strconv.Atoi(strings.TrimSuffix(string(lineByte), "\n")) - if err != nil { - log.Fatal(err) - } else { - tmp.Time = time - } - case 1: - tmp.Meta = append(tmp.Meta, lineByte...) - case 2: - tmp.Thumbnail = append(tmp.Thumbnail, lineByte...) - case 3: - tmp.Content = append(tmp.Content, lineByte...) - } - } - - if err := scanner.Err(); err != nil { - log.Fatal(err) - } - - file.Close() - return tmp -} - -func renderTemplate(w http.ResponseWriter, tmpl string, p *Posts) { - if tmpl == "post" { - t, err := template.ParseFiles("templates/master.html", "templates/postHelp.tmpl") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - err = t.Execute(w, p.Contents[0]) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - } else { - t, err := template.ParseFiles("templates/master.html", "templates/"+tmpl+".html") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - err = t.Execute(w, p) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - } -} - -func rootHandler(w http.ResponseWriter, r *http.Request) { - path := strings.Split(r.URL.Path, "/") - if path[1] != "" { - http.NotFound(w, r) - } else { - p, err := loadPosts("data/projects", 3) - if err != nil { - http.NotFound(w, r) - } - - renderTemplate(w, "index", p) - } -} - -func aboutHandler(w http.ResponseWriter, r *http.Request) { - renderTemplate(w, "about", nil) -} - -func agHandler(w http.ResponseWriter, r *http.Request) { - p, err := loadPosts("data"+strings.TrimSuffix(r.URL.Path, "/"), -1) - if err != nil { - http.NotFound(w, r) - } - - renderTemplate(w, strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/"), p) -} - -func postHandler(w http.ResponseWriter, r *http.Request) { - path := strings.Split(r.URL.Path, "/") - if path[2] == "" { - agHandler(w, r) - } else { - p, err := loadPosts("data/"+path[1]+"/"+path[2], 1) - if p == nil || err != nil { - http.NotFound(w, r) - } else { - renderTemplate(w, "post", p) - } - } -} - -func fileHandler(w http.ResponseWriter, r *http.Request) { - body, _ := os.ReadFile("data/www/" + r.URL.Path) - - w.Header().Set("Content-Type", "text/css; charset=utf-8") - fmt.Fprintf(w, "%s", body) -} - -func makeHandler(fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - fn(w, r) - } -} - -func httpsRedirect(w http.ResponseWriter, req *http.Request) { - // remove/add not default ports from req.Host - target := "https://" + req.Host + req.URL.Path - if len(req.URL.RawQuery) > 0 { - target += "?" + req.URL.RawQuery - } - log.Printf("redirect to: %s", target) - http.Redirect(w, req, target, - // see comments below and consider the codes 308, 302, or 301 - http.StatusMovedPermanently) -} - -func main() { - /* hostValid, _ := regexp.MatchString("^alexscerba.com$", r.URL.Path) - if !hostValid { - req, _ := http.NewRequest(r.Method, "alexscerba.com"+r.URL.Path, r.Body) - redirect(w, req) - } */ - - fs := http.FileServer(http.Dir("./data/static")) - http.Handle("/static/", http.StripPrefix("/static/", fs)) - - http.HandleFunc("/projects/", postHandler) - http.HandleFunc("/blog/", postHandler) - http.HandleFunc("/about/", aboutHandler) - http.HandleFunc("/", rootHandler) - - go http.ListenAndServe(":80", http.HandlerFunc(httpsRedirect)) - log.Fatal(http.ListenAndServeTLS(":443", "/etc/letsencrypt/live/alexscerba.com/fullchain.pem", "/etc/letsencrypt/live/alexscerba.com/privkey.pem", nil)) - //log.Fatal(http.ListenAndServe(":4000", nil)) // for local dev because I'm lazy -} |