Permalink
Browse files

Initial Commit

  • Loading branch information...
0 parents commit 6a713627684a6c9e57566d964b2253712ab2a172 @ben-ng committed Jul 29, 2013
Showing with 317 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +4 −0 Jakefile
  3. +66 −0 Readme.md
  4. +120 −0 lib/index.js
  5. +35 −0 package.json
  6. +11 −0 test/fixtures/bad/bad.md
  7. +11 −0 test/fixtures/good/good.md
  8. +68 −0 test/import.js
@@ -0,0 +1,2 @@
+node_modules
+.DS_Store
@@ -0,0 +1,4 @@
+var t = new jake.TestTask('kitten', function () {
+ this.testFiles.include('test/*.js');
+ this.testFiles.exclude('test/fixtures/*');
+});
@@ -0,0 +1,66 @@
+# Kitten
+
+#### Export Octopress blog posts as JSON
+
+## Usage
+
+```
+var kitten = require('kitten');
+
+kitten.load('folder/path', cb);
+
+/*
+ * {
+ * 'post.md': {
+ * title: 'My Blog Post'
+ * , layout: 'post'
+ * , date: 2112-04-09 23:26
+ * , comments: true
+ * , categories: [
+ * 'Music'
+ * , 'Turtles'
+ * ]
+ * }
+ *
+ */
+
+kitten.load('path/to/post.md', cb);
+
+/*
+ * {
+ * title: 'My Blog Post'
+ * , layout: 'post'
+ * , date: 2112-04-09 23:26
+ * , comments: true
+ * , categories: [
+ * 'Music'
+ * , 'Turtles'
+ * ]
+ * }
+ */
+
+```
+
+## License
+
+The MIT License (MIT)
+
+Copyright (c) 2013 Ben Ng
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
@@ -0,0 +1,120 @@
+(function () {
+
+var _ = require('lodash')
+ , fs = require('fs')
+ , path = require('path')
+ , yaml = require('js-yaml')
+ , async = require('async')
+ , kitten;
+
+kitten = {
+ load: function (path, cb) {
+ fs.stat(path, function (err, stat) {
+ if(err) {
+ return cb(err, null);
+ }
+
+ if(stat.isFile()) {
+ kitten._loadFile(path, cb);
+ }
+ else {
+ kitten._loadDirectory(path, cb);
+ }
+ });
+ }
+, _loadFile: function (file, cb) {
+ fs.readFile(file, function (err, data) {
+ var requiredFields = [
+ 'layout'
+ , 'title'
+ , 'date'
+ ]
+ , resp
+ , header
+ , content
+ , basename = path.basename(file)
+ , errorLoadingText = 'Error loading ' + basename + ': ';
+
+ if(err) {
+ return cb(errorLoadingText + err, null);
+ }
+
+ // Convert buffer to string
+ data = data.toString();
+
+ // Find the YAML header
+ header = data.match(/^---(.|\s)+?(?=---)/);
+
+ if(!header) {
+ return cb(errorLoadingText + 'No YAML header found', null);
+ }
+
+ // Find the post content
+ content = data.substring(data.lastIndexOf('---') + 3).trim();
+
+ // Remove '---'; JS has no negative lookbehind ):
+ header = header[0].substring(3).trim();
+
+ // Parse the header
+ try {
+ header = yaml.safeLoad(header);
+ }
+ catch (e) {
+ return cb(errorLoadingText + 'The YAML header was malformed (' + e + ')', null);
+ }
+
+ // Check for required fields
+ for(var i=0, ii=requiredFields.length; i<ii; i++) {
+ if(header[requiredFields[i]] == null) {
+ return cb(errorLoadingText + 'The YAML header is missing the required ' + requiredFields[i] + ' field');
+ }
+ }
+
+ // Parse date into a javascript Date object
+ header.date = new Date(header.date);
+
+ cb(null, _.extend({}, header, {content: content}));
+ });
+ }
+, _loadDirectory: function (directory, cb) {
+ var self = this
+ , basename = path.basename(directory)
+ , errorLoadingText = 'Error loading ' + basename + ': '
+ , loaders = {};
+
+ fs.readdir(directory, function (err, files) {
+ if(err) {
+ return cb(errorLoadingText + err, null);
+ }
+
+ _.each(files, function (filePath) {
+ var fullPath = path.join(directory, filePath)
+ , baseName = path.basename(fullPath);
+
+ loaders[baseName] = function (next) {
+ self._loadFile(fullPath, function (err, data) {
+ if(err) {
+ next(err);
+ }
+ else {
+ next(null, data);
+ }
+ });
+ };
+ });
+
+ async.series(loaders, function (err, data) {
+ if(err) {
+ cb(err, null);
+ }
+ else {
+ cb(null, data);
+ }
+ });
+ });
+ }
+};
+
+module.exports = kitten;
+
+}());
@@ -0,0 +1,35 @@
+{
+ "author": "Ben Ng <me@benng.me> (http://benng.me)",
+ "name": "kitten",
+ "description": "Export Octopress blog posts as JSON",
+ "keywords": [
+ "octopress",
+ "export",
+ "jekyll",
+ "scotch-blog",
+ "scotch",
+ "blog"
+ ],
+ "version": "0.0.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/ben-ng/kitten.git"
+ },
+ "main": "./lib/index.js",
+ "dependencies": {
+ "lodash": "1.3.x"
+ , "js-yaml": "2.1.x"
+ , "async": "0.2.x"
+ },
+ "scripts": {
+ "test": "jake test"
+ },
+ "devDependencies": {
+ "jake": "0.6.x"
+ , "assert-diff": "0.0.x"
+ },
+ "optionalDependencies": {},
+ "engines": {
+ "node": ">=0.8.x"
+ }
+}
@@ -0,0 +1,11 @@
+---
+layout: post
+title "This Is A Bad Header!"
+date: 2112-05-19 21:12
+comments: false
+categories: [Depressing, Fleeting]
+---
+
+# Lost Souls
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.
@@ -0,0 +1,11 @@
+---
+layout: post
+title: "Lost Souls"
+date: 2112-05-19 21:12
+comments: false
+categories: [Depressing, Fleeting]
+---
+
+# Lost Souls
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.
@@ -0,0 +1,68 @@
+(function () {
+
+var kitten = require('../lib')
+ , assert = require('assert-diff')
+ , path = require('path')
+ , tests
+ , fixtureDir = path.join(__dirname, 'fixtures')
+ , goodDir = path.join(fixtureDir, 'good')
+ , goodPostPath = path.join(goodDir, 'good.md')
+ , badDir = path.join(fixtureDir, 'bad')
+ , badPostPath = path.join(badDir, 'bad.md')
+ , goodPostExpected = {
+ layout: 'post'
+ , title: 'Lost Souls'
+ , date: new Date('2112-05-19 21:12')
+ , comments: false
+ , categories: ['Depressing', 'Fleeting']
+ , content: '# Lost Souls\n\
+\n\
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.'
+ }
+ , badPostExpectedError = new RegExp('Error loading ' + path.basename(badPostPath) + ': The YAML header was malformed.*')
+ , badPostExpected = {error: badPostExpectedError};
+
+tests = {
+ 'import good file': function (next) {
+ kitten.load(goodPostPath, function (err, post) {
+
+ assert.equal(err, null, err);
+ assert.deepEqual(post, goodPostExpected, 'The post should match our expected object');
+
+ next();
+ });
+ }
+, 'import bad file': function (next) {
+ kitten.load(badPostPath, function (err, post) {
+ assert.notEqual(err.match(badPostExpectedError) , null, 'There should be a malformed header error');
+ assert.equal(post, null, 'Post should be null');
+
+ next();
+ });
+ }
+, 'import bad folder': function (next) {
+ kitten.load(badDir, function (err, posts) {
+ assert.notEqual(err.match(badPostExpectedError) , null, 'There should be a malformed header error');
+ assert.equal(posts, null, 'Posts should be null');
+
+ next();
+ });
+ }
+, 'import good folder': function (next) {
+ var baseName = path.basename(goodPostPath)
+ , expected = {};
+
+ expected[baseName] = goodPostExpected;
+
+ kitten.load(goodDir, function (err, posts) {
+ assert.equal(err, null, err);
+ assert.deepEqual(posts
+ , expected, 'The result should match our expected object');
+ next();
+ });
+ }
+};
+
+module.exports = tests;
+
+}());

0 comments on commit 6a71362

Please sign in to comment.