<commit_before>const DEFAULT_ENCODING = 'UTF8';
const DEFAULT_RESOURCE_TIMEOUT = 3500;
const DEFAULT_PAGE_TIMEOUT = 5000;
const MAX_RESOURCES_CACHE = 50;
const MAX_RESOURCE_AGE = 1000 * 60; // 60 seconds
const MODIFIED_ROUND_TIME = 1000 * 60 * 5; // 5 minutes
const EXPIRY_TIME = 1000 * 60 * 5; // 5 minutes

var fs = require('fs');
var path = require('path');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

var _ = require('underscore');
var async = require('async');
var hyperquest = require('hyperquest');
var lru = require('lru-cache');
var cache = lru({
	max: MAX_RESOURCES_CACHE,
	length: function( n ){ return 1 },
	maxAge: MAX_RESOURCE_AGE
});

// rounds datetime to nearest 5 minutes (in the past)
var getRoundedTime = function( datetime, round_by ){
	var remainder = datetime % round_by;
	var rounded_time = new Date( datetime - remainder );
	return rounded_time;
};

var Page = function( page_path, options ){

	// properly inherit from EventEmitter part 1
	EventEmitter.call( this );

	var page = this;

	options = options || {};
	this.options = options;
	var server = this.options.server;
	var router = server.router;
	this.path = page_path;
	this.relative_path = path.relative( server.paths.views, page_path );

	// adds a route based on the page's path
	this.createRoute = function(){

		page.is_index = /index\.hbs$/i.test( this.relative_path );
		var route = this.relative_path.replace( /\.[a-z0-9]+$/i, '' ).replace( /\\/g, '/' );
		var route = '/'+ route;
		route = route.replace( '/index', '' ); // replace indexes with base routes
		route = route.replace( /{([a-z_-]*)}/ig, ':$1' ); // replace dynamic bits
		if( route === '' ) route = '/';
		page.route = route;

		// only overwrite existing routes if we're an index page
		var existing_route = _( router.routes.get ).find( function( route_data ){
			return route_data.path === route;
		});
		if( existing_route ){
			server.logger.log( 'Warning. You have a conflicting route at "'+ existing_route.path +'"', 0 );
			if( !page.is_index ) return route; // return out if this isn't an index
			router.routes.get = _( router.routes.get ).without( existing_route ); // ensure the old route is removed if this is an index
		}

		router.get( route +'.json', function( req, res ){
			page.render( req, res, {
				json: true
			});
		});

		router.get( route, function( req, res ){
			page.render( req, res );
		});

		return route;

	};

	// reads the json configuration inside the view
	this.parsePage = function( callback ){

		fs.readFile( this.path, DEFAULT_ENCODING, function( err, data ){

			var params = {};
			var params_exec = /^{{!\s([\S\s]+?)\s}}/.exec( data );
			try {
				params = ( params_exec )? JSON.parse( params_exec[1] ): {};
			}
			catch( err ){
				server.logger.log( 'Error preprocessing "'+ page.path +'" '+ err, 0 );
			}
			finally {
				page.params = params;
				_( page ).extend({
					title: params.title,
					description: params.description,
					name: params.name,
					layout: params.layout
				});
				if( callback ) callback( params );
			}

		});

	};

	// fetches remote resources
	this.fetchResources = function( resources, params, callback ){

		var page = this;
		var resources_data = {};

		if( resources ){
			var resources_array = _( resources ).pairs();
			async.each( resources_array, function( resource, cb ){
				var resource_name = resource[0];
				var resource_url = resource[1];
				// replace variable strings like {this} in the resource url with url or query parameters
				resource_url = resource_url.replace( /\{([^\}]*)\}/ig, function( match, capture, index, str ){
					var replacement = params[capture] || '';
					return replacement;
				});
				page.fetchResource( resource_url, function( err, response ){
					if( err ){
						server.logger.log( 'Error retrieving resource "'+ resource_name +'": '+ err, 3 );
						return cb( err );
					}
					resources_data[resource[0]] = response;
					return cb( null );
				});
			}, function( err ){
				if( callback ) callback( err, resources_data );
			});
		}
		else {
			if( callback ) callback( resources_data );
		}

	};

	// fetches a single resource and returns its contents
	this.fetchResource = function( url, callback ){

		var cached_resource = cache.get( url );
		if( cached_resource ) return callback( null, cached_resource );
		var req = hyperquest( url, {
			json: true,
			timeout: DEFAULT_RESOURCE_TIMEOUT
		}, function( err, res ){
			if( err ) return callback( err, null );
			var data = '';
			res.on( 'data', function( chunk ){
				data += chunk;
			});
			res.on( 'end', function(){
				var response;
				try {
					response = data.toString( DEFAULT_ENCODING );
					response = JSON.parse( response );
				} catch( err ){
					return callback( err, null );
				}
				cache.set( url, response );
				return callback( null, response );
			});
		});

	};

	// preprocesses the page's context
	this.preprocess = function( context, callback ){

		var preprocessor = server.preprocessors[page.params.preprocessor];

		if( preprocessor ){
			preprocessor.process( context, function( processed_context ){
				if( callback ) callback( processed_context );
			});
		}
		else {
			if( callback ) callback( context );
		}

	};

	// generates the page's markup
	this.render = function( req, res, options ){

		if( server.options.log_level >= 2 ) var start_serve = new Date;
		options = options || {};

		var context = {
			page: {
				path: this.path,
				title: this.title,
				description: this.description,
				name: this.name
			},
			parameters: {},
			query: req.query,
			resources: {},
			assets: {
				scripts: '<script src="/compiled/scripts.js"></script>',
				styles: '<link rel="stylesheet" href="/compiled/styles.css" />'
			},
			layout: ( this.layout || this.layout === false )? this.layout: this.getLayout()
		};
		context = _( context ).defaults( router.locals );

		// req.params is actually an array with crap stuck to it
		// so we have to parse that stuff out into a real object
		var parameters = {};
		for( var key in req.params ) parameters[key] = req.params[key];
		context.parameters = _( parameters ).extend( req.query );

		// actually render the page
		// uses once so we can have a custom timeout for .render
		var renderPage = _.once( function( context ){
			context = context || {};
			res.set({
				'Cache-Control': 'public, max-age='+ ( 60 * 5 ),
				'Expires': new Date( Date.now() + EXPIRY_TIME ).toUTCString(),
				'Last-Modified': getRoundedTime( Date.now(), MODIFIED_ROUND_TIME ).toUTCString()
			});
			if( options.json ) return res.json( context );
			res.expose( context, 'solidus.context', 'context' );
			server.logger.log( page.route +' served in '+ ( new Date - start_serve ) +'ms', 3 );
			res.render( page.relative_path, context );
		});

		// render the page manually if our context isn't fast enough
		setTimeout( renderPage, DEFAULT_PAGE_TIMEOUT );

		if( server.options.log_level >= 2 ) var start_resources = new Date;
		this.fetchResources( page.params.resources, context.parameters, function( err, resources ){
			server.logger.log( page.route +' resources fetched in '+ ( new Date - start_resources ) +'ms', 3 );
			context.resources = resources;
			if( server.options.log_level >= 2 ) var start_preprocess = new Date;
			page.preprocess( context, function( context ){
				server.logger.log( page.route +' preprocessed in '+ ( new Date - start_preprocess ) +'ms', 3 );
				renderPage( context );
			});
		});

	};

	// get the view's layout
	this.getLayout = function(){

		var layouts = _( server.layouts ).sortBy( function( layout_path ){
			return -layout_path.length;
		});
		var local_layout = _( layouts ).find( function( layout_path ){
			var layout_regex = new RegExp( layout_path.replace( /layout\..+$/i, '' ), 'i' );
			return layout_regex.test( page.path );
		});
		if( !local_layout ) return null;
		local_layout = path.relative( server.paths.views, local_layout );

		return local_layout;

	};

	// removes the page's route
	this.destroy = function(){

		router.routes.get = _( router.routes.get ).reject( function( current_route ){
			return current_route.path === page.route;
		});

	};

	this.createRoute();
	this.parsePage( function(){
		page.emit( 'ready' );
	});

};

// properly inherit from EventEmitter part 2
util.inherits( Page, EventEmitter );

Page.layouts = {};

module.exports = Page;<commit_msg>don't send cache headers if dev option is set<commit_after>const DEFAULT_ENCODING = 'UTF8';
const DEFAULT_RESOURCE_TIMEOUT = 3500;
const DEFAULT_PAGE_TIMEOUT = 5000;
const MAX_RESOURCES_CACHE = 50;
const MAX_RESOURCE_AGE = 1000 * 60; // 60 seconds
const MODIFIED_ROUND_TIME = 1000 * 60 * 5; // 5 minutes
const EXPIRY_TIME = 1000 * 60 * 5; // 5 minutes

var fs = require('fs');
var path = require('path');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

var _ = require('underscore');
var async = require('async');
var hyperquest = require('hyperquest');
var lru = require('lru-cache');
var cache = lru({
	max: MAX_RESOURCES_CACHE,
	length: function( n ){ return 1 },
	maxAge: MAX_RESOURCE_AGE
});

// rounds datetime to nearest 5 minutes (in the past)
var getRoundedTime = function( datetime, round_by ){
	var remainder = datetime % round_by;
	var rounded_time = new Date( datetime - remainder );
	return rounded_time;
};

var Page = function( page_path, options ){

	// properly inherit from EventEmitter part 1
	EventEmitter.call( this );

	var page = this;

	options = options || {};
	this.options = options;
	var server = this.options.server;
	var router = server.router;
	this.path = page_path;
	this.relative_path = path.relative( server.paths.views, page_path );

	// adds a route based on the page's path
	this.createRoute = function(){

		page.is_index = /index\.hbs$/i.test( this.relative_path );
		var route = this.relative_path.replace( /\.[a-z0-9]+$/i, '' ).replace( /\\/g, '/' );
		var route = '/'+ route;
		route = route.replace( '/index', '' ); // replace indexes with base routes
		route = route.replace( /{([a-z_-]*)}/ig, ':$1' ); // replace dynamic bits
		if( route === '' ) route = '/';
		page.route = route;

		// only overwrite existing routes if we're an index page
		var existing_route = _( router.routes.get ).find( function( route_data ){
			return route_data.path === route;
		});
		if( existing_route ){
			server.logger.log( 'Warning. You have a conflicting route at "'+ existing_route.path +'"', 0 );
			if( !page.is_index ) return route; // return out if this isn't an index
			router.routes.get = _( router.routes.get ).without( existing_route ); // ensure the old route is removed if this is an index
		}

		router.get( route +'.json', function( req, res ){
			page.render( req, res, {
				json: true
			});
		});

		router.get( route, function( req, res ){
			page.render( req, res );
		});

		return route;

	};

	// reads the json configuration inside the view
	this.parsePage = function( callback ){

		fs.readFile( this.path, DEFAULT_ENCODING, function( err, data ){

			var params = {};
			var params_exec = /^{{!\s([\S\s]+?)\s}}/.exec( data );
			try {
				params = ( params_exec )? JSON.parse( params_exec[1] ): {};
			}
			catch( err ){
				server.logger.log( 'Error preprocessing "'+ page.path +'" '+ err, 0 );
			}
			finally {
				page.params = params;
				_( page ).extend({
					title: params.title,
					description: params.description,
					name: params.name,
					layout: params.layout
				});
				if( callback ) callback( params );
			}

		});

	};

	// fetches remote resources
	this.fetchResources = function( resources, params, callback ){

		var page = this;
		var resources_data = {};

		if( resources ){
			var resources_array = _( resources ).pairs();
			async.each( resources_array, function( resource, cb ){
				var resource_name = resource[0];
				var resource_url = resource[1];
				// replace variable strings like {this} in the resource url with url or query parameters
				resource_url = resource_url.replace( /\{([^\}]*)\}/ig, function( match, capture, index, str ){
					var replacement = params[capture] || '';
					return replacement;
				});
				page.fetchResource( resource_url, function( err, response ){
					if( err ){
						server.logger.log( 'Error retrieving resource "'+ resource_name +'": '+ err, 3 );
						return cb( err );
					}
					resources_data[resource[0]] = response;
					return cb( null );
				});
			}, function( err ){
				if( callback ) callback( err, resources_data );
			});
		}
		else {
			if( callback ) callback( resources_data );
		}

	};

	// fetches a single resource and returns its contents
	this.fetchResource = function( url, callback ){

		var cached_resource = cache.get( url );
		if( cached_resource ) return callback( null, cached_resource );
		var req = hyperquest( url, {
			json: true,
			timeout: DEFAULT_RESOURCE_TIMEOUT
		}, function( err, res ){
			if( err ) return callback( err, null );
			var data = '';
			res.on( 'data', function( chunk ){
				data += chunk;
			});
			res.on( 'end', function(){
				var response;
				try {
					response = data.toString( DEFAULT_ENCODING );
					response = JSON.parse( response );
				} catch( err ){
					return callback( err, null );
				}
				cache.set( url, response );
				return callback( null, response );
			});
		});

	};

	// preprocesses the page's context
	this.preprocess = function( context, callback ){

		var preprocessor = server.preprocessors[page.params.preprocessor];

		if( preprocessor ){
			preprocessor.process( context, function( processed_context ){
				if( callback ) callback( processed_context );
			});
		}
		else {
			if( callback ) callback( context );
		}

	};

	// generates the page's markup
	this.render = function( req, res, options ){

		if( server.options.log_level >= 2 ) var start_serve = new Date;
		options = options || {};

		var context = {
			page: {
				path: this.path,
				title: this.title,
				description: this.description,
				name: this.name
			},
			parameters: {},
			query: req.query,
			resources: {},
			assets: {
				scripts: '<script src="/compiled/scripts.js"></script>',
				styles: '<link rel="stylesheet" href="/compiled/styles.css" />'
			},
			layout: ( this.layout || this.layout === false )? this.layout: this.getLayout()
		};
		context = _( context ).defaults( router.locals );

		// req.params is actually an array with crap stuck to it
		// so we have to parse that stuff out into a real object
		var parameters = {};
		for( var key in req.params ) parameters[key] = req.params[key];
		context.parameters = _( parameters ).extend( req.query );

		// actually render the page
		// uses once so we can have a custom timeout for .render
		var renderPage = _.once( function( context ){
			context = context || {};
			if( !server.options.dev ){
				res.set({
					'Cache-Control': 'public, max-age='+ ( 60 * 5 ),
					'Expires': new Date( Date.now() + EXPIRY_TIME ).toUTCString(),
					'Last-Modified': getRoundedTime( Date.now(), MODIFIED_ROUND_TIME ).toUTCString()
				});
			}
			if( options.json ) return res.json( context );
			res.expose( context, 'solidus.context', 'context' );
			server.logger.log( page.route +' served in '+ ( new Date - start_serve ) +'ms', 3 );
			res.render( page.relative_path, context );
		});

		// render the page manually if our context isn't fast enough
		setTimeout( renderPage, DEFAULT_PAGE_TIMEOUT );

		if( server.options.log_level >= 2 ) var start_resources = new Date;
		this.fetchResources( page.params.resources, context.parameters, function( err, resources ){
			server.logger.log( page.route +' resources fetched in '+ ( new Date - start_resources ) +'ms', 3 );
			context.resources = resources;
			if( server.options.log_level >= 2 ) var start_preprocess = new Date;
			page.preprocess( context, function( context ){
				server.logger.log( page.route +' preprocessed in '+ ( new Date - start_preprocess ) +'ms', 3 );
				renderPage( context );
			});
		});

	};

	// get the view's layout
	this.getLayout = function(){

		var layouts = _( server.layouts ).sortBy( function( layout_path ){
			return -layout_path.length;
		});
		var local_layout = _( layouts ).find( function( layout_path ){
			var layout_regex = new RegExp( layout_path.replace( /layout\..+$/i, '' ), 'i' );
			return layout_regex.test( page.path );
		});
		if( !local_layout ) return null;
		local_layout = path.relative( server.paths.views, local_layout );

		return local_layout;

	};

	// removes the page's route
	this.destroy = function(){

		router.routes.get = _( router.routes.get ).reject( function( current_route ){
			return current_route.path === page.route;
		});

	};

	this.createRoute();
	this.parsePage( function(){
		page.emit( 'ready' );
	});

};

// properly inherit from EventEmitter part 2
util.inherits( Page, EventEmitter );

Page.layouts = {};

module.exports = Page;<|endoftext|>


<commit_before>Ext4.define('Kwf.Ext4.Data.StoreSyncQueue', {
    constructor: function()
    {
        this._queue = [];
        this._running = [];
        this.exceptions = [];
        this.hasException = false;
        this.isRunning = false;
    },

    start: function(startOptions)
    {
        this._startOptions = startOptions;
        this.isRunning = true;
        Ext4.each(this._queue, function(i) {
            this._startSync(i.store, i.options);
        }, this);
        this._queue.length = 0;

        if (!this._running.length) {
            this._callbackFinished();
        }
    },

    /**
     * Add a store that should be synced to the queue. If sync is already running start sync immediately.
     */
    add: function(store, options)
    {
        options = options || {};
        if (this.isRunning) {
            this._startSync(store, options);
        } else {
            this._queue.push({store: store, options: options});
        }
    },

    _startSync: function(store, options)
    {
        if (store.getNewRecords().length || store.getUpdatedRecords().length || store.getRemovedRecords().length) {
            this._running.push(store);
            store.sync({
                store: store,
                queueOptions: options,
                callback: function(batch, options) {
                    var store = options.store;
                    this._running.splice(this._running.indexOf(store), 1);
                    this._callback(batch.hasException, batch, options);
                    if (!this._running.length) {
                        this._callbackFinished();
                    }
                },
                scope: this
            });
        } else {
            this._callback(false, {}, {
                queueOptions: options,
                store: store
            });
        }
    },

    _callback: function(hasException, batch, options)
    {
        var store = options.store;
        var qo = options.queueOptions;
        if (qo.callback) qo.callback.call(qo.scope || this, batch, qo);

        if (hasException) {
            if (qo.failure) qo.failure.call(qo.scope || this, batch, qo);
            this.exceptions = Ext.Array.merge(this.exceptions, batch.exceptions);
            this.hasException = true;
        } else {
            if (qo.success) qo.success.call(qo.scope || this, batch, qo);
        }
    },

    _callbackFinished: function()
    {
        this.isRunning = false;
        if (this.hasException) {
            if (this._startOptions.failure) this._startOptions.failure.call(this._startOptions.scope || this, this);
        } else {
            if (this._startOptions.success) this._startOptions.success.call(this._startOptions.scope || this, this);
        }
        if (this._startOptions.callback) this._startOptions.callback.call(this._startOptions.scope || this, this);
    }
});
<commit_msg>correctly use ext4 namespace<commit_after>Ext4.define('Kwf.Ext4.Data.StoreSyncQueue', {
    constructor: function()
    {
        this._queue = [];
        this._running = [];
        this.exceptions = [];
        this.hasException = false;
        this.isRunning = false;
    },

    start: function(startOptions)
    {
        this._startOptions = startOptions;
        this.isRunning = true;
        Ext4.each(this._queue, function(i) {
            this._startSync(i.store, i.options);
        }, this);
        this._queue.length = 0;

        if (!this._running.length) {
            this._callbackFinished();
        }
    },

    /**
     * Add a store that should be synced to the queue. If sync is already running start sync immediately.
     */
    add: function(store, options)
    {
        options = options || {};
        if (this.isRunning) {
            this._startSync(store, options);
        } else {
            this._queue.push({store: store, options: options});
        }
    },

    _startSync: function(store, options)
    {
        if (store.getNewRecords().length || store.getUpdatedRecords().length || store.getRemovedRecords().length) {
            this._running.push(store);
            store.sync({
                store: store,
                queueOptions: options,
                callback: function(batch, options) {
                    var store = options.store;
                    this._running.splice(this._running.indexOf(store), 1);
                    this._callback(batch.hasException, batch, options);
                    if (!this._running.length) {
                        this._callbackFinished();
                    }
                },
                scope: this
            });
        } else {
            this._callback(false, {}, {
                queueOptions: options,
                store: store
            });
        }
    },

    _callback: function(hasException, batch, options)
    {
        var store = options.store;
        var qo = options.queueOptions;
        if (qo.callback) qo.callback.call(qo.scope || this, batch, qo);

        if (hasException) {
            if (qo.failure) qo.failure.call(qo.scope || this, batch, qo);
            this.exceptions = Ext4.Array.merge(this.exceptions, batch.exceptions);
            this.hasException = true;
        } else {
            if (qo.success) qo.success.call(qo.scope || this, batch, qo);
        }
    },

    _callbackFinished: function()
    {
        this.isRunning = false;
        if (this.hasException) {
            if (this._startOptions.failure) this._startOptions.failure.call(this._startOptions.scope || this, this);
        } else {
            if (this._startOptions.success) this._startOptions.success.call(this._startOptions.scope || this, this);
        }
        if (this._startOptions.callback) this._startOptions.callback.call(this._startOptions.scope || this, this);
    }
});
<|endoftext|>


<commit_before>var api = require('./api.js');

$(document).ready(function() {
    function gup( name )
    {
	name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
	var regexS = "[\\?&]"+name+"=([^&#]*)";
	var regex = new RegExp( regexS );
	var results = regex.exec( window.location.href );
	if( results == null )
	    return "";
	else
	    return results[1];
    }
    var params = [ 'cameraid', 'year', 'month', 'day',
	           'hour', 'minute', 'second', 'micros' ];

    var query = {};
    var title = '';
    for( var pi = 0 ; pi < params.length; pi++ ) {
	var arg = gup(params[pi]);
	if( arg ) {
	    query[params[pi]] = arg;

	    switch(params[pi]) {
	    case 'cameraid': title += arg; break;
	    case 'year': title += ' 20' + arg; break;
	    case 'month': title += '-' + arg; break;
	    case 'day': title += '-' + arg; break;
	    case 'hour': title += ' klo ' + arg; break;
	    case 'minute': title += ':' + arg; break;
	    case 'second': title += ':' + arg; break;
	    };
        }
    }

    if( title )
        $('.navbar-nav li p').text('Kamera ' + title);

    api.getPics(query, function(err, pics) {
	if( err )
	    return;

	var counter = 0;
	for( var di = 0; di < pics.length; di++ ) {
	    var pic = pics[di];
	    if( pic.group ) {
                var nexturl = '/pics.html?';
	        for( var ci in pic.next )
	            nexturl += ci + '=' + pic.next[ci] + '&';
	        $('#pics .row:last').append(
		    '
' +
		    '  ' +
		    '
' + pic.titlestr + '
' +
		    '    ' +
		    '
' + pic.count + ' kuvaa' +
		    '

'
		);
            } else {
	        $('#pics .row:last').append(
		    '
' +
		    '  ' +
		    '    ' +
		    '
' + pic.timestamp +
		    '

'
		);
	    }
	}
    });
});
<commit_msg>translated texts<commit_after>var api = require('./api.js');

$(document).ready(function() {
    function gup( name )
    {
	name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
	var regexS = "[\\?&]"+name+"=([^&#]*)";
	var regex = new RegExp( regexS );
	var results = regex.exec( window.location.href );
	if( results == null )
	    return "";
	else
	    return results[1];
    }
    var params = [ 'cameraid', 'year', 'month', 'day',
	           'hour', 'minute', 'second', 'micros' ];

    var query = {};
    var title = '';
    for( var pi = 0 ; pi < params.length; pi++ ) {
	var arg = gup(params[pi]);
	if( arg ) {
	    query[params[pi]] = arg;

	    switch(params[pi]) {
	    case 'cameraid': title += arg; break;
	    case 'year': title += ' 20' + arg; break;
	    case 'month': title += '-' + arg; break;
	    case 'day': title += '-' + arg; break;
	    case 'hour': title += ' klo ' + arg; break;
	    case 'minute': title += ':' + arg; break;
	    case 'second': title += ':' + arg; break;
	    };
        }
    }

    if( title )
        $('.navbar-nav li p').text('Camera ' + title);

    api.getPics(query, function(err, pics) {
	if( err )
	    return;

	var counter = 0;
	for( var di = 0; di < pics.length; di++ ) {
	    var pic = pics[di];
	    if( pic.group ) {
                var nexturl = '/pics.html?';
	        for( var ci in pic.next )
	            nexturl += ci + '=' + pic.next[ci] + '&';
	        $('#pics .row:last').append(
		    '
' +
		    '  ' +
		    '
' + pic.titlestr + '
' +
		    '    ' +
		    '
' + pic.count + ' pictures' +
		    '

'
		);
            } else {
	        $('#pics .row:last').append(
		    '
' +
		    '  ' +
		    '    ' +
		    '
' + pic.timestamp +
		    '

'
		);
	    }
	}
    });
});
<|endoftext|>


<commit_before>define(['underscore', 'backbone', 'backbone.marionette', 'nusmods',
    'hbs!../templates/select', 'hbs!../templates/select_result','select2'],
  function(_, Backbone, Marionette, NUSMods, template, selectResultTemplate) {
    'use strict';

    var codes = _.keys(timetableData.mods);
    var titles = _.pluck(_.values(timetableData.mods), 'title');
    var modsLength = codes.length;

    return Marionette.ItemView.extend({
      className: 'form-group',
      template: template,

      events: {
        'select2-open': 'onSelect2Open',
        'select2-selecting': 'onSelect2Selecting'
      },

      ui: {
        'input': 'input'
      },

      onAdd: function (event) {
        var id = $(event.currentTarget).data('code');
        NUSMods.getMod(id, _.bind(function (mod) {
          mod.code = id;
          this.collection.add(mod);
        }, this));
        $('#select2-drop').off('mouseup', '.btn');
        this.ui.input.select2('focus');
      },

      onSelect2Open: function () {
        $('#select2-drop').on('mouseup', '.btn', this.onAdd);
      },

      onSelect2Selecting: function(event) {
        event.preventDefault();
        Backbone.history.navigate('modules/' + event.val, {trigger: true});
        this.ui.input.select2('focus');
      },

      onShow: function () {
        _.bindAll(this, 'onAdd');

        var PAGE_SIZE = 50;
        this.ui.input.select2({
          multiple: true,
          formatResult: function (object) {
            return selectResultTemplate(object);
          },
          initSelection: function (el, callback) {
            callback(_.map(el.val().split(','), function (code) {
              return {
                id: code,
                text: code + ' ' + timetableData.mods[code].title
              };
            }));
          },
          query: function (options) {
            var i,
              results = [],
              pushResult = function (i) {
                return results.push({
                  id: codes[i],
                  text: codes[i] + ' ' + titles[i]
                });
              };
            if (options.term) {
              var re = new RegExp(options.term, 'i');
              for (i = options.context || 0; i < modsLength; i++) {
                if (codes[i].search(re) !== -1 || titles[i].search(re) !== -1) {
                  if (pushResult(i) === PAGE_SIZE) {
                    i++;
                    break;
                  }
                }
              }
            } else {
              for (i = (options.page - 1) * PAGE_SIZE; i < options.page * PAGE_SIZE; i++) {
                pushResult(i);
              }
            }
            options.callback({
              context: i,
              more: i < modsLength,
              results: results
            });
          }
        });
      }
    });
  });
<commit_msg>Execute select2-open handler once as #select2-drop is never destroyed<commit_after>define(['underscore', 'backbone', 'backbone.marionette', 'nusmods',
    'hbs!../templates/select', 'hbs!../templates/select_result','select2'],
  function(_, Backbone, Marionette, NUSMods, template, selectResultTemplate) {
    'use strict';

    var codes = _.keys(timetableData.mods);
    var titles = _.pluck(_.values(timetableData.mods), 'title');
    var modsLength = codes.length;

    return Marionette.ItemView.extend({
      className: 'form-group',
      template: template,

      events: {
        'select2-selecting': 'onSelect2Selecting'
      },

      ui: {
        'input': 'input'
      },

      onAdd: function (event) {
        var id = $(event.currentTarget).data('code');
        NUSMods.getMod(id, _.bind(function (mod) {
          mod.code = id;
          this.collection.add(mod);
        }, this));
        this.ui.input.select2('focus');
      },

      onSelect2Selecting: function(event) {
        event.preventDefault();
        Backbone.history.navigate('modules/' + event.val, {trigger: true});
        this.ui.input.select2('focus');
      },

      onShow: function () {
        _.bindAll(this, 'onAdd');

        var PAGE_SIZE = 50;
        this.ui.input.select2({
          multiple: true,
          formatResult: function (object) {
            return selectResultTemplate(object);
          },
          initSelection: function (el, callback) {
            callback(_.map(el.val().split(','), function (code) {
              return {
                id: code,
                text: code + ' ' + timetableData.mods[code].title
              };
            }));
          },
          query: function (options) {
            var i,
              results = [],
              pushResult = function (i) {
                return results.push({
                  id: codes[i],
                  text: codes[i] + ' ' + titles[i]
                });
              };
            if (options.term) {
              var re = new RegExp(options.term, 'i');
              for (i = options.context || 0; i < modsLength; i++) {
                if (codes[i].search(re) !== -1 || titles[i].search(re) !== -1) {
                  if (pushResult(i) === PAGE_SIZE) {
                    i++;
                    break;
                  }
                }
              }
            } else {
              for (i = (options.page - 1) * PAGE_SIZE; i < options.page * PAGE_SIZE; i++) {
                pushResult(i);
              }
            }
            options.callback({
              context: i,
              more: i < modsLength,
              results: results
            });
          }
        });

        this.ui.input.one('select2-open', _.bind(function () {
          $('#select2-drop').on('mouseup', '.btn', this.onAdd);
        }, this));
      }
    });
  });
<|endoftext|>


<commit_before>var mcpe_ping = require('mcpe-ping');
var mcpc_ping = require('mc-ping-updated');

var util = require('./util');

// This is a wrapper function for mc-ping-updated, mainly used to convert the data structure of the result.
function pingMinecraftPC(host, port, timeout, callback) {
    var startTime = util.getCurrentTimeMs();

	// Try catch incase the down stream module is bad at handling exceptions.
	try {
		mcpc_ping(host, port, function(err, res) {
		    if (err) {
		        callback(err, null);
		    } else {
		    	// Remap our JSON into our custom structure.
		        callback(null, {
					players: {
						online: res.players.online,
						max: res.players.max
					},
					version: res.version.protocol,
					latency: util.getCurrentTimeMs() - startTime,
					favicon: res.favicon
				});
		    }
		}, timeout);
	} catch (err) {
		callback(err, null);
	}
}

// This is a wrapper function for mcpe-ping, mainly used to convert the data structure of the result.
function pingMinecraftPE(host, port, timeout, callback) {
	var startTime = util.getCurrentTimeMs();

	// Try catch incase the down stream module is bad at handling exceptions.
	try {
		mcpe_ping(host, port || 19132, function(err, res) {
			if (err) {
				callback(err, null);
			} else {
				// Remap our JSON into our custom structure.
				callback(err, {
					players: {
						online: parseInt(res.currentPlayers),
						max: parseInt(res.maxPlayers)
					},
					version: res.version,
					latency: util.getCurrentTimeMs() - startTime
				});
			}
		}, timeout);
	} catch (err) {
		callback(err, null);
	}
}

exports.ping = function(host, port, type, timeout, callback) {
	if (type === 'PC') {
		pingMinecraftPC(host, port || 25565, timeout, callback);
	} else if (type === 'PE') {
		pingMinecraftPE(host, port || 19132, timeout, callback);
	} else {
		throw new Error('Unsupported type: ' + type);
	}
};<commit_msg>Drop the try catches, this might make it a bit unstable until we work out some bugs.<commit_after>var mcpe_ping = require('mcpe-ping');
var mcpc_ping = require('mc-ping-updated');

var util = require('./util');

// This is a wrapper function for mc-ping-updated, mainly used to convert the data structure of the result.
function pingMinecraftPC(host, port, timeout, callback) {
    var startTime = util.getCurrentTimeMs();

	mcpc_ping(host, port, function(err, res) {
	    if (err) {
	        callback(err, null);
	    } else {
	    	// Remap our JSON into our custom structure.
	        callback(null, {
				players: {
					online: res.players.online,
					max: res.players.max
				},
				version: res.version.protocol,
				latency: util.getCurrentTimeMs() - startTime,
				favicon: res.favicon
			});
	    }
	}, timeout);
}

// This is a wrapper function for mcpe-ping, mainly used to convert the data structure of the result.
function pingMinecraftPE(host, port, timeout, callback) {
	var startTime = util.getCurrentTimeMs();

	mcpe_ping(host, port || 19132, function(err, res) {
		if (err) {
			callback(err, null);
		} else {
			// Remap our JSON into our custom structure.
			callback(err, {
				players: {
					online: parseInt(res.currentPlayers),
					max: parseInt(res.maxPlayers)
				},
				version: res.version,
				latency: util.getCurrentTimeMs() - startTime
			});
		}
	}, timeout);
}

exports.ping = function(host, port, type, timeout, callback) {
	if (type === 'PC') {
		pingMinecraftPC(host, port || 25565, timeout, callback);
	} else if (type === 'PE') {
		pingMinecraftPE(host, port || 19132, timeout, callback);
	} else {
		throw new Error('Unsupported type: ' + type);
	}
};<|endoftext|>


<commit_before>(function () {
    'use strict';

    var assert      = require('assert');

    module.exports          = all;
    module.exports.getBody  = getBody;

    function all(streams, options, callback) {
        if (!callback) {
            callback    = options;

            options     = {
                end: true
            };
        }

        assert(streams, 'streams could not be empty!');
        assert(options, 'callback could not be empty!');

        pipe(streams, options, callback);
    }

    function pipe(streams, options, callback) {
        var read    = streams[0],
            fn      = function() {
                callback.apply(null, arguments);
                unsetListeners(streams, fn);
            };

        read.pause();

        setListeners(streams, options, fn);

        read.resume();
    }

    function setListeners(streams, options, fn) {
        var main,
            read    = streams[0];

        streams.forEach(function(stream) {
            stream.on('error', fn);

            if (!main)
                main = stream;
            else
                main = main.pipe(stream, {
                    end: options.end
                });
        });

        read.on('end', fn);
    }

    function unsetListeners(streams, fn) {
        var read    = streams[0];

        streams.forEach(function(stream) {
            stream.removeListener('error', fn);
        });

        read.removeListener('end', fn);
    }

    function once(callback) {
        var called,
            fn  = function() {
                if (!called) {
                    callback.apply(null, arguments);
                }
            };

        return fn;
    }

    /**
     * get body of readStream
     *
     * @param readStream
     * @param callback
     */
    function getBody(readStream, callback) {
        var body = '',
            fn  = once(callback);

        assert(readStream, 'could not be empty!');
        assert(callback, 'could not be empty!');

        readStream.on('data', function(chunk) {
            body += chunk;
        });

        readStream.once('error', function(error) {
            fn(error);
        });

        readStream.once('end', function() {
            fn(null, body);
        });
    }
})();
<commit_msg>feature(pipe) getBody: rmListeners<commit_after>(function () {
    'use strict';

    var assert      = require('assert');

    module.exports          = all;
    module.exports.getBody  = getBody;

    function all(streams, options, callback) {
        if (!callback) {
            callback    = options;

            options     = {
                end: true
            };
        }

        assert(streams, 'streams could not be empty!');
        assert(options, 'callback could not be empty!');

        pipe(streams, options, callback);
    }

    function pipe(streams, options, callback) {
        var read    = streams[0],
            fn      = function() {
                callback.apply(null, arguments);
                unsetListeners(streams, fn);
            };

        read.pause();

        setListeners(streams, options, fn);

        read.resume();
    }

    function setListeners(streams, options, fn) {
        var main,
            read    = streams[0];

        streams.forEach(function(stream) {
            stream.on('error', fn);

            if (!main)
                main = stream;
            else
                main = main.pipe(stream, {
                    end: options.end
                });
        });

        read.on('end', fn);
    }

    function unsetListeners(streams, fn) {
        var read    = streams[0];

        streams.forEach(function(stream) {
            stream.removeListener('error', fn);
        });

        read.removeListener('end', fn);
    }

    /**
     * get body of readStream
     *
     * @param readStream
     * @param callback
     */
    function getBody(readStream, callback) {
        var body    = '',

            onData  = function(chunk) {
                body += chunk;
            },

            onError = function(error) {
                callback(error);
                rmListeners();
            },

            onEnd   = function() {
                callback(null, body);
                rmListeners();
            },

            rmListeners = function() {
                readStream.removeListener('data', onData);
                readStream.removeListener('error', onError);
                readStream.removeListener('end', onEnd);
            };

        assert(readStream, 'could not be empty!');
        assert(callback, 'could not be empty!');

        readStream.on('data', onData);
        readStream.on('error', onError);
        readStream.on('end', onEnd);
    }
})();
<|endoftext|>


<commit_before>var _ = require('./utils')

module.exports = pool

function pool(num, thread) {
  var threadRun = thread.run
  var threads = [ thread ]
  var options = thread.options
  var terminate = thread.terminate

  function findBestAvailableThread(offset) {
    var i, l, thread, pending
    for (i = 0, l = threads.length; i < l; i += 1) {
      thread = threads[i]
      pending = thread.pending()
      if (pending === 0 || pending < offset) {
        if (thread.terminated) {
          threads.splice(i, 1)
          l -= 1
        } else {
          return thread
        }
      }
    }
  }

  function newThread() {
    var thread = new pool.Thread(options)
    threads.push(thread)
    return thread
  }

  function runTask(thread, args) {
    var task
    if (thread === threads[0]) {
      task = threadRun.apply(thread, args)
    } else {
      task = thread.run.apply(thread, args)
    }
    return task
  }

  thread.run = thread.exec = function () {
    var args = arguments

    function nextThread(count) {
      var task, thread = findBestAvailableThread(count)
      if (thread) {
        task = runTask(thread, args)
      } else {
        if (threads.length < num) {
          task = runTask(newThread(), args)
        } else {
          task = nextThread(count + 1)
        }
      }
      return task
    }

    return nextThread(0)
  }

  thread.terminate = thread.kill = function () {
    _.each(threads, function (thread, i) {
      if (i === 0) terminate.call(thread)
      else thread.terminate()
    })
    threads.splice(0)
  }

  thread.threadPool = threads
  thread.isPool = true

  return thread
}
<commit_msg>refactor(pool): new code design and lambda lifting<commit_after>var _ = require('./utils')

module.exports = pool

function pool(num, thread) {
  var threads = [ thread ]

  // decorate Thread interface
  thread.run = thread.exec = function () {
    return selectThread(thread, threads, arguments)(0)
  }

  thread.terminate = thread.kill = function () {
    _.each(threads, function (thread) {
      thread.terminate()
    })
    threads.splice(0)
  }

  thread.threadPool = threads
  thread.isPool = true

  return thread
}

function createThread(threads) {
  var options = threads[0].options
  return threads.push(new pool.Thread(options))
}

function runTaskInThread(thread, args) {
  return thread.run.apply(thread, args)
}

function selectThread(thread, threads, args) {
  return function newSelectRound(count) {
    var task, thread = findBestAvailableThread(threads, count)
    if (thread) {
      task = runTaskInThread(thread, args)
    } else {
      if (threads.length < num) {
        task = runTaskInThread(createThread(threads), args)
      } else {
        task = newSelectRound(count + 1)
      }
    }
    return task
  }
}

function findBestAvailableThread(threads, offset) {
  var thread, pending
  for (var i = 0, l = threads.length; i < l; i += 1) {
    thread = threads[i]
    pending = thread.pending()
    if (pending === 0 || pending < offset) {
      if (thread.terminated) {
        threads.splice(i, 1)
        l -= 1; i -= 1
      } else {
        return thread
      }
    }
  }
}
<|endoftext|>


<commit_before>var opener = require('opener')
var template = require('lodash.template')
var fs = require('fs')
var path = require('path')
var md = fs.readFileSync(path.join(__dirname, 'template.md'), 'utf8')
var message = template(md)
var endpoint = 'https://github.com/npm/support-cli/issues/new?body='

module.exports = function (answers) {
  var body = message(answers)
  opener(endpoint + body)
}
<commit_msg>Update the target url to npm/npm from npm/support-cli<commit_after>var opener = require('opener')
var template = require('lodash.template')
var fs = require('fs')
var path = require('path')
var md = fs.readFileSync(path.join(__dirname, 'template.md'), 'utf8')
var message = template(md)
var endpoint = 'https://github.com/npm/npm/issues/new?body='

module.exports = function (answers) {
  var body = message(answers)
  opener(endpoint + body)
}
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var req = require('./req');
var debug = require('debug')('wp-connect:action');

/**
 * Post methods
 *
 * @param {WPCONN} wpconn
 * @api public
 */

function Post(wpconn){
  this.wpconn = wpconn;
}

/**
 * Add method
 *
 * @param {String} id
 * @param {Object} params (optional)
 * @param {Function} fn
 * @api public
 */

Post.prototype.get = function(id, params, fn){
  // params || fn
  fn = fn || params;
  params = 'function' == typeof params ? {} : (params || {});

  // pass token value in params object
  params.token = this.wpconn.tkn;

  // set endpoint
  var set = {
    site: this.wpconn.site._id,
    post_ID: id
  };

  req('post', set, params, fn);
};

/**
 * Add method
 *
 * @param {Object} data
 * @param {String} rid
 * @param {Object} params
 * @param {Function} fn
 * @api public
 */

Post.prototype.add = function(data, fn){
  // set endpoint
  var set = { site: this.wpconn.site._id };

  var params = {
    token: this.wpconn.tkn,
    method: 'post',
    data: data
  };

  req('post_add', set, params, fn);
};

/**
 * Expose `Post` module
 */

module.exports = Post;
<commit_msg>post: use wpconn req instance instead of req mdl<commit_after>
/**
 * Module dependencies.
 */

var debug = require('debug')('wp-connect:action');

/**
 * Post methods
 *
 * @param {WPCONN} wpconn
 * @api public
 */

function Post(wpconn){
  this.wpconn = wpconn;
}

/**
 * Add method
 *
 * @param {String} id
 * @param {Object} params (optional)
 * @param {Function} fn
 * @api public
 */

Post.prototype.get = function(id, params, fn){
  // params || fn
  fn = fn || params;
  params = 'function' == typeof params ? {} : (params || {});

  // set endpoint
  var set = {
    site: this.wpconn.site._id,
    post_ID: id
  };

  this.wpconn.req.exec('post', set, params, fn);
};

/**
 * Add method
 *
 * @param {Object} data
 * @param {String} rid
 * @param {Object} params
 * @param {Function} fn
 * @api public
 */

Post.prototype.add = function(data, fn){
  // set endpoint
  var set = { site: this.wpconn.site._id };

  var params = {
    method: 'post',
    data: data
  };

  this.wpconn.req.exec('post_add', set, params, fn);
};

/**
 * Expose `Post` module
 */

module.exports = Post;
<|endoftext|>


<commit_before>'use babel'
/* global atom */

import { spawn } from 'child_process'
import { accessSync, X_OK } from 'fs'
import { homedir } from 'os'

const python = project => {
  if (!project) {
    return 'python'
  }
  const projectName = project.getBaseName()
  const cwd = project.getPath()
  return atom.config.get('python-import-magic.pythonPaths')
    .split(';')
    .find(pth => {
      try {
        pth = pth.replace(/\~/i, homedir())
          .replace(/\$PROJECT_NAME/i, projectName)
          .replace(/\$PROJECT/i, cwd)
        accessSync(pth, X_OK)
        return pth
      } catch (error) {
        return false
      }
    }) || 'python'
}

export default {
  call(in_, editor, callback, silent) {
    let err
    const encoding = editor.getBuffer().getEncoding()
    const filePath = editor.getBuffer().getPath()
    const project = atom.project.getDirectories().find(dir =>
      dir.contains(filePath)
    )
    if (project) {
      in_.cwd = project.getPath()
    }

    let out = err = ''
    const proc = spawn(
      python(project), [`${ __dirname }/import_magic_interface.py`])

    proc.stdout.on('data', data => out += data)
    proc.stderr.on('data', data => err += data)

    proc.on('error', error => {
      atom.notifications.addError(
        'Python Import Magic Invocation Error', { detail: out + err + error })
      console.error('Python Import Magic Invocation Error', out, err, error)
    })

    proc.on('close', function(code) {
      if (code === 0) {
        let o
        try {
          o = JSON.parse(out)
        } catch (error) {
          atom.notifications.addError(
            'Python Import Magic Invocation Erruor',
            { detail: out + err + error })
          console.error('Python Import Magic Error', error, out, err)
          return
        }
        if (o.notification) {
          const addNotification =
            `add${ o.notification[0].toUpperCase() }${
              o.notification.slice(1) }`
          if (!silent) {
            atom.notifications[addNotification](
              `[python-import-magic] ${ o.message }`, { detail: o.detail })
          }
        }
        callback && callback(o)
      } else {
        atom.notifications.addError(
            'Python Import Magic Invocation Error', { detail: out + err })
        console.error('Python Import Magic Error', code, out, err)
      }

    })
    proc.stdin.setEncoding(encoding)
    proc.stdin.write(JSON.stringify(in_))
    proc.stdin.end()
  }
}
<commit_msg>... i'm sorry...<commit_after>'use babel'
/* global atom */

import { spawn } from 'child_process'
import { accessSync, X_OK } from 'fs'
import { homedir } from 'os'

const python = project => {
  if (!project) {
    return 'python'
  }
  const projectName = project.getBaseName()
  const cwd = project.getPath()
  return atom.config.get('python-import-magic.pythonPaths')
    .split(';')
    .map(pth => {
      try {
        pth = pth.replace(/\~/i, homedir())
          .replace(/\$PROJECT_NAME/i, projectName)
          .replace(/\$PROJECT/i, cwd)
        accessSync(pth, X_OK)
        return pth
      } catch (error) {
        return false
      }
    })
    .find(pth => pth) || 'python'
}

export default {
  call(in_, editor, callback, silent) {
    let err
    const encoding = editor.getBuffer().getEncoding()
    const filePath = editor.getBuffer().getPath()
    const project = atom.project.getDirectories().find(dir =>
      dir.contains(filePath)
    )
    if (project) {
      in_.cwd = project.getPath()
    }

    let out = err = ''
    const proc = spawn(
      python(project), [`${ __dirname }/import_magic_interface.py`])

    proc.stdout.on('data', data => out += data)
    proc.stderr.on('data', data => err += data)

    proc.on('error', error => {
      atom.notifications.addError(
        'Python Import Magic Invocation Error', { detail: out + err + error })
      console.error('Python Import Magic Invocation Error', out, err, error)
    })

    proc.on('close', function(code) {
      if (code === 0) {
        let o
        try {
          o = JSON.parse(out)
        } catch (error) {
          atom.notifications.addError(
            'Python Import Magic Invocation Erruor',
            { detail: out + err + error })
          console.error('Python Import Magic Error', error, out, err)
          return
        }
        if (o.notification) {
          const addNotification =
            `add${ o.notification[0].toUpperCase() }${
              o.notification.slice(1) }`
          if (!silent) {
            atom.notifications[addNotification](
              `[python-import-magic] ${ o.message }`, { detail: o.detail })
          }
        }
        callback && callback(o)
      } else {
        atom.notifications.addError(
            'Python Import Magic Invocation Error', { detail: out + err })
        console.error('Python Import Magic Error', code, out, err)
      }

    })
    proc.stdin.setEncoding(encoding)
    proc.stdin.write(JSON.stringify(in_))
    proc.stdin.end()
  }
}
<|endoftext|>


<commit_before>"use strict";

var fs = require ("fs");
var path = require ("path");
var parse = require ("./parse");

var INCLUDE_KEY = "include";
var INDEX_FILE = "index.properties";

var cast = function (value){
	if (value === null || value === "null") return null;
	if (value === "undefined") return undefined;
	if (value === "true") return true;
	if (value === "false") return false;
	var v = Number (value);
	return isNaN (v) ? value : v;
};

var expand = function  (o, str, options, cb){
	if (!options.variables || !str) return cb (null, str);

	var stack = [];
	var c;
	var cp;
	var key = "";
	var section = null;
	var v;
	var holder;
	var t;
	var n;

	for (var i=0, ii=str.length; i<ii; i++){
		c = str[i];

		if (cp === "$" && c === "{"){
			key = key.substring (0, key.length - 1);
			stack.push ({
				key: key,
				section: section
			});
			key = "";
			section = null;
			continue;
		}else if (stack.length){
			if (options.sections && c === "|"){
				section = key;
				key = "";
				continue;
			}else if (c === "}"){
				holder = section !== null ? o[section] : o;
				if (!holder){
					return cb (new Error ("The section \"" + section + "\" does not " +
							"exist"));
				}

				v = holder[key];
				if (v === undefined){
					//Read the external vars
					if (options.namespaces){
						v = namespaceValue (options._vars, key);
					}else{
						v = options._vars[key];
					}

					if (v === undefined){
						return cb (new Error ("The property \"" + key + "\" does not " +
								"exist"));
					}
				}

				t = stack.pop ();
				section = t.section;
				key = t.key + (v === null ? "" : v);
				continue;
			}
		}

		cp = c;
		key += c;
	}

	if (stack.length !== 0){
		return cb (new Error ("Malformed variable: " + str));
	}

	cb (null, key);
};

var namespaceValue = function (p, key){
	var n = key.split (".");
	var str;

	for (var i=0, ii=n.length-1; i<ii; i++){
		str = n[i];
		if (p[str] === undefined) return;
		p = p[str];
	}

	return p[n[n.length - 1]];
};

var namespace = function (p, key, value){
	var n = key.split (".");
	var str;

	for (var i=0, ii=n.length-1; i<ii; i++){
		str = n[i];
		if (p[str] === undefined){
			p[str] = {};
		}else if (typeof p[str] !== "object"){
			return new Error ("Invalid namespace, the key must contain an " +
					"object: " + str);
		}
		p = p[str];
	}

	p[n[n.length - 1]] = value;
};

var merge = function (o1, o2){
	for (var p in o2){
		try{
			if (o1[p].constructor === Object){
				o1[p] = merge (o1[p], o2[p]);
			}else{
				o1[p] = o2[p];
			}
		}catch (e){
			o1[p] = o2[p];
		}
	}
	return o1;
}

var build = function (data, options, dirname, cb){
	var o = {};

	if (options.namespaces){
		var n = {};
	}

	var control = {
		abort: false,
		skipSection: false
	};

	if (options.include){
		var remainingIncluded = 0;

		var include = function (value){
			if (currentSection !== null){
				return abort (new Error ("Cannot include files from inside a " +
						"section: " + currentSection));
			}

			var p = path.resolve (dirname, value);
			if (options._included[p]) return;

			options._included[p] = true;
			remainingIncluded++;
			control.pause = true;

			read (p, options, function (error, included){
				if (error) return abort (error);

				remainingIncluded--;
				merge (options.namespaces ? n : o, included);
				control.pause = false;

				if (!control.parsed){
					parse (data, options, handlers, control);
					if (control.error) return abort (control.error);
				}

				if (!remainingIncluded) cb (null, options.namespaces ? n : o);
			});
		};
	}

	if (!data){
		if (cb) return cb (null, o);
		return o;
	}

	var currentSection = null;

	var abort = function (error){
		control.abort = true;
		if (cb) return cb (error);
		throw error;
	};

	var handlers = {};
	var reviver = {
		assert: function (){
			return this.isProperty ? reviverLine.value : true;
		}
	};
	var reviverLine = {};

	//Line handler
	//For speed reasons, if namespaces are enabled the old object is still
	//populated, e.g.: ${a.b} reads the "a.b" property from { "a.b": 1 }, instead
	//of having a unique object { a: { b: 1 } } which is slower to search for
	//the "a.b" value
	//If "a.b" is not found, then the external vars are read. If "namespaces" is
	//enabled the var "a.b" is split and it searches for a.b. If it is not enabled
	//then the var "a.b" searches for "a.b"

	var line;
	var error;

	if (options.reviver){
		if (options.sections){
			line = function (key, value){
				if (options.include && key === INCLUDE_KEY) return include (value);

				reviverLine.value = value;
				reviver.isProperty = true;
				reviver.isSection = false;

				value = options.reviver.call (reviver, key, value, currentSection);
				if (value !== undefined){
					if (currentSection === null) o[key] = value;
					else o[currentSection][key] = value;

					if (options.namespaces){
						error = namespace (currentSection === null ? n : n[currentSection],
								key, value);
						if (error) abort (error);
					}
				}
			};
		}else{
			line = function (key, value){
				if (options.include && key === INCLUDE_KEY) return include (value);

				reviverLine.value = value;
				reviver.isProperty = true;
				reviver.isSection = false;

				value = options.reviver.call (reviver, key, value);
				if (value !== undefined){
					o[key] = value;

					if (options.namespaces){
						error = namespace (n, key, value);
						if (error) abort (error);
					}
				}
			};
		}
	}else{
		if (options.sections){
			line = function (key, value){
				if (options.include && key === INCLUDE_KEY) return include (value);

				if (currentSection === null) o[key] = value;
				else o[currentSection][key] = value;

				if (options.namespaces){
					error = namespace (currentSection === null ? n : n[currentSection],
							key, value);
					if (error) abort (error);
				}
			};
		}else{
			line = function (key, value){
				if (options.include && key === INCLUDE_KEY) return include (value);

				o[key] = value;

				if (options.namespaces){
					error = namespace (n, key, value);
					if (error) abort (error);
				}
			};
		}
	}

	//Section handler
	var section;
	if (options.sections){
		if (options.reviver){
			section = function (section){
				reviverLine.section = section;
				reviver.isProperty = false;
				reviver.isSection = true;

				var add = options.reviver.call (reviver, null, null, section);
				if (add){
					currentSection = section;
					o[currentSection] = {};

					if (options.namespaces){
						n[currentSection] = {};
					}
				}else{
					control.skipSection = true;
				}
			};
		}else{
			section = function (section){
				currentSection = section;
				o[currentSection] = {};

				if (options.namespaces){
					n[currentSection] = {};
				}
			};
		}
	}

	//Variables
	if (options.variables){
		handlers.line = function (key, value){
			expand (o, key, options, function (error, key){
				if (error) return abort (error);

				expand (o, value, options, function (error, value){
					if (error) return abort (error);

					line (key, cast (value || null));
				});
			});
		};

		if (options.sections){
			handlers.section = function (s){
				expand (o, s, options, function (error, s){
					if (error) return abort (error);

					section (s);
				});
			};
		}
	}else{
		handlers.line = function (key, value){
			line (key, cast (value || null));
		};

		if (options.sections){
			handlers.section = section;
		}
	}

	parse (data, options, handlers, control);
	if (control.error) return abort (control.error);

	if (control.abort || control.pause) return;

	if (cb) return cb (null, options.namespaces ? n : o);
	return options.namespaces ? n : o;
};

var read = function (f, options, cb){
	fs.stat (f, function (error, stats){
		if (error) return cb (error);

		var dirname;

		if (stats.isDirectory ()){
			dirname = f;
			f = path.join (f, INDEX_FILE);
		}else{
			dirname = path.dirname (f);
		}

		fs.readFile (f, { encoding: "utf8" }, function (error, data){
			if (error) return cb (error);
			build (data, options, dirname, cb);
		});
	});
};

module.exports = function (data, options, cb){
	if (typeof options === "function"){
		cb = options;
		options = {};
	}

	options = options || {};
	var code;

	if (options.include){
		options._included = {};
	}

	options = options || {};
	options._strict = options.strict && (options.comments || options.separators);
	options._vars = options.vars || {};

	var comments = options.comments || [];
	if (!Array.isArray (comments)) comments = [comments];
	var c = {};
	comments.forEach (function (comment){
		code = comment.charCodeAt (0);
		if (comment.length > 1 || code < 33 || code > 126){
			throw new Error ("The comment token must be a single printable ASCII " +
					"character");
		}
		c[comment] = true;
	});
	options._comments = c;

	var separators = options.separators || [];
	if (!Array.isArray (separators)) separators = [separators];
	var s = {};
	separators.forEach (function (separator){
		code = separator.charCodeAt (0);
		if (separator.length > 1 || code < 33 || code > 126){
			throw new Error ("The separator token must be a single printable ASCII " +
					"character");
		}
		s[separator] = true;
	});
	options._separators = s;

	if (options.path){
		if (!cb) throw new Error ("A callback must be passed if the data is a " +
				"path");
		if (options.include){
			read (data, options, cb);
		}else{
			fs.readFile (data, { encoding: "utf8" }, function (error, data){
				if (error) return cb (error);
				build (data, options, ".", cb);
			});
		}
	}else{
		return build (data, options, ".", cb);
	}
};<commit_msg>better error message<commit_after>"use strict";

var fs = require ("fs");
var path = require ("path");
var parse = require ("./parse");

var INCLUDE_KEY = "include";
var INDEX_FILE = "index.properties";

var cast = function (value){
	if (value === null || value === "null") return null;
	if (value === "undefined") return undefined;
	if (value === "true") return true;
	if (value === "false") return false;
	var v = Number (value);
	return isNaN (v) ? value : v;
};

var expand = function  (o, str, options, cb){
	if (!options.variables || !str) return cb (null, str);

	var stack = [];
	var c;
	var cp;
	var key = "";
	var section = null;
	var v;
	var holder;
	var t;
	var n;

	for (var i=0, ii=str.length; i<ii; i++){
		c = str[i];

		if (cp === "$" && c === "{"){
			key = key.substring (0, key.length - 1);
			stack.push ({
				key: key,
				section: section
			});
			key = "";
			section = null;
			continue;
		}else if (stack.length){
			if (options.sections && c === "|"){
				section = key;
				key = "";
				continue;
			}else if (c === "}"){
				holder = section !== null ? o[section] : o;
				if (!holder){
					return cb (new Error ("The section \"" + section + "\" does not " +
							"exist"));
				}

				v = holder[key];
				if (v === undefined){
					//Read the external vars
					if (options.namespaces){
						v = namespaceValue (options._vars, key);
					}else{
						v = options._vars[key];
					}

					if (v === undefined){
						return cb (new Error ("The property \"" + key + "\" does not " +
								"exist"));
					}
				}

				t = stack.pop ();
				section = t.section;
				key = t.key + (v === null ? "" : v);
				continue;
			}
		}

		cp = c;
		key += c;
	}

	if (stack.length !== 0){
		return cb (new Error ("Malformed variable: " + str));
	}

	cb (null, key);
};

var namespaceValue = function (p, key){
	var n = key.split (".");
	var str;

	for (var i=0, ii=n.length-1; i<ii; i++){
		str = n[i];
		if (p[str] === undefined) return;
		p = p[str];
	}

	return p[n[n.length - 1]];
};

var namespace = function (p, key, value){
	var n = key.split (".");
	var str;

	for (var i=0, ii=n.length-1; i<ii; i++){
		str = n[i];
		if (p[str] === undefined){
			p[str] = {};
		}else if (typeof p[str] !== "object"){
			return new Error ("Invalid namespace chain, the key must contain an " +
					"object: " + str);
		}
		p = p[str];
	}

	p[n[n.length - 1]] = value;
};

var merge = function (o1, o2){
	for (var p in o2){
		try{
			if (o1[p].constructor === Object){
				o1[p] = merge (o1[p], o2[p]);
			}else{
				o1[p] = o2[p];
			}
		}catch (e){
			o1[p] = o2[p];
		}
	}
	return o1;
}

var build = function (data, options, dirname, cb){
	var o = {};

	if (options.namespaces){
		var n = {};
	}

	var control = {
		abort: false,
		skipSection: false
	};

	if (options.include){
		var remainingIncluded = 0;

		var include = function (value){
			if (currentSection !== null){
				return abort (new Error ("Cannot include files from inside a " +
						"section: " + currentSection));
			}

			var p = path.resolve (dirname, value);
			if (options._included[p]) return;

			options._included[p] = true;
			remainingIncluded++;
			control.pause = true;

			read (p, options, function (error, included){
				if (error) return abort (error);

				remainingIncluded--;
				merge (options.namespaces ? n : o, included);
				control.pause = false;

				if (!control.parsed){
					parse (data, options, handlers, control);
					if (control.error) return abort (control.error);
				}

				if (!remainingIncluded) cb (null, options.namespaces ? n : o);
			});
		};
	}

	if (!data){
		if (cb) return cb (null, o);
		return o;
	}

	var currentSection = null;

	var abort = function (error){
		control.abort = true;
		if (cb) return cb (error);
		throw error;
	};

	var handlers = {};
	var reviver = {
		assert: function (){
			return this.isProperty ? reviverLine.value : true;
		}
	};
	var reviverLine = {};

	//Line handler
	//For speed reasons, if namespaces are enabled the old object is still
	//populated, e.g.: ${a.b} reads the "a.b" property from { "a.b": 1 }, instead
	//of having a unique object { a: { b: 1 } } which is slower to search for
	//the "a.b" value
	//If "a.b" is not found, then the external vars are read. If "namespaces" is
	//enabled the var "a.b" is split and it searches for a.b. If it is not enabled
	//then the var "a.b" searches for "a.b"

	var line;
	var error;

	if (options.reviver){
		if (options.sections){
			line = function (key, value){
				if (options.include && key === INCLUDE_KEY) return include (value);

				reviverLine.value = value;
				reviver.isProperty = true;
				reviver.isSection = false;

				value = options.reviver.call (reviver, key, value, currentSection);
				if (value !== undefined){
					if (currentSection === null) o[key] = value;
					else o[currentSection][key] = value;

					if (options.namespaces){
						error = namespace (currentSection === null ? n : n[currentSection],
								key, value);
						if (error) abort (error);
					}
				}
			};
		}else{
			line = function (key, value){
				if (options.include && key === INCLUDE_KEY) return include (value);

				reviverLine.value = value;
				reviver.isProperty = true;
				reviver.isSection = false;

				value = options.reviver.call (reviver, key, value);
				if (value !== undefined){
					o[key] = value;

					if (options.namespaces){
						error = namespace (n, key, value);
						if (error) abort (error);
					}
				}
			};
		}
	}else{
		if (options.sections){
			line = function (key, value){
				if (options.include && key === INCLUDE_KEY) return include (value);

				if (currentSection === null) o[key] = value;
				else o[currentSection][key] = value;

				if (options.namespaces){
					error = namespace (currentSection === null ? n : n[currentSection],
							key, value);
					if (error) abort (error);
				}
			};
		}else{
			line = function (key, value){
				if (options.include && key === INCLUDE_KEY) return include (value);

				o[key] = value;

				if (options.namespaces){
					error = namespace (n, key, value);
					if (error) abort (error);
				}
			};
		}
	}

	//Section handler
	var section;
	if (options.sections){
		if (options.reviver){
			section = function (section){
				reviverLine.section = section;
				reviver.isProperty = false;
				reviver.isSection = true;

				var add = options.reviver.call (reviver, null, null, section);
				if (add){
					currentSection = section;
					o[currentSection] = {};

					if (options.namespaces){
						n[currentSection] = {};
					}
				}else{
					control.skipSection = true;
				}
			};
		}else{
			section = function (section){
				currentSection = section;
				o[currentSection] = {};

				if (options.namespaces){
					n[currentSection] = {};
				}
			};
		}
	}

	//Variables
	if (options.variables){
		handlers.line = function (key, value){
			expand (o, key, options, function (error, key){
				if (error) return abort (error);

				expand (o, value, options, function (error, value){
					if (error) return abort (error);

					line (key, cast (value || null));
				});
			});
		};

		if (options.sections){
			handlers.section = function (s){
				expand (o, s, options, function (error, s){
					if (error) return abort (error);

					section (s);
				});
			};
		}
	}else{
		handlers.line = function (key, value){
			line (key, cast (value || null));
		};

		if (options.sections){
			handlers.section = section;
		}
	}

	parse (data, options, handlers, control);
	if (control.error) return abort (control.error);

	if (control.abort || control.pause) return;

	if (cb) return cb (null, options.namespaces ? n : o);
	return options.namespaces ? n : o;
};

var read = function (f, options, cb){
	fs.stat (f, function (error, stats){
		if (error) return cb (error);

		var dirname;

		if (stats.isDirectory ()){
			dirname = f;
			f = path.join (f, INDEX_FILE);
		}else{
			dirname = path.dirname (f);
		}

		fs.readFile (f, { encoding: "utf8" }, function (error, data){
			if (error) return cb (error);
			build (data, options, dirname, cb);
		});
	});
};

module.exports = function (data, options, cb){
	if (typeof options === "function"){
		cb = options;
		options = {};
	}

	options = options || {};
	var code;

	if (options.include){
		options._included = {};
	}

	options = options || {};
	options._strict = options.strict && (options.comments || options.separators);
	options._vars = options.vars || {};

	var comments = options.comments || [];
	if (!Array.isArray (comments)) comments = [comments];
	var c = {};
	comments.forEach (function (comment){
		code = comment.charCodeAt (0);
		if (comment.length > 1 || code < 33 || code > 126){
			throw new Error ("The comment token must be a single printable ASCII " +
					"character");
		}
		c[comment] = true;
	});
	options._comments = c;

	var separators = options.separators || [];
	if (!Array.isArray (separators)) separators = [separators];
	var s = {};
	separators.forEach (function (separator){
		code = separator.charCodeAt (0);
		if (separator.length > 1 || code < 33 || code > 126){
			throw new Error ("The separator token must be a single printable ASCII " +
					"character");
		}
		s[separator] = true;
	});
	options._separators = s;

	if (options.path){
		if (!cb) throw new Error ("A callback must be passed if the data is a " +
				"path");
		if (options.include){
			read (data, options, cb);
		}else{
			fs.readFile (data, { encoding: "utf8" }, function (error, data){
				if (error) return cb (error);
				build (data, options, ".", cb);
			});
		}
	}else{
		return build (data, options, ".", cb);
	}
};<|endoftext|>


<commit_before>var fs = require("fs"),
	util = require("util"),
	events = require("events"),
	redis = require("redis"),
	async = require("async"),
	md = require("node-markdown").Markdown;

//singleton to enable events.
//user code interacts with it through exports.on method.
function Reed() { }
util.inherits(Reed, events.EventEmitter);

var reed = new Reed();

//the redis client.
var client;

//directory to look for markdown files in.
//set by exports.open
var dir;

//if we are open, further calls to open() will be rejected.
//if we are ready, allow calls to the methods.
var open = false;
var ready = false;

function endsWith(str, text) {
	return str.substr(-text.length) == text;
}

function redisInsert(key, date, processedData, post, callback) {
	processedData = JSON.stringify(processedData);
	client.zadd("blog:dates", date, key, function(err) {
		client.hset(key, "metadata", processedData, function() {
			client.hset(key, "post", post, callback);
		});
	});
}

//taken from wheat -- MIT license
function preProcess(markdown) {
	if (!(typeof markdown === 'string')) {
		markdown = markdown.toString();
	}

	var props = {};

	// Parse out headers
	var match;
	while(match = markdown.match(/^([a-z]+):\s*(.*)\s*\n/i)) {
		var name = match[1].toLowerCase(),
		value = match[2];
		markdown = markdown.substr(match[0].length);
		props[name] = value;
	}
	props.markdown = markdown;

	return props;
}

function readMarkdown(directory, callback) {
	fs.readdir(directory, function(err, files) {
		if (typeof files === "undefined") {
			callback(err);
			return;
		}

		files = files.filter(function(el, i, arr) {
			return endsWith(el, ".md");
		});

		for (var c = 0; c < files.length; c++) {
			var file = files[c];
			file = directory + "/" + file;
			files[c] = file;
		}

		callback(null, files);
	});
}

var watched = {};
function loadFromFilesystem(filename, callback) {
	fs.readFile(filename, function(err, data) {
		if (typeof(data) === "undefined") {
			if (typeof callback !== "undefined") callback(err);
			return;
		}

		var processed = preProcess(data.toString());
		var post = md(processed.markdown);

		var postDate = fs.statSync(filename).mtime.getTime();
		processed.lastModified = postDate;

		redisInsert("blog:" + filename, postDate, processed, post, function() {
			if (watched[filename] !== true) {
				reed.emit("add", processed, post);
			}

			watch(filename);

			if (typeof callback !== "undefined")
				callback(null, processed, post);
		});
	});
}

function watch(filename) {
	if (watched[filename] !== true) {
		watched[filename] = true;
		fs.watchFile(filename, function(curr, prev) {
			loadFromFilesystem(filename, function(err, metadata, htmlContent) {
				if (err != null) {
					reed.emit("error", err);
				}
				else {
					reed.emit("update", metadata, htmlContent);
				}
			});
		});
	}
}

function findNewFiles(firstTime) {
	readMarkdown(dir, function(err, files) {
		if (typeof files === "undefined") {
			reed.emit("error", err);
			return;
		}

		var c = 0;
		files.forEach(function(file) {
			if (watched[file] !== true) {
				//new file?
				reed.getMetadata(file, function(err, md) {
					fs.stat(file, function(err, stats) {
						if (err != null) {
							reed.emit("error", err);
							return;
						}

						if (md.lastModified !== stats.mtime.getTime()) {
							//new file!
							loadFromFilesystem(file, function(err) {
								if (err != null) {
									console.log("add error: " + err);
								}

								//emit ready event if we are done.
								if (firstTime && c + 1 === files.length) {
									ready = true;
									reed.emit("ready");
								}
								c++;
							});
						}
						else {
							watch(file);
							//emit ready event if we are done.
							if (firstTime && c + 1 === files.length) {
								ready = true;
								reed.emit("ready");
							}
							c++;
						}
					});
				});
			}
		}); //end foreach
	});
}

function toFilename(title) {
	var filename = title.replace(" ", "-");
	if (!endsWith(filename, ".md")) filename += ".md";
	return filename;
}

function toKey(title) {
	var filename = title.replace(" ", "-");
	if (!endsWith(filename, ".md")) filename += ".md";
	return "blog:" + filename;
}

function readyCheck(fn, args) {
	if (ready) {
		return true;
	}
	else {
		process.nextTick(function() {
			fn.apply(module, args);
		});

		return false;
	}
}

reed.getMetadata = function(title, callback) {
	//convert to key/filename
	var filename = toFilename(title);
	var key = toKey(title);

	//first hit redis
	client.hget(key, "metadata", function(err, metadata) {
		metadata = JSON.parse(metadata);
		if (typeof(metadata) !== "undefined" && metadata != null && Object.keys(metadata).length > 0) {
			if (typeof callback !== "undefined")
				callback(null, metadata);
		}
		else {
			//now hit filesystem.
			loadFromFilesystem(filename, callback);
		}
	});
}

reed.get = function get(title, callback) {
	if (!readyCheck(get, arguments)) return;

	//convert to key/filename
	var filename = toFilename(title);
	var key = toKey(title);

	//first hit redis
	client.hgetall(key, function(err, hash) {
		var post = hash.post;
		if (typeof(hash) !== "undefined" && Object.keys(hash).length > 0) {
			if (typeof callback !== "undefined")
				callback(null, JSON.parse(hash.metadata), hash.post);
		}
		else {
			//now hit filesystem.
			loadFromFilesystem(filename, callback);
		}
	});
}

reed.index = function index(callback) {
	if (!readyCheck(index, arguments)) return;

	readMarkdown(dir, function(err, files) {
		if (typeof files === "undefined") {
			callback(err);
			return;
		}

		var mdFilter = function(el, i, arr) {
			return endsWith(el, ".md");
		}

		var c = 0;
		var skip = false;
		files = files.filter(mdFilter);
		for (var x = 0; x < files.length; x++) {
			if (skip) break; //probably shouldn't hit this, but who knows.
			var file = files[x];
			loadFromFilesystem(file, function(err) {
				if (skip) return; //SHOULD hit this though.
				if (err != null) {
					if (typeof(callback) !== "undefined") {
						skip = true;
						callback(err);
						return;
					}
				}
				else {
					if (c + 1 == files.length) {
						if (typeof(callback) !== "undefined")
							callback(null);
						return;
					}

					c++;
				}
			});
		}
	});
}

reed.list = function list(callback) {
	if (!readyCheck(list, arguments)) return;

	var titles = [];
	client.zrevrange("blog:dates", 0, -1, function(err, keys) {
		keys.forEach(function(key) {
			//5 = "blog:", length - 3 = ".md"
			var title = key.substring(5, key.length - 3);
			title = title.replace("-", " ");
			titles.push(title);
		});

		if (typeof callback !== "undefined")
			callback(null, titles);
	});
}

reed.remove = function remove(title, callback) {
	if (!readyCheck(remove, arguments)) return;

	var key = toKey(title);
	client.del(key, function(err) {
		client.zrem("blog:dates", key, function(err) {
			callback(null);
		});
	});
}

reed.removeAll = function removeAll(callback) {
	if (!readyCheck(removeAll, arguments)) return;

	function delegate(key) {
		return function(callback) {
			client.del(key, function(err) {
				if (err != null) callback(err);
				else callback(null);
			});
		}
	}

	var funcs = [];
	for (file in watched) {
		funcs.push(delegate(toKey(file)));
	}

	funcs.push(delegate("blog:dates"));

	async.waterfall(funcs, function(err) {
		if (!err) {
			callback(null);
		}
		else {
			callback(err);
		}
	});
}

reed.open = function(directory) {
	if (open === true) {
		throw new Error("reed already open on " + dir);
	}
	else {
		open = true;
	}

	if (typeof(directory) !== "string") {
		throw new Error("Must specify directory to read from");
	}

	dir = directory;
	client = redis.createClient();

	fs.watchFile(dir, function(curr, prev) {
		findNewFiles();
	});

	process.nextTick(function() {
		findNewFiles(true);
	});
}

reed.close = function() {
	client.quit();

	for (file in watched) {
		if (watched[file] === true) {
			fs.unwatchFile(file);
		}
	}

	watched = {};
	fs.unwatchFile(dir);
	open = false;
	ready = false;
}

//Export an event emitting object that also has the various control methods
//on it.
module.exports = reed;
<commit_msg>Fixed #4 for real<commit_after>var fs = require("fs"),
	util = require("util"),
	events = require("events"),
	redis = require("redis"),
	async = require("async"),
	md = require("node-markdown").Markdown;

//singleton to enable events.
//user code interacts with it through exports.on method.
function Reed() { }
util.inherits(Reed, events.EventEmitter);

var reed = new Reed();

//the redis client.
var client;

//directory to look for markdown files in.
//set by exports.open
var dir;

//if we are open, further calls to open() will be rejected.
//if we are ready, allow calls to the methods.
var open = false;
var ready = false;

function endsWith(str, text) {
	return str.substr(-text.length) == text;
}

function redisInsert(key, date, processedData, post, callback) {
	processedData = JSON.stringify(processedData);
	client.zadd("blog:dates", date, key, function(err) {
		client.hset(key, "metadata", processedData, function() {
			client.hset(key, "post", post, callback);
		});
	});
}

//taken from wheat -- MIT license
function preProcess(markdown) {
	if (!(typeof markdown === 'string')) {
		markdown = markdown.toString();
	}

	var props = {};

	// Parse out headers
	var match;
	while(match = markdown.match(/^([a-z]+):\s*(.*)\s*\n/i)) {
		var name = match[1].toLowerCase(),
		value = match[2];
		markdown = markdown.substr(match[0].length);
		props[name] = value;
	}
	props.markdown = markdown;

	return props;
}

function readMarkdown(directory, callback) {
	fs.readdir(directory, function(err, files) {
		if (typeof files === "undefined") {
			callback(err);
			return;
		}

		files = files.filter(function(el, i, arr) {
			return endsWith(el, ".md");
		});

		callback(null, files);
	});
}

var watched = {};
function loadFromFilesystem(filename, callback) {
	fs.readFile(filename, function(err, data) {
		if (typeof(data) === "undefined") {
			if (typeof callback !== "undefined") callback(err);
			return;
		}

		var processed = preProcess(data.toString());
		var post = md(processed.markdown);

		var postDate = fs.statSync(filename).mtime.getTime();
		processed.lastModified = postDate;

		redisInsert("blog:" + filename, postDate, processed, post, function() {
			if (watched[filename] !== true) {
				reed.emit("add", processed, post);
			}

			watch(filename);

			if (typeof callback !== "undefined")
				callback(null, processed, post);
		});
	});
}

function watch(filename) {
	if (watched[filename] !== true) {
		watched[filename] = true;
		fs.watchFile(filename, function(curr, prev) {
			loadFromFilesystem(filename, function(err, metadata, htmlContent) {
				if (err != null) {
					reed.emit("error", err);
				}
				else {
					reed.emit("update", metadata, htmlContent);
				}
			});
		});
	}
}

function findNewFiles(firstTime) {
	readMarkdown(dir, function(err, files) {
		if (typeof files === "undefined") {
			reed.emit("error", err);
			return;
		}

		var c = 0;
		files.forEach(function(file) {
			if (watched[file] !== true) {
				//new file?
				file = toFilename(file);
				reed.getMetadata(file, function(err, md) {
					fs.stat(file, function(err, stats) {
						if (err != null) {
							reed.emit("error", err);
							return;
						}

						if (md.lastModified !== stats.mtime.getTime()) {
							//new file!
							loadFromFilesystem(toFilename(file), function(err) {
								if (err != null) {
									console.log("add error: " + err);
								}

								//emit ready event if we are done.
								if (firstTime && c + 1 === files.length) {
									ready = true;
									reed.emit("ready");
								}
								c++;
							});
						}
						else {
							watch(file);
							//emit ready event if we are done.
							if (firstTime && c + 1 === files.length) {
								ready = true;
								reed.emit("ready");
							}
							c++;
						}
					});
				});
			}
		}); //end foreach
	});
}

function toFilename(title) {
	var filename = title.replace(" ", "-");
	if (!endsWith(filename, ".md")) filename += ".md";
	filename = dir + "/" + filename;
	return filename;
}

function toKey(title) {
	var filename = title.replace(" ", "-");
	if (!endsWith(filename, ".md")) filename += ".md";
	return "blog:" + filename;
}

function readyCheck(fn, args) {
	if (ready) {
		return true;
	}
	else {
		process.nextTick(function() {
			fn.apply(module, args);
		});

		return false;
	}
}

reed.getMetadata = function(title, callback) {
	//convert to key/filename
	var filename = toFilename(title);
	var key = toKey(title);

	//first hit redis
	client.hget(key, "metadata", function(err, metadata) {
		metadata = JSON.parse(metadata);
		if (typeof(metadata) !== "undefined" && metadata != null && Object.keys(metadata).length > 0) {
			if (typeof callback !== "undefined")
				callback(null, metadata);
		}
		else {
			//now hit filesystem.
			loadFromFilesystem(filename, callback);
		}
	});
}

reed.get = function get(title, callback) {
	if (!readyCheck(get, arguments)) return;

	//convert to key/filename
	var filename = toFilename(title);
	var key = toKey(title);

	//first hit redis
	client.hgetall(key, function(err, hash) {
		var post = hash.post;
		if (typeof(hash) !== "undefined" && Object.keys(hash).length > 0) {
			if (typeof callback !== "undefined")
				callback(null, JSON.parse(hash.metadata), hash.post);
		}
		else {
			//now hit filesystem.
			loadFromFilesystem(filename, callback);
		}
	});
}

reed.index = function index(callback) {
	if (!readyCheck(index, arguments)) return;

	readMarkdown(dir, function(err, files) {
		if (typeof files === "undefined") {
			callback(err);
			return;
		}

		var mdFilter = function(el, i, arr) {
			return endsWith(el, ".md");
		}

		var c = 0;
		var skip = false;
		files = files.filter(mdFilter);
		for (var x = 0; x < files.length; x++) {
			if (skip) break; //probably shouldn't hit this, but who knows.
			var file = files[x];
			loadFromFilesystem(file, function(err) {
				if (skip) return; //SHOULD hit this though.
				if (err != null) {
					if (typeof(callback) !== "undefined") {
						skip = true;
						callback(err);
						return;
					}
				}
				else {
					if (c + 1 == files.length) {
						if (typeof(callback) !== "undefined")
							callback(null);
						return;
					}

					c++;
				}
			});
		}
	});
}

reed.list = function list(callback) {
	if (!readyCheck(list, arguments)) return;

	var titles = [];
	client.zrevrange("blog:dates", 0, -1, function(err, keys) {
		keys.forEach(function(key) {
			//5 = "blog:", length - 3 = ".md"
			var title = key.substring(5, key.length - 3);
			title = title.replace("-", " ");
			titles.push(title);
		});

		if (typeof callback !== "undefined")
			callback(null, titles);
	});
}

reed.remove = function remove(title, callback) {
	if (!readyCheck(remove, arguments)) return;

	var key = toKey(title);
	client.del(key, function(err) {
		client.zrem("blog:dates", key, function(err) {
			callback(null);
		});
	});
}

reed.removeAll = function removeAll(callback) {
	if (!readyCheck(removeAll, arguments)) return;

	function delegate(key) {
		return function(callback) {
			client.del(key, function(err) {
				if (err != null) callback(err);
				else callback(null);
			});
		}
	}

	var funcs = [];
	for (file in watched) {
		funcs.push(delegate(toKey(file)));
	}

	funcs.push(delegate("blog:dates"));

	async.waterfall(funcs, function(err) {
		if (!err) {
			callback(null);
		}
		else {
			callback(err);
		}
	});
}

reed.open = function(directory) {
	if (open === true) {
		throw new Error("reed already open on " + dir);
	}
	else {
		open = true;
	}

	if (typeof(directory) !== "string") {
		throw new Error("Must specify directory to read from");
	}

	dir = directory;
	client = redis.createClient();

	fs.watchFile(dir, function(curr, prev) {
		findNewFiles();
	});

	process.nextTick(function() {
		findNewFiles(true);
	});
}

reed.close = function() {
	client.quit();

	for (file in watched) {
		if (watched[file] === true) {
			fs.unwatchFile(file);
		}
	}

	watched = {};
	fs.unwatchFile(dir);
	open = false;
	ready = false;
}

//Export an event emitting object that also has the various control methods
//on it.
module.exports = reed;
<|endoftext|>