initial commit

This commit is contained in:
AJ ONeal 2015-01-05 19:23:26 +00:00
parent 3f3b892240
commit f31f6ea1a4
13 changed files with 720 additions and 0 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
node_modules
bower_components
# Logs
logs
*.log

40
bower.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "deardesi",
"version": "1.0.0",
"authors": [
"AJ ONeal <awesome@coolaj86.com>"
],
"description": "A blogging platform in the browser. Wow!",
"main": "deardesi.js",
"moduleType": [
"globals",
"node"
],
"keywords": [
"dear",
"desi",
"deardesi",
"blog",
"blogging",
"platform",
"browser"
],
"license": "Apache2",
"homepage": "http://github.com/coolaj86/deardesi",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"mustache": "~0.8.2",
"bluebird": "~2.5.2",
"rsvp": "~3.0.16",
"escape-string-regexp": "~1.0.2",
"marked": "~0.3.2",
"js-yaml": "~3.2.5",
"path": "~3.46.1"
}
}

216
deardesi.js Normal file
View File

@ -0,0 +1,216 @@
;(function (exports) {
'use strict';
//require('require-yaml');
var PromiseA = exports.Promise || require('bluebird').Promise
, path = exports.path || require('path')
, Mustache = exports.Mustache || require('mustache')
, marked = exports.marked || require('marked')
, forEachAsync = exports.forEachAsync || require('foreachasync').forEachAsync
, sha1sum = exports.sha1sum || require('./lib/deardesi-node').sha1sum
, frontmatter = exports.frontmatter || require('./lib/frontmatter').Frontmatter
, safeResolve = exports.safeResolve || require('./lib/deardesi-utils').safeResolve
, getStats = exports.getStats || require('./lib/deardesi-node').getStats
, getContents = exports.getContents || require('./lib/deardesi-node').getContents
;
function getCollections(blogbase, ignorable, collectionnames) {
var collectiondir
, collectiondirs = []
, lost = []
, found = []
, errors = []
;
collectionnames.forEach(function (collectionname) {
collectiondir = safeResolve(_dirname, collectionname);
if (!collectiondir) {
return PromiseA.reject(new Error("Please update your config.yml: " + collectionname + " is outside of '" + _dirname + "'"));
}
collectiondirs.push({ name: collectionname, path: collectiondir });
});
return getFolders(collectiondirs, { recursive: true, limit: 5, stats: true }).then(function (stats) {
collectiondirs.forEach(function (collection) {
if (!stats[collection.path]) {
errors.push({
collection: collection
, message: "server did not return success or error for " + collection.path + ':\n' + JSON.stringify(stats)
});
}
else if (!stats[collection.path].type) {
lost.push(collection);
}
else if ('directory' !== stats[collection.path].type) {
errors.push({
collection: collection
, message: collection.path + " is not a directory (might be a symbolic link)"
});
} else {
found.push(collection);
}
});
return {
lost: lost
, found: found
, errors: errors
};
});
}
function showCollectionNotes(notes) {
if (notes.lost.length) {
console.warn("WARNING: these collections you specified couldn't be found");
notes.lost.forEach(function (node) {
console.warn('? ' + node.name);
});
console.log('');
}
if (notes.found.length) {
console.log("Will compile these collections");
notes.found.forEach(function (node) {
console.log('+ ' + node.name);
});
console.log('');
}
}
function getLayouts() {
// TODO
}
function updatePage(pagedir, node, lstat, data) {
var parts = frontmatter.parse(data)
, meta
, html
, view
;
if (!parts.yml) {
console.error("Could not parse frontmatter for " + node);
console.error(parts.frontmatter);
return;
}
if (/\.(html|htm)$/.test(node)) {
html = parts.body.trim();
} else if (/\.(md|markdown|mdown|mkdn|mkd|mdwn|mdtxt|mdtext)$/.test(node)) {
console.log('parsing markdown...');
html = marked(parts.body.trim());
} else {
console.error('unknown parser for ' + node);
}
meta = {
mtime: lstat.mtime
, ymlsum: sha1sum(parts.frontmatter.trim())
, textsum: sha1sum(parts.body.trim())
, htmlsum: sha1sum(html)
, filesum: sha1sum(data)
, filename: node
, filepath: pagedir
};
/*
// TODO
db.getCached(meta).error(function () {
// TODO rebuild and save
});
*/
// TODO meta.layout
view = {
page: parts.yml
, content: html
};
console.log(node);
console.log(parts.frontmatter);
console.log(parts.yml); //Mustache.render(pagetpl, view));
//console.log(meta.mtime.valueOf(), meta.ymlsum, meta.textsum, node);
return meta;
}
function templatePosts() {
var pagetpl
, defaulttpl
;
// TODO declare path to theme
pagetpl = frontmatter.parse(fs.readFileSync(path.join(config.theme, 'layouts', 'page.html'), 'utf8'));
defaulttpl = frontmatter.parse(fs.readFileSync(path.join(config.theme, 'layouts', 'default.html'), 'utf8'));
}
function getCollection() {
}
console.log('');
console.log('');
console.log('loading caches...');
getMetaCache().then(function (db) {
console.log('last update: ' + (db.lastUpdate && new Date(db.lastUpdate) || 'never'));
console.log('checking for local updates...');
// TODO get layouts here
return getCollections('.', Object.keys(config.collections)).then(function (notes) {
showCollectionNotes(notes);
return notes.found;
}).then(function (found) {
var metas = []
;
return forEachAsync(found, function (collection) {
begintime = Date.now();
console.log('begin', ((begintime - starttime) / 1000).toFixed(4));
return fs.readdirAsync(collection.path).then(function (nodes) {
// TODO look for companion yml file aside html|md|jade
nodes = nodes.filter(function (node) {
// TODO have handlers accept or reject extensions in the order they are registered
if (!/\.(htm|html|md|markdown|mdown|mkdn|mkd|jade)$/.test(node)) {
console.warn("ignoring " + collection.name + '/' + node + " (unknown filetype processor)");
return false;
}
return true;
});
return forEachAsync(nodes, function (pagename) {
var pagepath = path.join(collection.path, pagename)
;
// TODO: support walking deep
// TODO: test .html, .md, etc
return fs.lstatAsync(pagepath).then(function (lstat) {
// no funny business allowed
if (!lstat.isFile()) {
return;
}
return fs.readFileAsync(nodepath, 'utf8').then(function (data) {
updatePage(pagedir, node, lstat, data);
});
});
});
}).then(function () {
console.log('doneish', ((Date.now() - begintime) / 1000).toFixed(4));
});
});
});
});
}('undefined' !== typeof exports && exports || window));

19
index.html Normal file
View File

@ -0,0 +1,19 @@
<html>
<head>
<title>Dear Desi</title>
<script src="./bower_components/bluebird/js/browser/bluebird.js"></script>
<script src="./bower_components/mustache/mustache.js"></script>
<script src="./bower_components/marked/lib/marked.js"></script>
<script src="./bower_components/js-yaml/dist/js-yaml.js"></script>
<script src="./bower_components/path/path.js"></script>
<script src="./lib/deardesi-utils.js"></script>
<script src="./lib/verify-config.js"></script>
<script src="./lib/deardesi-browser.js"></script>
<script src="./lib/frontmatter.js"></script>
<script src="./deardesi.js"></script>
</head>
<body>
<p>Open up the console, fool!</p>
</body>
</html>

View File

@ -0,0 +1,7 @@
'use strict';
module.exports.convert = function () {
console.error("I haven't implemented a ruhoh -> nuhoh converter yet, but it's not very hard to do.");
console.error("see https://github.com/coolaj86/nuhoh/tree/master/MIGRATE.md");
throw new Error('Not Implemented.');
};

128
lib/deardesi-browser.js Normal file
View File

@ -0,0 +1,128 @@
/*jshint -W054 */
var tmpglobal
;
try {
tmpglobal = new Function('return this')();
} catch(e) {
tmpglobal = window;
}
;(function (exports) {
'use strict';
// Chrome, Firefox, and even MSIE11+ all support crypto
var crypto = window.crypto || window.msCrypto
, algos
;
// convenience mappings for common digest algorithms
algos = {
'sha1': 'SHA-1'
, 'sha256': 'SHA-256'
, 'sha512': 'SHA-512'
};
// The function to generate a sha1sum is the same as generating any digest
// but here's a shortcut function anyway
function sha1sum(str) {
return hashsum('sha1', str);
}
// a more general convenience function
function hashsum(hash, str) {
// you have to convert from string to array buffer
var ab
// you have to represent the algorithm as an object
, algo = { name: algos[hash] }
;
if ('string' === typeof str) {
ab = str2ab(str);
} else {
ab = str;
}
// All crypto digest methods return a promise
return crypto.subtle.digest(algo, ab).then(function (digest) {
// you have to convert the ArrayBuffer to a DataView and then to a hex String
return ab2hex(digest);
}).catch(function (e) {
// if you specify an unsupported digest algorithm or non-ArrayBuffer, you'll get an error
console.error('sha1sum ERROR');
console.error(e);
throw e;
});
}
// convert from arraybuffer to hex
function ab2hex(ab) {
var dv = new DataView(ab)
, i
, len
, hex = ''
, c
;
for (i = 0, len = dv.byteLength; i < len; i += 1) {
c = dv.getUint8(i).toString(16);
if (c.length < 2) {
c = '0' + c;
}
hex += c;
}
return hex;
}
// convert from string to arraybuffer
function str2ab(stringToEncode, insertBom) {
stringToEncode = stringToEncode.replace(/\r\n/g,"\n");
var utftext = []
, n
, c
;
if (true === insertBom) {
utftext[0] = 0xef;
utftext[1] = 0xbb;
utftext[2] = 0xbf;
}
for (n = 0; n < stringToEncode.length; n += 1) {
c = stringToEncode.charCodeAt(n);
if (c < 128) {
utftext[utftext.length]= c;
}
else if((c > 127) && (c < 2048)) {
utftext[utftext.length] = (c >> 6) | 192;
utftext[utftext.length] = (c & 63) | 128;
}
else {
utftext[utftext.length] = (c >> 12) | 224;
utftext[utftext.length] = ((c >> 6) & 63) | 128;
utftext[utftext.length] = (c & 63) | 128;
}
}
return new Uint8Array(utftext).buffer;
}
exports.hashsum = hashsum;
exports.sha1sum = sha1sum;
}('undefined' !== typeof exports && exports || tmpglobal));
;(function () {
'use strict';
exports.getStats
exports.getContents
exports.getMetaCache
exports.getContentCache
//require('./db').create(path.join(_dirname, 'db.json'))
}());

9
lib/deardesi-node.js Normal file
View File

@ -0,0 +1,9 @@
'use strict';
var PromiseA = require('bluebird').Promise
, secretutils = require('secret-utils')
;
module.exports.sha1sum = function (str) {
return PromiseA.resolve( secretutils.hashsum('sha1', str) );
};

23
lib/deardesi-utils.js Normal file
View File

@ -0,0 +1,23 @@
;(function (exports) {
'use strict';
var path = exports.path || require('path')
;
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function safeResolve(basename, target) {
basename = path.resolve(basename);
var targetname = path.resolve(basename, target)
, re = new RegExp('^' + escapeRegExp(basename) + '(/|$)')
;
return re.test(targetname) && targetname;
}
exports.safeResolve = safeResolve;
exports.escapeRegExp = escapeRegExp;
}('undefined' !== typeof exports && exports || window));

103
lib/frontmatter.js Normal file
View File

@ -0,0 +1,103 @@
/*jshint -W054 */
;(function (exports) {
'use strict';
var YAML = {}
;
YAML.parse = exports.jsyaml.load || require('jsyaml').load;
//YAML.parse = require('yaml').eval;
//YAML.parse2 = require('yamljs').parse;
function readFrontMatter(text) {
var lines
, line
, padIndent = ''
, ymllines = []
;
lines = text.split(/\n/);
line = lines.shift();
if (!line.match(/^---\s*$/)) {
return;
}
// our yaml parser can't handle objects
// that start without indentation, so
// we can add it if this is the case
if (lines[0] && lines[0].match(/^\S/)) {
padIndent = '';
}
while (true) {
line = lines.shift();
// premature end-of-file (unsupported yaml)
if (!line && '' !== line) {
ymllines = [];
break;
}
// end-of-yaml front-matter
if (line.match(/^---\s*$/)) {
break;
}
if (line) {
// supported yaml
ymllines.push(padIndent + line);
}
}
// XXX can't be sorted because arrays get messed up
//ymllines.sort();
if (ymllines) {
return '---\n' + ymllines.join('\n');
}
return;
}
function separateText(text, fm) {
var len
, yml
;
yml = readFrontMatter(fm);
// strip frontmatter from text, if any
// including trailing '---' (which is accounted for by the added '\n')
if (yml) {
len = fm.split(/\n/).length;
} else {
len = 0;
}
return text.split(/\n/).slice(len).join('\n');
}
function parseText(text) {
var fm = readFrontMatter(text)
, body = separateText(text, fm)
, yml
;
try {
yml = YAML.parse(fm);
} catch(e) {
//
}
return {
yml: yml
, frontmatter: fm
, body: body
};
}
exports.Frontmatter.Frontmatter = exports.Frontmatter = {};
exports.Frontmatter.readText = readFrontMatter;
exports.Frontmatter.separateText = separateText;
exports.Frontmatter.parse = parseText;
}('undefined' !== typeof exports && exports || window));

56
lib/verify-config.js Normal file
View File

@ -0,0 +1,56 @@
'use strict';
module.export.verify = function (conf) {
if (!conf.NuhohSpec) {
throw new Error("missing key NuhohSpec");
}
if (!conf.production) {
throw new Error("missing key production");
}
if (!conf.production.canonical_url) {
throw new Error("missing key production.canonical_url");
}
if (!conf.production.base_path) {
throw new Error("missing key production.base_path");
}
if (!conf.development) {
throw new Error("missing key development");
}
if (!conf.development.compiled_path) {
throw new Error("missing key development.compiled_path");
}
if (!Array.isArray(conf.collections)) {
if (conf.posts) {
console.error("Please indent and nest 'posts' under the key 'collection' to continue");
}
throw new Error("missing key 'collections'.");
}
if (!conf.themes) {
if (conf.twitter) {
console.error("Please indent and nest 'twitter' under the key 'themes' to continue");
}
throw new Error("missing key 'themes'");
}
if (!conf.themes.default) {
if (conf.twitter) {
console.error("Please set themes.default to 'twitter'");
}
throw new Error("missing key 'themes.default'");
}
if (!conf.root) {
throw new Error("missing key root");
}
if (!conf.widgets) {
throw new Error("missing key root");
}
};

28
lib/walk.js Normal file
View File

@ -0,0 +1,28 @@
'use strict';
var PromiseA = require('bluebird').Promise
, path = require('path')
, walk = require('walk')
, walker
;
function getFs(parent, sub) {
// TODO safe
var trueRoot = path.resolve(parent, sub)
;
return new PromiseA(function (resolve) {
walker = walk.walk('posts');
walker.on('directories', function (root, stat, next) {
console.log(root, stat);
next();
});
walker.on('files', function (root, stat, next) {
//console.log(root, stat);
next();
});
walker.on('end', function () {
console.log('done');
});
});
}

47
package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "deardesi",
"version": "0.1.0",
"description": "An in-browser knockoff of the Ruhoh static blog generator. (similar to Jekyll, Octopress, Nanoc, etc)",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@github.com:coolaj86/deardesi.git"
},
"keywords": [
"dear",
"deardesi",
"desi",
"ruhoh",
"ruhoh",
"blog",
"static",
"jekyll",
"octopress",
"nanoc"
],
"author": "AJ ONeal",
"license": "Apache2",
"bugs": {
"url": "https://github.com/coolaj86/deardesi/issues"
},
"homepage": "https://github.com/coolaj86/deardesi",
"dependencies": {
"bluebird": "^2.5.3",
"circular-json": "^0.1.6",
"escape-string-regexp": "^1.0.2",
"foreachasync": "^5.0.2",
"json2yaml": "^1.0.3",
"markdown": "^0.5.0",
"marked": "^0.3.2",
"mustache": "^1.0.0",
"require-yaml": "0.0.1",
"require-yamljs": "^1.0.1",
"secret-utils": "^1.0.2",
"walk": "^2.3.5",
"yaml": "^0.2.3",
"yamljs": "^0.2.1"
}
}

41
server.js Normal file
View File

@ -0,0 +1,41 @@
'use strict';
var connect = require('connect')
, query = require('connect-query')
, bodyParser = require('body-parser')
, serveStatic = require('serve-static')
, app = connect()
;
app
.use('/api/fs', query())
.use('/api/fs', bodyParser.json())
.use('/api/fs', function (req, res, next) {
if (!(/^GET$/i.test(req.method) || /^GET$/i.test(req.query._method))) {
next();
return;
}
/*
return forEachAsync(collectiondirs, function (collection) {
return fs.lstatAsync(collection.path).then(function (stat) {
if (!stat.isDirectory()) {
//return;
}
}).error(function () {
});
}).then(function () {
});
*/
res.end('not implemented');
})
.use('/api/fs', function (req, res, next) {
next();
return;
})
.use(serveStatic('.'))
;
module.exports = app;