aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthinkpadmaster <a.scerba02@gmail.com>2023-07-13 23:34:25 -0500
committerthinkpadmaster <a.scerba02@gmail.com>2023-07-13 23:34:25 -0500
commite864807341990f5c72e198d96740983bf7671584 (patch)
treed0c9dd12da81272e7bd30cae4643bacdc030d47b
parent8106f531198952489de53c93dd3c955e8ea4d78f (diff)
Move to multifile system and prepare for new file format
-rw-r--r--errors.go23
-rw-r--r--handle.go66
-rw-r--r--load.go131
-rw-r--r--main.go49
-rw-r--r--render.go20
-rw-r--r--site.go228
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
+ }
+ }
+}
diff --git a/load.go b/load.go
new file mode 100644
index 0000000..b6866e3
--- /dev/null
+++ b/load.go
@@ -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
+
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..296cae2
--- /dev/null
+++ b/main.go
@@ -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
-}