commit 71d6b1156f60947dd71d3ed492ec3ee340b3a575 Author: Aaron Bieber Date: Fri Feb 3 13:29:15 2017 -0700 functional static site generator diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bc29bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +vendor/* +boring +dateconv +util/dateconv diff --git a/README.md b/README.md new file mode 100644 index 0000000..297faee --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +boring +###### + +Static site generator written in go. + +# What does it do? + +It takes the markdown, and puts it in the HTML! + diff --git a/glide.lock b/glide.lock new file mode 100644 index 0000000..ce92d9a --- /dev/null +++ b/glide.lock @@ -0,0 +1,10 @@ +hash: 83b61e3f32d25a66b5faa0922f14dd294a540f1b9918921aa73af340de31ad24 +updated: 2017-02-03T09:14:47.670780155-07:00 +imports: +- name: github.com/russross/blackfriday + version: 0b647d0506a698cca42caca173e55559b12a69f2 +- name: github.com/shurcooL/sanitized_anchor_name + version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 +- name: github.com/ylih/extrasys + version: 48c6a025836d8f4eb4ffd5470ceafcf140932118 +testImports: [] diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..969309d --- /dev/null +++ b/glide.yaml @@ -0,0 +1,5 @@ +package: code.bolddaemon.com/qbit/boring +import: +- package: github.com/russross/blackfriday + version: ~1.4.0 +- package: github.com/ylih/extrasys diff --git a/main.go b/main.go new file mode 100644 index 0000000..4fbe4b8 --- /dev/null +++ b/main.go @@ -0,0 +1,290 @@ +package main + +import ( + "bufio" + "fmt" + "html/template" + "io/ioutil" + "log" + "os" + "regexp" + "sort" + "strings" + "time" + + "github.com/russross/blackfriday" + // "github.com/ylih/extrasys" +) + +var templ *template.Template + +var funcMap = template.FuncMap{ + "formatDate": func(t time.Time) string { + return t.Format(time.RFC1123) + }, + "shortDate": func(t time.Time) string { + return t.Format("January _2, 2006") + }, + "printByte": func(b []byte) string { + return string(b) + }, + "lop": func(p Posts, start, end int) Posts { + return p[start:end] + }, + "joinTags": func(ts Tags) template.HTML { + var s []string + for _, t := range ts { + s = append(s, fmt.Sprintf(`%s`, t.Name)) + } + return template.HTML(strings.Join(s, ", ")) + }, + "printHTML": func(b []byte) template.HTML { + return template.HTML(string(b)) + }, +} + +type response struct { + User interface{} + Data interface{} +} + +// AuthorRE is a regex to grab our Authors +var AuthorRE = regexp.MustCompile(`^author:\s(.*)$`) + +// TitleRE matches our article title +var TitleRE = regexp.MustCompile(`^title:\s(.*)$`) + +// DateRE matches our article date +var DateRE = regexp.MustCompile(`^date:\s(.*)$`) + +// TagRE matches the tags for a given post +var TagRE = regexp.MustCompile(`^tags:\s(.*)$`) + +// DescRE matches the descriptoin for a given post +var DescRE = regexp.MustCompile(`^description:\s(.*)$`) + +// Tag represents a specific tag for an article +type Tag struct { + ID int + Created time.Time + Name string +} + +// Tags are a collection of Tag +type Tags []*Tag + +// Join returns a concat'd string of Tag names +func (t *Tags) Join() []string { + var s []string + for _, t := range *t { + s = append(s, t.Name) + } + return s +} + +func (t *Tags) String() string { + return strings.Join(t.Join(), ", ") +} + +// User represents an author of an article +type User struct { + LName string + FName string + Email string + Pubkey []byte + User string +} + +var userLineRE = regexp.MustCompile(`^(.*)\s(.*)\s<(.*)>$`) + +// Parse takes a 'First Last ' style string and creates a User +func (u *User) Parse(s string) { + u.FName = userLineRE.ReplaceAllString(s, "$1") + u.LName = userLineRE.ReplaceAllString(s, "$2") + u.Email = userLineRE.ReplaceAllString(s, "$3") +} + +// Combine concatenates FName, LName and Email into one line +func (u *User) Combine() string { + return fmt.Sprintf("%s %s <%s>", u.FName, u.LName, u.Email) +} + +// Post is the base type for all posts +type Post struct { + Title string + Description string + Date time.Time + Body []byte + Author User + Signed bool + Signature []byte + Tags Tags + URL string +} + +// HTML returns converted MD to HTML +func (p *Post) HTML() { + p.Body = blackfriday.MarkdownCommon(p.Body) +} + +// LoadFromFile takes the File of a given page and loads the markdown for rendering +func (p *Post) LoadFromFile(f string) error { + file, err := os.Open(f) + if err != nil { + return err + } + scanner := bufio.NewScanner(file) + if err != nil { + return err + } + + for scanner.Scan() { + var line = scanner.Bytes() + useLine := true + if AuthorRE.Match(line) { + aline := AuthorRE.ReplaceAllString(string(line), "$1") + p.Author.Parse(aline) + fmt.Printf("Author: %s %s (%s)\n", p.Author.FName, p.Author.LName, p.Author.Email) + useLine = false + } + if TitleRE.Match(line) { + p.Title = TitleRE.ReplaceAllString(string(line), "$1") + fmt.Printf("Title: %s\n", p.Title) + useLine = false + } + if DateRE.Match(line) { + d := DateRE.ReplaceAllString(string(line), "$1") + p.Date, _ = time.Parse(time.RFC1123, d) + fmt.Printf("Date: %s\n", p.Date) + useLine = false + } + + if TagRE.Match(line) { + ts := TagRE.ReplaceAllString(string(line), "$1") + for _, tag := range strings.Split(ts, ",") { + var t Tag + t.Name = strings.TrimSpace(tag) + p.Tags = append(p.Tags, &t) + } + fmt.Printf("Tags: %s\n", p.Tags.Join()) + useLine = false + } + + if DescRE.Match(line) { + p.Description = DescRE.ReplaceAllString(string(line), "$1") + fmt.Printf("Description: %s\n", p.Description) + useLine = false + } + + if useLine { + p.Body = append(p.Body, line...) + p.Body = append(p.Body, 10) + } + } + + if err != nil { + return err + } + return nil +} + +// Posts represent a collection of a set of Post +type Posts []*Post + +func (p Posts) Len() int { + return len(p) +} + +func (p Posts) Less(i, j int) bool { + return p[i].Date.After(p[j].Date) +} + +func (p Posts) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} + +func renderPost(f string, path string) (Post, error) { + var err error + p := Post{} + + p.LoadFromFile(f) + + if err != nil { + log.Fatal(err) + } + + p.HTML() + p.URL = "/" + md2html(path) + + return p, nil +} + +func renderTemplate(dst string, tmpl string, data interface{}) { + o, err := os.Create(dst) + defer o.Close() + + if err != nil { + log.Fatal(err) + } + + err = templ.ExecuteTemplate(o, tmpl, data) + if err != nil { + log.Fatal(err) + } +} + +func md2html(f string) string { + return strings.Replace(f, ".md", ".html", -1) +} + +func main() { + var err error + // extrasys.Pledge("stdio wpath rpath cpath", nil) + + if len(os.Args) < 2 { + fmt.Println("Wrong number of arguments") + os.Exit(1) + } + + src := os.Args[1] + tmpl := os.Args[2] + dst := os.Args[3] + + templ, err = template.New("boring").Funcs(funcMap).ParseGlob(tmpl + "/*.html") + if err != nil { + log.Fatal(err) + } + + log.Printf("Generating static html from %s to %s\n", src, dst) + + files, err := ioutil.ReadDir(src) + if err != nil { + log.Fatal(err) + } + + posts := Posts{} + for _, file := range files { + fn := file.Name() + srcFile := src + "/" + fn + dstFile := dst + "/" + md2html(fn) + post, err := renderPost(srcFile, fn) + if err != nil { + log.Fatal(err) + } + + renderTemplate(dstFile, "default.html", struct { + Content Post + }{ + post, + }) + + posts = append(posts, &post) + } + + sort.Sort(posts) + + renderTemplate(dst+"/index.html", "index.html", posts) + renderTemplate(dst+"/about.html", "about.html", posts[0].Author) + renderTemplate(dst+"/contact.html", "contact.html", posts[0].Author) + renderTemplate(dst+"/archive.html", "archive.html", posts[5:]) +} diff --git a/util/dateconv.go b/util/dateconv.go new file mode 100644 index 0000000..589bd44 --- /dev/null +++ b/util/dateconv.go @@ -0,0 +1,12 @@ +package main + +import ( + "fmt" + "os" + "time" +) + +func main() { + t, _ := time.Parse("2006-01-02", os.Args[1]) + fmt.Println(t.Format(time.RFC1123)) +}