From cd933959d09c91dfd32a49925056413a363d3f63 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Jan 2013 21:34:41 +1000 Subject: [PATCH] yeehaw first preview working. --- bootstrap/config.json | 2 +- bootstrap/posts/index.md | 1 + journo.js | 125 ++++++++++++++++++++++++++++++++++----- journo.litcoffee | 79 ++++++++++++++++++++++--- package.json | 1 + 5 files changed, 183 insertions(+), 25 deletions(-) create mode 100644 bootstrap/posts/index.md diff --git a/bootstrap/config.json b/bootstrap/config.json index 888dd0b..10b7aa2 100644 --- a/bootstrap/config.json +++ b/bootstrap/config.json @@ -10,7 +10,7 @@ "path": "blog", "secure": false, "user": "admin", - "password": "password", + "password": "password" }, "s3": { diff --git a/bootstrap/posts/index.md b/bootstrap/posts/index.md new file mode 100644 index 0000000..6a2c9c8 --- /dev/null +++ b/bootstrap/posts/index.md @@ -0,0 +1 @@ +Home page diff --git a/journo.js b/journo.js index 7e77abb..fb4c437 100644 --- a/journo.js +++ b/journo.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 1.5.0-pre (function() { - var Highlight, Journo, compareManifest, config, emptyManifest, fs, ftp, loadConfig, loadManifest, manifest, manifestPath, marked, path, postPath, postUrl, s3Client, siteUrl, writeManifest, _, + var Highlight, Journo, allPosts, compareManifest, config, emptyManifest, fatal, fs, ftp, knox, loadConfig, loadManifest, manifest, manifestPath, marked, path, postPath, postUrl, s3, siteUrl, skeleton, writeManifest, _, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Journo = module.exports = {}; @@ -9,8 +9,21 @@ _ = require('underscore'); - Journo.markdown = function(source) { - return marked.parser(marked.lexer(source)); + skeleton = null; + + Journo.render = function(source) { + var content, lexed, markdown, title; + skeleton || (skeleton = _.template(fs.readFileSync('skeleton.html').toString())); + markdown = source = _.template(source.toString())({}); + lexed = marked.lexer(markdown); + title = _.find(lexed, function(token) { + return token.type === 'heading'; + }); + content = marked.parser(lexed); + return skeleton({ + title: title, + content: content + }); }; fs = require('fs'); @@ -20,14 +33,13 @@ ftp = config = siteUrl = manifest = null; Journo.publish = function() { - var FTPClient, post, posts, todo, _i, _j, _len, _len1, _ref, _ref1; + var FTPClient, post, todo, _i, _j, _len, _len1, _ref, _ref1; FTPClient = require('ftp'); ftp = new FTPClient; - loadConfig(); - loadManifest(); - posts = fs.readdirSync('posts'); - todo = compareManifest(posts); + Journo.build(); + todo = compareManifest(allPosts()); ftp.connect(config.ftp); + Journo.publishPage; _ref = todo.puts; for (_i = 0, _len = _ref.length; _i < _len; _i++) { post = _ref[_i]; @@ -43,11 +55,33 @@ return true; }; + Journo.build = function() { + var html, markdown, post, _i, _len, _ref, _results; + loadConfig(); + loadManifest(); + if (!fs.existsSync('site')) { + fs.mkdirSync('site'); + } + _ref = allPosts(); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + post = _ref[_i]; + markdown = fs.readFileSync(postPath(post)); + html = Journo.render(markdown); + _results.push(fs.writeFileSync("site/" + (path.basename(post, '.md')) + ".html", html)); + } + return _results; + }; + loadConfig = function() { if (config) { return; } - config = JSON.parse(fs.readFileSync('config.json')); + try { + config = JSON.parse(fs.readFileSync('config.json')); + } catch (err) { + fatal("Unable to read config.json"); + } return siteUrl = config.url.replace(/\/$/, ''); }; @@ -59,6 +93,15 @@ return "" + siteUrl + "/" + post; }; + allPosts = function() { + return fs.readdirSync('posts'); + }; + + fatal = function(message) { + console.error(message); + return process.exit(1); + }; + Journo.FTP = {}; Journo.FTP.publishPost = function(post) { @@ -71,11 +114,30 @@ Journo.S3 = {}; - s3Client = null; + knox = require('knox'); + + s3 = null; + + Journo.S3.connect = function() { + loadConfig(); + return s3 || (s3 = knox.createClient(config.s3)); + }; Journo.S3.publishPost = function(post) { - var client; - return client = s3Client(); + var client, request; + client = Journo.S3.connect(); + throw 'content TK'; + request = client.put(path.join(config.s3.path, post), { + 'Content-Length': content.length, + 'Content-Type': 'text/html', + 'x-amz-acl': 'public-read' + }); + request.on('response', function(response) { + if (response.statusCode === 200) { + return console.log("PUT " + post); + } + }); + return request.end(content); }; manifestPath = 'journo-manifest.json'; @@ -181,15 +243,48 @@ }; Journo.preview = function() { - var http; + var http, mime, server, url, util; http = require('http'); - return 'TK'; + mime = require('mime'); + url = require('url'); + util = require('util'); + server = http.createServer(function(req, res) { + var publicPath; + path = url.parse(req.url).pathname.replace(/^\//, ''); + publicPath = "public/" + path; + return fs.exists(publicPath, function(exists) { + var post; + if (exists) { + res.writeHead(200, { + 'Content-Type': mime.lookup(publicPath) + }); + return fs.createReadStream(publicPath).pipe(res); + } else { + post = "posts/" + path + ".md"; + return fs.exists(post, function(exists) { + if (exists) { + return fs.readFile(post, function(err, content) { + res.writeHead(200, { + 'Content-Type': 'text/html' + }); + return res.end(Journo.render(content)); + }); + } else { + res.writeHead(404); + return res.end('404 Not Found'); + } + }); + } + }); + }); + server.listen(1234); + return console.log("Journo is previewing at http://localhost:1234"); }; Journo.run = function() { var args, command; args = process.argv.slice(2); - command = args[0]; + command = args[0] || 'preview'; if (Journo[command]) { return Journo[command](); } else { diff --git a/journo.litcoffee b/journo.litcoffee index 2b3a737..9d7fb12 100644 --- a/journo.litcoffee +++ b/journo.litcoffee @@ -38,9 +38,15 @@ Underscore for many of its goodies later on. marked = require 'marked' _ = require 'underscore' + skeleton = null - Journo.markdown = (source) -> - marked.parser marked.lexer source + Journo.render = (source) -> + skeleton or= _.template(fs.readFileSync('skeleton.html').toString()) + markdown = source = _.template(source.toString())({}) + lexed = marked.lexer markdown + title = _.find lexed, (token) -> token.type is 'heading' + content = marked.parser lexed + skeleton {title, content} Publish to Flat Files @@ -56,24 +62,37 @@ folder for blog posts, and a `journo.json` file for configuration. Journo.publish = -> FTPClient = require 'ftp' ftp = new FTPClient - loadConfig() - loadManifest() - posts = fs.readdirSync 'posts' - todo = compareManifest posts + Journo.build() + todo = compareManifest allPosts() ftp.connect config.ftp + Journo.publishPage Journo.publishPost post for post in todo.puts Journo.unpublishPost post for post in todo.deletes ftp.end() writeManifest() yes +In order to `build` the blog, we render all of the posts out as HTML on disk. + + Journo.build = -> + loadConfig() + loadManifest() + fs.mkdirSync('site') unless fs.existsSync('site') + for post in allPosts() + markdown = fs.readFileSync postPath post + html = Journo.render markdown + fs.writeFileSync "site/#{path.basename(post, '.md')}.html", html + The `config.json` configuration file is where you keep the nitty gritty details, like how to connect to your FTP server. The settings are: `host`, `port`, `secure`, `user`, and `password`. loadConfig = -> return if config - config = JSON.parse fs.readFileSync 'config.json' + try + config = JSON.parse fs.readFileSync 'config.json' + catch err + fatal "Unable to read config.json" siteUrl = config.url.replace(/\/$/, '') For convenience, keep functions handy for finding the local file path to a post, @@ -83,6 +102,14 @@ and the URL for a post on the server. postUrl = (post) -> "#{siteUrl}/#{post}" + allPosts = -> fs.readdirSync 'posts' + +Quick helper for any fatal errors that might be encountered during a build. + + fatal = (message) -> + console.error message + process.exit 1 + Publish Via FTP --------------- @@ -117,6 +144,18 @@ To publish a post, we render it and `PUT` it to S3. Journo.S3.publishPost = (post) -> client = Journo.S3.connect() + throw 'content TK' + + request = client.put path.join(config.s3.path, post), + 'Content-Length': content.length + 'Content-Type': 'text/html' + 'x-amz-acl': 'public-read' + + request.on 'response', (response) -> + if response.statusCode is 200 + console.log "PUT #{post}" + + request.end content Maintain a Manifest File @@ -230,7 +269,29 @@ Preview via a Local Server Journo.preview = -> http = require 'http' - 'TK' + mime = require 'mime' + url = require 'url' + util = require 'util' + server = http.createServer (req, res) -> + path = url.parse(req.url).pathname.replace /^\//, '' + publicPath = "public/#{path}" + fs.exists publicPath, (exists) -> + if exists + res.writeHead 200, 'Content-Type': mime.lookup(publicPath) + fs.createReadStream(publicPath).pipe res + else + post = "posts/#{path}.md" + fs.exists post, (exists) -> + if exists + fs.readFile post, (err, content) -> + res.writeHead 200, 'Content-Type': 'text/html' + res.end Journo.render content + else + res.writeHead 404 + res.end '404 Not Found' + + server.listen 1234 + console.log "Journo is previewing at http://localhost:1234" Work Without JavaScript, But Default to a Fluid JavaScript-Enabled UI @@ -242,7 +303,7 @@ Finally, Putting it all Together. Run Journo From the Terminal Journo.run = -> args = process.argv.slice 2 - command = args[0] + command = args[0] or 'preview' if Journo[command] do Journo[command] else diff --git a/package.json b/package.json index 5541648..54a4afa 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "marked": ">= 0.2.6", "ftp": ">= 0.2.8", "knox": ">= 0.4.6", + "mime": ">= 1.2.7", "highlight": ">= 0.2.3", "rss": ">= 0.0.4", "ncp": ">= 0.2.6"