Permalink
Browse files

[major] constructor handbook and add searching with lunr

  • Loading branch information...
1 parent 418767d commit fd5cf7d4cc68a4bc5c41a59ed9352edff7a718f3 @Swaagie Swaagie committed Mar 13, 2013
Showing with 93 additions and 31 deletions.
  1. +90 −29 index.js
  2. +3 −2 package.json
View
119 index.js
@@ -3,6 +3,11 @@
var path = require('path'),
fs = require('fs'),
natural = require('natural'),
+ lunr = require('lunr'),
+ idx = lunr(function setupLunr() {
+ this.field('title', { boost: 10 });
+ this.field('body');
+ }),
tokenizer = new natural.WordTokenizer(),
loc = path.resolve(__dirname, 'content'),
scraper = {
@@ -11,20 +16,22 @@ var path = require('path'),
};
//
-// ### function scrape()
+// ### @private function scrape()
// #### @content {String} document content
// #### @key {String} scraper key
// Scrapes the [key] from the content by Regular Epression
//
function scrape(content, key) {
+ if (!content) return '';
+
var match = content.replace(/\n/g, ' ').match(scraper[key]);
// Only return scraped content if there is a meta:[key].
return match ? match[1].trim() : '';
}
//
-// ### function normalize()
+// ### @private function normalize()
// #### @file {String} file name
// Normalize the file name to resolve to a Markdown or index file.
//
@@ -35,21 +42,21 @@ function normalize(file) {
}
//
-// ### function fileContent()
+// ### @private function fileContent()
// #### @content {String} Document content
// Sugar content with additional properties from scraped content.
//
function fileContent(content) {
return {
- content: content,
+ content: content || '',
description: scrape(content, 'description'),
title: scrape(content, 'title'),
tags: tags(content, 10)
};
}
//
-// ### function frequency()
+// ### @private function frequency()
// #### @content {String} Document content
// Return list of words scored by Term Frequency-Inverse Document Frequency.
//
@@ -79,12 +86,14 @@ function frequency(content) {
}
//
-// ### function tags()
+// ### @private function tags()
// #### @content {String} Document content
// #### @n {Number} number of tags
// Return n highest scoring tags as determined by term frequency.
//
function tags(content, n) {
+ if (!content) return [];
+
return frequency(content).sort(function sortByScore(a, b) {
return b.score - a.score;
}).slice(0, n).map(function extractWords(tag) {
@@ -93,7 +102,26 @@ function tags(content, n) {
}
//
-// ### function walk()
+// ### @private function read()
+// #### @file {String} Filename
+// #### @callback {Function} Callback for file contents
+// Returns file content by normalized path, if a callback is provided, content
+// is returned asynchronously.
+//
+function read(file, callback) {
+ if (!callback) {
+ return fileContent(fs.readFileSync(path.resolve(loc, normalize(file)), 'utf8'));
+ }
+
+ file = path.resolve(loc, normalize(file));
+
+ fs.readFile(file, 'utf8', function read(error, content) {
+ callback.apply(this, [error, fileContent(content)]);
+ });
+}
+
+//
+// ### @private function walk()
// #### @dir {String} Directory path to crawl
// #### @result {Object} Append content to current results
// #### @callback {Function} Callback for sub directory
@@ -136,7 +164,7 @@ function walk(dir, callback, result, sub) {
if (ref === 'index') name.pop();
// Get the tile of the file.
- get(file, function getFile(err, file) {
+ read(file, function getFile(err, file) {
result[current][ref] = {
href: sub ? name.join('/') : '',
title: file.title,
@@ -152,7 +180,7 @@ function walk(dir, callback, result, sub) {
}
//
-// ### function walkSync()
+// ### @private function walkSync()
// #### @dir {String} Directory path to crawl
// #### @result {Object} Append content to current results
// #### @sub {Boolean} Is directory subdirectory of dir
@@ -189,7 +217,7 @@ function walkSync(dir, result, sub) {
// Append file information to current container.
result[current][ref] = {
href: sub ? name.join('/') : '',
- title: get(file).title,
+ title: read(file).title,
path: dir
};
}
@@ -199,37 +227,70 @@ function walkSync(dir, result, sub) {
}
//
+// ### @private function prepareSearch()
+// #### @toc {Object} Representation of table of contents
+// Prepare search index to query against, this method may not be exposed
+// as it is synchronously only.
+//
+function prepareSearch(toc) {
+ var document;
+
+ Object.keys(toc).forEach(function loopSections(section) {
+ Object.keys(toc[section]).forEach(function loopPages(page) {
+ page = (section !== 'index' ? section + '/' : '') + page;
+ document = read(page);
+
+ idx.add({
+ id: page,
+ title: document.title,
+ body: document.content
+ });
+ });
+ });
+}
+
+//
+// ### function Handbook()
+// Constructor for easy access to Handbook content.
+//
+function Handbook() {
+ // On constructuing handbook synchronously prepare the search index. Listening
+ // to a search index ready event is not required thus easing the flow.
+ prepareSearch(walkSync(loc));
+}
+
+//
// ### function get()
// #### @file {String} Filename
// #### @callback {Function} Callback for file contents
-// Returns file content by normalized path, if a callback is provided, content
-// is returned asynchronously.
+// Proxy method to private async method read. This method can be called
+// synchronously by omitting the callback.
//
-function get(file, callback) {
- if (!callback) {
- return fileContent(fs.readFileSync(path.resolve(loc, normalize(file)), 'utf8'));
- }
-
- file = path.resolve(loc, normalize(file));
-
- fs.readFile(file, 'utf8', function read(error, content) {
- callback.apply(this, [error, fileContent(content)]);
- });
-}
+Handbook.prototype.get = function get(file, callback) {
+ read.apply(this, arguments);
+};
//
// ### function catalog()
// #### @callback {Function} Callback for catalog/TOC
// Returns a catalog by parsing the content directory, if a callback is provided
// content is returned asynchronously.
-function catalog(callback) {
+//
+Handbook.prototype.catalog = function catalog(callback) {
if (!callback) return walkSync(loc);
walk(loc, callback);
-}
+};
-// Expose public functions.
-module.exports = {
- get: get,
- catalog: catalog
+//
+// ### function search()
+// #### @query {String} Query to search against
+// Proxy to the search method of Lunr, returns a list of documents, due to Lunr
+// this must be called on scope of Lunr.
+//
+Handbook.prototype.search = function (query) {
+ return idx.search.call(idx, query);
};
+
+// Expose public functions.
+module.exports = Handbook;
View
@@ -2,7 +2,7 @@
"author": "Nodejitsu <support@nodejitsu.com> (http://nodejitsu.com)",
"name": "handbook",
"description": "A gentle introduction to the art of nodejitsu",
- "version": "0.3.5",
+ "version": "0.4.0",
"subdomain": "handbook",
"homepage": "http://www.nodejitsu.com/docs",
"repository": {
@@ -13,7 +13,8 @@
"start": "bin/server"
},
"dependencies": {
- "natural": "0.1.19"
+ "natural": "0.1.19",
+ "lunr": "0.2.x"
},
"engines": {
"node": "0.8.x"

0 comments on commit fd5cf7d

Please sign in to comment.