From 1e77f70eba243ffac9fbd6401ac5d63e0639e586 Mon Sep 17 00:00:00 2001 From: Gilbert Pellegrom Date: Wed, 4 Jun 2014 16:19:52 +0100 Subject: [PATCH] v0.1.0 --- Makefile | 4 + package.json | 48 ++++++++ raneto.js | 212 +++++++++++++++++++++++++++++++++++ test/content/example-page.md | 6 + test/content/sub/page.md | 5 + test/raneto.js | 143 +++++++++++++++++++++++ 6 files changed, 418 insertions(+) create mode 100644 Makefile create mode 100644 package.json create mode 100644 raneto.js create mode 100644 test/content/example-page.md create mode 100644 test/content/sub/page.md create mode 100644 test/raneto.js diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a96b06a --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +test: + ./node_modules/.bin/mocha --reporter spec + +.PHONY: test diff --git a/package.json b/package.json new file mode 100644 index 0000000..6d133af --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "name": "raneto-core", + "version": "0.1.0", + "description": "Core module for Raneto", + "author": "Gilbert Pellegrom ", + "license": "MIT", + "keywords": [ + "raneto", + "markdown", + "static", + "site", + "generator" + ], + "main": "raneto.js", + "scripts": { + "test": "make test" + }, + "repository": { + "type": "git", + "url": "https://github.com/gilbitron/Raneto-Core.git" + }, + "bugs": { + "url": "https://github.com/gilbitron/Raneto-Core/issues" + }, + "homepage": "https://github.com/gilbitron/Raneto-Core", + "licenses": [ + { + "type": "MIT", + "url": "https://raw.githubusercontent.com/gilbitron/Raneto-Core/master/LICENSE" + } + ], + "engines": { + "node": "~0.10.0" + }, + "dependencies": { + "glob": "^4.0.0", + "lunr": "^0.5.3", + "marked": "^0.3.2", + "moment": "^2.6.0", + "underscore": "^1.6.0", + "underscore.string": "^2.3.3", + "validator": "^3.13.0" + }, + "devDependencies": { + "chai": "^1.9.1", + "mocha": "^1.20.1" + } +} diff --git a/raneto.js b/raneto.js new file mode 100644 index 0000000..409204b --- /dev/null +++ b/raneto.js @@ -0,0 +1,212 @@ +var path = require('path'), + fs = require('fs'), + glob = require('glob'), + _ = require('underscore'), + _s = require('underscore.string'), + moment = require('moment'), + marked = require('marked'), + lunr = require('lunr'), + validator = require('validator'); + +var raneto = { + + // Default content directory where Markdown files are stored + contentDir: './content/', + + // Toggle debug logging + debug: false, + + // Regex for page meta + _metaRegex: /^\/\*([\s\S]*?)\*\//i, + + // Makes filename safe strings + cleanString: function(str, use_underscore) { + var u = use_underscore || false; + str = str.replace(/\//g, ' ').trim(); + if(u){ + return _s.underscored(str); + } else { + return _s.trim(_s.dasherize(str), '-'); + } + }, + + // Convert a slug to a title + slugToTitle: function(slug) { + slug = slug.replace('.md', '').trim(); + return _s.titleize(_s.humanize(path.basename(slug))); + }, + + // Get meta information from Markdown content + processMeta: function(markdownContent) { + var metaArr = markdownContent.match(raneto._metaRegex), + meta = {}; + + var metaString = metaArr ? metaArr[1].trim() : ''; + if(metaString){ + var metas = metaString.match(/(.*): (.*)/ig); + metas.forEach(function(item){ + var parts = item.split(': '); + if(parts[0] && parts[1]){ + meta[raneto.cleanString(parts[0], true)] = parts[1].trim(); + } + }); + } + + return meta; + }, + + // Strip meta from Markdown content + stripMeta: function(markdownContent) { + return markdownContent.replace(raneto._metaRegex, '').trim(); + }, + + // Replace content variables in Markdown content + processVars: function(markdownContent, config) { + config = config || {}; + if(typeof config.base_url !== 'undefined') markdownContent = markdownContent.replace(/\%base_url\%/g, config.base_url); + if (typeof config.image_url !== 'undefined') markdownContent = markdownContent.replace(/\%image_url\%/g, config.image_url); + return markdownContent; + }, + + // Get a page + getPage: function(filePath, config) { + config = config || {}; + try { + var file = fs.readFileSync(filePath), + slug = filePath.replace(raneto.contentDir, '').trim(); + + if(slug.indexOf('index.md') > -1){ + slug = slug.replace('index.md', ''); + } + slug = slug.replace('.md', '').trim(); + + var meta = raneto.processMeta(file.toString('utf-8')), + content = raneto.stripMeta(file.toString('utf-8')); + content = raneto.processVars(content, config); + var html = marked(content); + + return { + 'slug': slug, + 'title': meta.title ? meta.title : raneto.slugToTitle(slug), + 'body': html, + 'excerpt': _s.prune(_s.stripTags(_s.unescapeHTML(html)), config.excerpt_length) + }; + } + catch(e){ + if(raneto.debug) console.log(e); + return null; + } + }, + + // Get a structured array of the contents of contentDir + getPages: function(activePageSlug, config) { + activePageSlug = activePageSlug || ''; + config = config || {}; + var page_sort_meta = config.page_sort_meta || '', + category_sort = config.category_sort || false, + files = glob.sync(raneto.contentDir +'**/*'), + filesProcessed = []; + + filesProcessed.push({ + slug: '.', + title: '', + is_index: true, + class: 'category-index', + sort: 0, + files: [] + }); + + files.forEach(function(filePath){ + var shortPath = filePath.replace(raneto.contentDir, '').trim(), + stat = fs.lstatSync(filePath); + + if(stat.isDirectory()){ + var sort = 0; + if(category_sort){ + try { + var sortFile = fs.readFileSync(raneto.contentDir + shortPath +'/sort'); + sort = parseInt(sortFile.toString('utf-8'), 10); + } + catch(e){ + if(raneto.debug) console.log(e); + } + } + + filesProcessed.push({ + slug: shortPath, + title: _s.titleize(_s.humanize(path.basename(shortPath))), + is_index: false, + class: 'category-'+ raneto.cleanString(shortPath), + sort: sort, + files: [] + }); + } + if(stat.isFile() && path.extname(shortPath) == '.md'){ + try { + var file = fs.readFileSync(filePath), + slug = shortPath, + pageSort = 0; + + if(shortPath.indexOf('index.md') > -1){ + slug = slug.replace('index.md', ''); + } + slug = slug.replace('.md', '').trim(); + + var dir = path.dirname(shortPath), + meta = raneto.processMeta(file.toString('utf-8')); + + if(page_sort_meta && meta[page_sort_meta]) pageSort = parseInt(meta[page_sort_meta], 10); + + var val = _.find(filesProcessed, function(item){ return item.slug == dir; }); + val.files.push({ + slug: slug, + title: meta.title ? meta.title : raneto.slugToTitle(slug), + active: (activePageSlug.trim() == '/'+ slug), + sort: pageSort + }); + } + catch(e){ + if(raneto.debug) console.log(e); + } + } + }); + + filesProcessed = _.sortBy(filesProcessed, function(cat){ return cat.sort; }); + filesProcessed.forEach(function(category){ + category.files = _.sortBy(category.files, function(file){ return file.sort; }); + }); + + return filesProcessed; + }, + + // Index and search contents + doSearch: function(query) { + var files = glob.sync(raneto.contentDir +'**/*.md'); + var idx = lunr(function(){ + this.field('title', { boost: 10 }); + this.field('body'); + }); + + files.forEach(function(filePath){ + try { + var shortPath = filePath.replace(raneto.contentDir, '').trim(), + file = fs.readFileSync(filePath); + + var meta = raneto.processMeta(file.toString('utf-8')); + idx.add({ + 'id': shortPath, + 'title': meta.title ? meta.title : raneto.slugToTitle(shortPath), + 'body': file.toString('utf-8') + }); + } + catch(e){ + if(raneto.debug) console.log(e); + } + }); + + return idx.search(query); + } + +}; + +module.exports = raneto; diff --git a/test/content/example-page.md b/test/content/example-page.md new file mode 100644 index 0000000..00cd9d9 --- /dev/null +++ b/test/content/example-page.md @@ -0,0 +1,6 @@ +/* +Title: Example Page +Sort: 2 +*/ + +This is some example content. diff --git a/test/content/sub/page.md b/test/content/sub/page.md new file mode 100644 index 0000000..7ffba46 --- /dev/null +++ b/test/content/sub/page.md @@ -0,0 +1,5 @@ +/* +Title: Example Sub Page +*/ + +This is some example content. diff --git a/test/raneto.js b/test/raneto.js new file mode 100644 index 0000000..5e4aeea --- /dev/null +++ b/test/raneto.js @@ -0,0 +1,143 @@ +var chai = require('chai'), + should = chai.should(), + expect = chai.expect, + raneto = require('../raneto'); + +chai.config.truncateThreshold = 0; + +describe('#cleanString()', function() { + it('converts "Hello World" into "hello-world"', function() { + raneto.cleanString('Hello World').should.equal('hello-world'); + }); + + it('converts "/some/direcoty-example/hello/" into "some-direcoty-example-hello"', function() { + raneto.cleanString('/some/direcoty-example/hello/').should.equal('some-direcoty-example-hello'); + }); + + it('converts "with trailing space " into "with-trailing-space"', function() { + raneto.cleanString('with trailing space ').should.equal('with-trailing-space'); + }); + + it('converts "also does underscores" into "also_does_underscores"', function() { + raneto.cleanString('also does underscores', true).should.equal('also_does_underscores'); + }); + + it('converts "/some/direcoty-example/underscores/" into "some_direcoty_example_underscores"', function() { + raneto.cleanString('/some/direcoty-example/underscores/', true).should.equal('some_direcoty_example_underscores'); + }); +}); + +describe('#slugToTitle()', function() { + it('converts "hello-world" into "Hello World"', function() { + raneto.slugToTitle('hello-world').should.equal('Hello World'); + }); + + it('converts "dir/some-example-file.md" into "Some Example File"', function() { + raneto.slugToTitle('dir/some-example-file.md').should.equal('Some Example File'); + }); +}); + +describe('#processMeta()', function() { + it('returns array of meta values', function() { + var result = raneto.processMeta('/*\n'+ + 'Title: This is a title\n'+ + 'Description: This is a description\n'+ + 'Sort: 4\n'+ + 'Multi word: Value\n'+ + '*/\n'); + expect(result).to.have.property('title', 'This is a title'); + expect(result).to.have.property('description', 'This is a description'); + expect(result).to.have.property('sort', '4'); + expect(result).to.have.property('multi_word', 'Value'); + }); + it('returns an empty array if no meta specified', function() { + var result = raneto.processMeta('no meta here'); + expect(result).to.be.empty; + }); +}); + +describe('#stripMeta()', function() { + it('strips meta comment block', function() { + var result = raneto.stripMeta('/*\n'+ + 'Title: This is a title\n'+ + 'Description: This is a description\n'+ + 'Sort: 4\n'+ + 'Multi word: Value\n'+ + '*/\nThis is the content'); + result.should.equal('This is the content'); + }); + it('leaves content if no meta comment block', function() { + var result = raneto.stripMeta('This is the content'); + result.should.equal('This is the content'); + }); + it('only strips the first comment block', function() { + var result = raneto.stripMeta('/*\n'+ + 'Title: This is a title\n'+ + 'Description: This is a description\n'+ + 'Sort: 4\n'+ + 'Multi word: Value\n'+ + '*/\nThis is the content/*\n'+ + 'Title: This is a title\n'+ + '*/'); + result.should.equal('This is the content/*\n'+ + 'Title: This is a title\n'+ + '*/'); + }); +}); + +describe('#processVars()', function() { + it('replaces config vars in Markdown content', function() { + raneto.processVars( + 'This is some Markdown with a %base_url%.', + { + base_url: '/base/url' + } + ).should.equal('This is some Markdown with a /base/url.'); + }); +}); + +describe('#getPage()', function() { + it('returns an array of values for a given page', function() { + raneto.contentDir = __dirname +'/content/'; + var result = raneto.getPage(raneto.contentDir +'example-page.md'); + expect(result).to.have.property('slug', 'example-page'); + expect(result).to.have.property('title', 'Example Page'); + expect(result).to.have.property('body'); + expect(result).to.have.property('excerpt'); + }); + it('returns null if no page found', function() { + raneto.contentDir = __dirname +'/content/'; + var result = raneto.getPage(raneto.contentDir +'nonexistent-page.md'); + expect(result).to.be.null; + }); +}); + +describe('#getPages()', function() { + it('returns an array of categories and pages', function() { + raneto.contentDir = __dirname +'/content/'; + var result = raneto.getPages(); + expect(result[0]).to.have.property('is_index', true); + expect(result[0].files[0]).to.have.property('title', 'Example Page'); + expect(result[1]).to.have.property('slug', 'sub'); + expect(result[1].files[0]).to.have.property('title', 'Example Sub Page'); + }); + it('marks activePageSlug as active', function() { + raneto.contentDir = __dirname +'/content/'; + var result = raneto.getPages('/example-page'); + expect(result[0].files[0]).to.have.property('active', true); + expect(result[1].files[0]).to.have.property('active', false); + }); +}); + +describe('#doSearch()', function() { + it('returns an array of search results', function() { + raneto.contentDir = __dirname +'/content/'; + var result = raneto.doSearch('example'); + expect(result).to.have.length(2); + }); + it('returns an empty array if nothing found', function() { + raneto.contentDir = __dirname +'/content/'; + var result = raneto.doSearch('asdasdasd'); + expect(result).to.be.empty; + }); +});