Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mna committed Jul 10, 2013
0 parents commit a558b46
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
*.swp
*.swo
tags
trofaf
59 changes: 59 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"html/template"
"log"
"os"
"path/filepath"

"github.com/eknkc/amber"
"github.com/howeyc/fsnotify"
"github.com/jessevdk/go-flags"
)

var (
postTpl *template.Template
)

func main() {
_, err := flags.Parse(&Options)
if err == nil { // err prints the usage automatically
// Compile the template(s)
compileTemplate()
// Generate the site
regeneratePosts()

// Start the watcher
w, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal("FATAL ", err)
}
defer w.Close()
go watch(w)
if err = w.Watch(PostsDir); err != nil {
log.Fatal("FATAL ", err)
}

// Start the web server
run()
}
}

func compileTemplate() {
ap := filepath.Join(TemplatesDir, "post.amber")
if _, err := os.Stat(ap); os.IsNotExist(err) {
// Amber post template does not exist, compile the native Go templates
postTpl, err = template.ParseGlob(filepath.Join(TemplatesDir, "*.html"))
if err != nil {
log.Fatal("FATAL ", err)
}
} else {
c := amber.New()
if err := c.ParseFile(ap); err != nil {
log.Fatal("FATAL ", err)
}
if postTpl, err = c.Compile(); err != nil {
log.Fatal("FATAL ", err)
}
}
}
28 changes: 28 additions & 0 deletions opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"log"
"os"
"path/filepath"
)

type options struct {
Port int `short:"p" long:"port" description:"the port to use for the web server" default:"9000"`
}

var (
Options options
PublicDir string
PostsDir string
TemplatesDir string
)

func init() {
pwd, err := os.Getwd()
if err != nil {
log.Fatal("FATAL ", err)
}
PublicDir = filepath.Join(pwd, "public/")
PostsDir = filepath.Join(pwd, "posts/")
TemplatesDir = filepath.Join(pwd, "templates/")
}
3 changes: 3 additions & 0 deletions posts/test1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This is my first post!

I'm so happy to write this right now!
1 change: 1 addition & 0 deletions posts/test2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Test 2!
1 change: 1 addition & 0 deletions posts/test3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Test 3!
Empty file added public/.test3.md
Empty file.
Empty file added public/test.txt
Empty file.
Empty file added public/test1
Empty file.
Empty file added public/test2
Empty file.
Empty file added public/test3
Empty file.
38 changes: 38 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"fmt"
"log"
"net/http"
"path/filepath"
"time"

"github.com/PuerkitoBio/ghost/handlers"
)

var (
faviconPath = filepath.Join(PublicDir, "favicon.ico")
faviconCache = 2 * 24 * time.Hour
)

func run() {
h := handlers.FaviconHandler(
handlers.PanicHandler(
handlers.LogHandler(
handlers.GZIPHandler(
http.FileServer(http.Dir(PublicDir)),
nil),
handlers.NewLogOptions(nil, handlers.Lshort)),
nil),
faviconPath,
faviconCache)

// Assign the combined handler to the server.
http.Handle("/", h)

// Start it up.
log.Printf("trofaf server listening on port %d", Options.Port)
if err := http.ListenAndServe(fmt.Sprintf(":%d", Options.Port), nil); err != nil {
log.Fatal("FATAL ", err)
}
}
18 changes: 18 additions & 0 deletions templates/layout.amber
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
!!! html
// From HTML5 Boilerplate
html.no-js
head
meta[charset="utf-8"]
meta[http-equiv="X-UA-Compatible"][content="IE=edge"]
title ${Title}
meta[name="description"][content=Description]
meta[name="viewport"][content="width=device-width, initial-scale=1"]
link[rel="stylesheet"][href="/public/css/normalize.css"]
link[rel="stylesheet"][href="/public/css/main.css"]
body
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->

block content

4 changes: 4 additions & 0 deletions templates/post.amber
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
extends layout

block content
h1 My Post!
101 changes: 101 additions & 0 deletions watch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strings"
"time"

"github.com/howeyc/fsnotify"
)

const (
// Sometimes many events can be triggered in succession for the same file
// (i.e. Create followed by Modify, etc.). No need to rush to generate
// the HTML, just wait for it to calm down before processing.
watchEventDelay = 30 * time.Second
)

// Receive watcher events for the posts directory. All events require re-generating
// the whole site (because the template may display the n most recent posts, the
// next and previous post, etc.). It could be fine-tuned based on what data we give
// to the templates, but for now, lazy approach.
func watch(w *fsnotify.Watcher) {
var delay <-chan time.Time
for {
select {
case ev := <-w.Event:
log.Print("watch event ", ev)
// Regenerate the files after the delay, reset the delay if an event is triggered
// in the meantime
delay = time.After(watchEventDelay)

case err := <-w.Error:
log.Print("WATCH ERROR ", err)

case <-delay:
log.Print("trigger regeneration of site")
regeneratePosts()
}
}
}

type sortableFileInfo []os.FileInfo

func (s sortableFileInfo) Len() int { return len(s) }
func (s sortableFileInfo) Less(i, j int) bool { return s[i].ModTime().Before(s[j].ModTime()) }
func (s sortableFileInfo) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

func FilterDir(s sortableFileInfo) sortableFileInfo {
for i := 0; i < len(s); {
if s[i].IsDir() {
s[i], s = s[len(s)-1], s[:len(s)-1]
} else {
i++
}
}
return s
}

func regeneratePosts() {
fis, err := ioutil.ReadDir(PostsDir)
if err != nil {
log.Fatal("FATAL ", err)
}
sfi := sortableFileInfo(fis)
sfi = FilterDir(sfi)
sort.Sort(sfi)
for _, fi := range sfi {
regenerateFile(fi)
}
}

// TODO : Should pass to the template:
// Title : The first heading in the file, or the file name, or front matter?
// Description : ?
// ModTime
// Parsed : The html-parsed markdown
// Recent : A slice of n recent posts
// Next : The next (more recent) post
// Previous : The previous (older) post

func regenerateFile(fi os.FileInfo) {
f, err := os.Open(filepath.Join(PostsDir, fi.Name()))
if err != nil {
log.Fatal("FATAL ", err)
}
defer f.Close()
// TODO : Blackfriday...

nm := fi.Name()
nm = strings.Replace(nm, filepath.Ext(nm), "", 1)
fw, err := os.Create(filepath.Join(PublicDir, nm))
if err != nil {
log.Fatal("FATAL ", err)
}
defer fw.Close()
postTpl.ExecuteTemplate(fw, "post", nil)
}

0 comments on commit a558b46

Please sign in to comment.