diff --git a/QCore.js b/QCore.js new file mode 100644 index 0000000..5864e28 --- /dev/null +++ b/QCore.js @@ -0,0 +1,261 @@ +/********************************* + * QCore * + *********************************/ +var http = require('http'); +var fs = require('fs'); +var m_path = require('path'); +var formidable = require('formidable'); + +var QCore = function() { + var acts = {}; + this.acts = acts; + self = this; + this.mimetypes = {}; + this.index = null; + this.index_parts = []; + this.last_part = { start: 0, end: 0 }; + + this.etags = {}; // etags cache, obj: uri { etag: '...', timestamp: } + + this.res_cache = {}; // resource cache ('/view.png' => '03xc345'); + this.rules = {}; + + this.http = http.createServer(function(req, res) { + var last = req.url.lastIndexOf("/"); + var act = req.url.substr(last+1); + if (acts[act]) { + req.url = req.url.substr(0, last); // rewrite the url without the act + } else { + act = ""; + } + req.fields = {}; + req.files = {}; + if (req.method == 'POST') { + var form = new formidable.IncomingForm(); + form.multiples = true; + form.uploadDir = process.cwd()+'/tmp'; + form.parse(req, function(err, fields, files) { + // TODO: handle err + req.fields = fields; + req.files = files; + for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) { + acts[act][i](req, res); + } + }); + return; + } else { + for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) { + acts[act][i](req, res); + } + } + }); + + this.act = function(act, callback) { + if (typeof acts[act] === 'undefined') { + acts[act] = []; + } + acts[act].push(callback); + }; + this.rule = function(zone, replace, callback) { + if (typeof this.rules[zone] === 'undefined') { + this.rules[zone] = {}; + } + this.rules[zone][replace] = callback; + }; + var fs_timeout; // timer to bypass multiple fs.watch triggering due to unstable API + this.loadIndex = function(event, filename) { + if (fs_timeout) return; + fs_timeout = setTimeout(function() { fs_timeout = null; }, 100); + var reg = /@@[a-z]+@@/ig; + fs.readFile('index.html', function(err, data) { + if (err) throw err; + self.index = data; + var search; + self.index_parts = []; + while ((search = reg.exec(data)) !== null) { + self.index_parts.push({ replace: search[0], start: search.index, end: reg.lastIndex }); + } + }); + }; + // TODO: loadActs should allow for dynamic updates by using fs.watch and clearing the require cache + this.loadActs = function(cb) { + var self = this; + fs.readdir('acts', function(err, files) { + if (err) throw err; + for (var i = 0; i < files.length; i++) { + var ext = self.getExt(files[i]); + if (ext == 'js') { + console.log('Loading act: ' + files[i]); + require('./acts/'+files[i])(self); + } + } + (typeof cb !== 'undefined' ? cb() : ''); + }); + }; + fs.watch('index.html', this.loadIndex); +}; +QCore.prototype.defaults = {}; +QCore.prototype.listen = function(port, cb) { + this.loadActs(); + this.loadIndex(); + if (typeof cb !== 'undefined') cb(); + try { + this.http.listen(port); + } catch(err) { + console.log('uhoh, could not listen on port '+port); + console.log(err); + } +}; +QCore.prototype.setDefault = function(key, value) { + this.defaults[key] = value; +}; +QCore.prototype.getDefault = function(key) { + if (key in this.defaults) { + return this.defaults[key]; + } + return ''; +}; +QCore.prototype.addMIMEtype = function(ext, mimetype) { + if (typeof this.mimetypes[ext] === 'undefined') this.mimetypes[ext] = []; + this.mimetypes[ext].push(mimetype); +}; +QCore.prototype.getMIMEtype = function(ext) { + if (this.mimetypes[ext]) return this.mimetypes[ext][0]; + return ''; +}; +QCore.prototype.parsePage = function(zone, fallback_zone, req, res) { + var qparse = new QCore.prototype.QParse(); + qparse.indices = this.index_parts.slice(); // clone it TODO: just make an indices (number-only) array + + var context = this; + var cb = function(qparse, callback) { + res.write(context.index.slice(qparse.offset, qparse.current.start)); + if (typeof context.rules[zone] === 'undefined') { zone = fallback_zone; } + if (typeof context.rules[zone] === 'undefined') { zone = ''; } + if (typeof context.rules[zone] !== 'undefined') { + if (typeof context.rules[zone][qparse.current.replace] !== 'undefined') { + context.rules[zone][qparse.current.replace](req, res, qparse, callback); + } else { + // fallback action + if (typeof context.rules[fallback_zone][qparse.current.replace] !== 'undefined') { + context.rules[fallback_zone][qparse.current.replace](req, res, qparse, callback); + // default action + } else if (typeof context.rules[''][qparse.current.replace] !== 'undefined') { + context.rules[''][qparse.current.replace](req, res, qparse, callback); + } else { + callback(); + } + } + } + }; + + var series = function(qparse) { + if (qparse.current) { + cb(qparse, function(next) { + if (qparse.next) { + res.write(context.index.slice(qparse.current.end, qparse.next.start)); + qparse.offset = qparse.next.end; + qparse.current = qparse.indices.shift(); + qparse.next = qparse.indices[0]; + series(qparse); + } else { + res.write(context.index.slice(qparse.current.end)); + qparse.current = qparse.indices.shift(); + qparse.next = qparse.indices[0]; + series(qparse); + } + }); + } else { + res.end(); + } + }; + qparse.current = qparse.indices.shift(); + qparse.next = qparse.indices[0]; + series(qparse); +}; + +QCore.prototype.readFile = function(stream, path, cb) { + fs.exists(path, function(exists) { + if (exists) { + var rs = fs.createReadStream(path); + rs.on('data', function(chunk) { + stream.write(chunk); + }); + rs.on('end', function() { + cb('EOF'); + }); + rs.on('error', function(err) { + cb('ERR', err); + }); + } else { + cb('FNF'); // file not found + } + }); +}; +QCore.prototype.readFiles = function(dir, callback) { + fs.readdir(dir, function(err, files) { + files.forEach(function(file) { + var stat_file = dir+'/'+file + fs.stat(stat_file, function(err, stats) { + if (stats.isDirectory()) { + QCore.prototype.readFiles(stat_file, callback); + callback(err, stat_file, true); + } else if (stats.isFile()) { + callback(err, stat_file, false); + } + }); + }); + }); +}; +QCore.prototype.readDirectory = function(dir, callback) { + var all_files = []; + fs.readdir(dir, function(err, files) { + if (err) { + callback(err, null); + return; + } + var len = files.length; + var total = 0; + if (len == 0) callback(err, all_files); + files.forEach(function(file, index) { + var stat_file = dir + '/' + file; + fs.stat(stat_file, function(err, stats) { + if (stats.isDirectory()) { + all_files.push({'is_dir': true, 'name': file}); + } else if (stats.isFile()) { + all_files.push({'is_dir': false, 'name': file}); + } + if (++total == len) { + callback(err, all_files); + } + }); + }); + }); +}; +QCore.prototype.getExt = function(file) { + if (typeof file === 'undefined') return ''; + var last = file.lastIndexOf("."); + if (last == -1) return ''; + var ext = file.substr(last+1); + return ext; +}; +QCore.prototype.r_mkdir = function(dir, mode, cb) { + console.log('creating '+dir); + fs.mkdir(dir, mode, function(err) { + if (err && err.code == 'ENOENT') { + QCore.r_mkdir(m_path.dirname(dir), mode, QCore.r_mkdir.bind(this, dir, mode, cb)); + } else if (typeof cb !== 'undefined') { + cb(err); + } + }); +}; +/********************************* + * QParse * + *********************************/ +QCore.prototype.QParse = function() {}; +QCore.prototype.QParse.prototype.indices = {}; +QCore.prototype.QParse.prototype.offset = 0; +QCore.prototype.QParse.prototype.current = null; +QCore.prototype.QParse.prototype.next = null; + +module.exports = QCore; diff --git a/index.html b/index.html index 4ddabd5..bfcec48 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - + qwiki @@PAGE@@ diff --git a/index.js b/index.js index 1808b72..e277020 100644 --- a/index.js +++ b/index.js @@ -1,133 +1,47 @@ #!/usr/bin/env node -var http = require('http'); var fs = require('fs'); var m_path = require('path'); var util = require('util'); -var formidable = require('formidable'); +var QCore = require('./QCore.js'); /* qwiki Node.js based Implementation of the proposed qwiki document */ -/********************************* - * QCore * +/********************************* + * QWiki * *********************************/ -var QCore = function() { - var acts = {}; - this.acts = acts; - self = this; - this.mimetypes = {}; - this.index = null; - this.index_parts = []; - this.last_part = { start: 0, end: 0 }; - - this.etags = {}; // etags cache, obj: uri { etag: '...', timestamp: } - - this.res_cache = {}; // resource cache ('/view.png' => '03xc345'); - this.rules = {}; - - this.http = http.createServer(function(req, res) { - var last = req.url.lastIndexOf("/"); - var act = req.url.substr(last+1); - if (acts[act]) { - req.url = req.url.substr(0, last); // rewrite the url without the act +var QWiki = function() { + QCore.call(this); + // make cache directory + try { + var stats = fs.statSync('cache'); + if (!stats.isDirectory()) { + console.log('error, "cache" is not a directory'); + } + } catch (err) { + if (err.code == 'ENOENT') { + fs.mkdirSync('cache'); + console.log('Created "cache/" -- this is where processed wiki pages are stored as HTML'); } else { - act = ""; + console.log('error while loading cache'); } - req.fields = {}; - req.files = {}; - if (req.method == 'POST') { - var form = new formidable.IncomingForm(); - form.multiples = true; - form.uploadDir = process.cwd()+'/tmp'; - form.parse(req, function(err, fields, files) { - // TODO: handle err - req.fields = fields; - req.files = files; - for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) { - acts[act][i](req, res); - } - }); - return; + } + // make tmp directory + try { + var stats = fs.statSync('tmp'); + if (!stats.isDirectory()) { + console.log('error, "tmp" is not a directory'); + } + } catch (err) { + if (err.code == 'ENOENT') { + fs.mkdirSync('tmp'); + console.log('Created "tmp/" -- this is where uploaded files are temporarily stored'); } else { - for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) { - acts[act][i](req, res); - } + console.log('error while loading tmp directory'); } - }); + } - this.act = function(act, callback) { - if (typeof acts[act] === 'undefined') { - acts[act] = []; - } - acts[act].push(callback); - }; - this.rule = function(zone, replace, callback) { - if (typeof this.rules[zone] === 'undefined') { - this.rules[zone] = {}; - } - this.rules[zone][replace] = callback; - }; - var fs_timeout; // timer to bypass multiple fs.watch triggering due to unstable API - this.loadIndex = function(event, filename) { - if (fs_timeout) return; - fs_timeout = setTimeout(function() { fs_timeout = null; }, 100); - var reg = /@@[a-z]+@@/ig; - fs.readFile('index.html', function(err, data) { - if (err) throw err; - self.index = data; - var search; - self.index_parts = []; - while ((search = reg.exec(data)) !== null) { - self.index_parts.push({ replace: search[0], start: search.index, end: reg.lastIndex }); - } - }); - }; - // TODO: loadActs should allow for dynamic updates by using fs.watch and clearing the require cache - this.loadActs = function(cb) { - var self = this; - fs.readdir('acts', function(err, files) { - if (err) throw err; - for (var i = 0; i < files.length; i++) { - var ext = self.getExt(files[i]); - if (ext == 'js') { - console.log('Loading act: ' + files[i]); - require('./acts/'+files[i])(self); - } - } - (typeof cb !== 'undefined' ? cb() : ''); - }); - }; - fs.watch('index.html', this.loadIndex); -}; -QCore.prototype.defaults = {}; -QCore.prototype.listen = function(port) { - this.loadActs(); - this.loadIndex(); - this.loadWikiIndex(); - try { - this.http.listen(port); - } catch(err) { - console.log('uhoh, could not listen on port '+port); - console.log(err); - } -}; -QCore.prototype.setDefault = function(key, value) { - this.defaults[key] = value; -}; -QCore.prototype.getDefault = function(key) { - if (key in this.defaults) { - return this.defaults[key]; - } - return ''; -}; -QCore.prototype.addMIMEtype = function(ext, mimetype) { - if (typeof this.mimetypes[ext] === 'undefined') this.mimetypes[ext] = []; - this.mimetypes[ext].push(mimetype); -}; -QCore.prototype.getMIMEtype = function(ext) { - if (this.mimetypes[ext]) return this.mimetypes[ext][0]; - return ''; -}; -QCore.prototype.loadWikiIndex = function() { +}; util.inherits(QWiki, QCore); +QWiki.prototype.loadWikiIndex = function() { var self = this; try { this.wiki_index = JSON.parse(fs.readFileSync('index.json')); @@ -137,7 +51,7 @@ QCore.prototype.loadWikiIndex = function() { } var wiki_index = this.wiki_index; console.log('loaded indices for ' + Object.keys(this.wiki_index.pages).length + ' pages'); - readFiles('wiki', function(err, file, is_dir) { + QCore.prototype.readFiles('wiki', function(err, file, is_dir) { var wiki_file = file.replace('wiki/', ''); if (is_dir) { if (wiki_file in wiki_index.pages) { @@ -146,7 +60,7 @@ QCore.prototype.loadWikiIndex = function() { wiki_index.pages[wiki_file] = { dir: true }; } } else { - if (self.getExt(file) == 'qwk') { + if (QCore.prototype.getExt(file) == 'qwk') { if (wiki_file in wiki_index.pages) { } else { var name = wiki_file.slice(0, -4); @@ -161,83 +75,6 @@ QCore.prototype.loadWikiIndex = function() { } }); } -QCore.prototype.parsePage = function(zone, fallback_zone, req, res) { - var qparse = new QParse(); - qparse.indices = this.index_parts.slice(); // clone it TODO: just make an indices (number-only) array - - var context = this; - var cb = function(qparse, callback) { - res.write(context.index.slice(qparse.offset, qparse.current.start)); - if (typeof context.rules[zone] === 'undefined') { zone = fallback_zone; } - if (typeof context.rules[zone] === 'undefined') { zone = ''; } - if (typeof context.rules[zone] !== 'undefined') { - if (typeof context.rules[zone][qparse.current.replace] !== 'undefined') { - context.rules[zone][qparse.current.replace](req, res, qparse, callback); - } else { - // fallback action - if (typeof context.rules[fallback_zone][qparse.current.replace] !== 'undefined') { - context.rules[fallback_zone][qparse.current.replace](req, res, qparse, callback); - // default action - } else if (typeof context.rules[''][qparse.current.replace] !== 'undefined') { - context.rules[''][qparse.current.replace](req, res, qparse, callback); - } else { - callback(); - } - } - } - }; - - var series = function(qparse) { - if (qparse.current) { - cb(qparse, function(next) { - if (qparse.next) { - res.write(context.index.slice(qparse.current.end, qparse.next.start)); - qparse.offset = qparse.next.end; - qparse.current = qparse.indices.shift(); - qparse.next = qparse.indices[0]; - series(qparse); - } else { - res.write(context.index.slice(qparse.current.end)); - qparse.current = qparse.indices.shift(); - qparse.next = qparse.indices[0]; - series(qparse); - } - }); - } else { - res.end(); - } - }; - qparse.current = qparse.indices.shift(); - qparse.next = qparse.indices[0]; - series(qparse); -}; -/********************************* - * QParse * - *********************************/ -var QParse = function() {}; -QParse.prototype.indices = {}; -QParse.prototype.offset = 0; -QParse.prototype.current = null; -QParse.prototype.next = null; -/********************************* - * QWiki * - *********************************/ -var QWiki = function() { - QCore.call(this); - try { - var stats = fs.statSync('cache'); - if (!stats.isDirectory()) { - console.log('error, "cache" is not a directory'); - } - } catch (err) { - if (err.code == 'ENOENT') { - fs.mkdirSync('cache'); - console.log('Created "cache/" -- this is where processed wiki pages are stored as HTML'); - } else { - console.log('error while loading cache'); - } - } -}; util.inherits(QWiki, QCore); QWiki.prototype.formats = { raw: { name: 'raw', @@ -270,8 +107,8 @@ QWiki.prototype.convertAndSave = function(path, name, source, cb) { } console.log('converting to ' + name + ' with path ' + path); var converted = this.formats[name].convert(source); - r_mkdir(m_path.dirname('cache'+path+'.html'), 0777, function() { - fs.writeFile('cache'+path+'.html', converted, function(err) { + QCore.prototype.r_mkdir(m_path.dirname('cache/'+path+'.html'), 0777, function() { + fs.writeFile('cache/'+path+'.html', converted, function(err) { if (err) { console.log('error writing "cache'+ path + '.html"'); } else { @@ -299,7 +136,7 @@ QWiki.prototype.createCache = function(wiki_page, source, cb) { var path = 'cache/' + wiki_page + '.html'; - r_mkdir(m_path.dirname(path), 0777, function() { + QCore.prototype.r_mkdir(m_path.dirname(path), 0777, function() { fs.writeFile(path, converted, function(err) { if (err) { console.log('createCache: error writing "' + path + '"'); @@ -315,7 +152,7 @@ QWiki.prototype.deletePage = function(wiki_page, cb) { }; QWiki.prototype.savePage = function(wiki_page, content, cb) { var path = 'wiki/' + wiki_page + '.qwk'; - r_mkdir(m_path.dirname(path), 0777, function() { + QCore.prototype.r_mkdir(m_path.dirname(path), 0777, function() { console.log('savePage: writing ' + path); qwiki.deleteCache(wiki_page, function() { fs.writeFile(path, content, function(err) { @@ -328,7 +165,6 @@ QWiki.prototype.savePage = function(wiki_page, content, cb) { QWiki.prototype.deleteFile = function(file, cb) { fs.unlink('wiki/'+file, cb); }; - /********************************* * qwiki instance * *********************************/ @@ -354,88 +190,6 @@ qwiki.addFormat('md', 'Markdown', function(source) { qwiki.setDefault('format', 'md'); -var readFile = function(stream, path, cb) { - fs.exists(path, function(exists) { - if (exists) { - var rs = fs.createReadStream(path); - rs.on('data', function(chunk) { - stream.write(chunk); - }); - rs.on('end', function() { - cb('EOF'); - }); - rs.on('error', function(err) { - cb('ERR', err); - }); - } else { - cb('FNF'); // file not found - } - }); -}; -QCore.prototype.readFile = readFile; - -var readFiles = function(dir, callback) { - fs.readdir(dir, function(err, files) { - files.forEach(function(file) { - var stat_file = dir+'/'+file - fs.stat(stat_file, function(err, stats) { - if (stats.isDirectory()) { - readFiles(stat_file, callback); - callback(err, stat_file, true); - } else if (stats.isFile()) { - callback(err, stat_file, false); - } - }); - }); - }); -}; - -QCore.prototype.readDirectory = function(dir, callback) { - var all_files = []; - fs.readdir(dir, function(err, files) { - if (err) { - callback(err, null); - return; - } - var len = files.length; - var total = 0; - if (len == 0) callback(err, all_files); - files.forEach(function(file, index) { - var stat_file = dir + '/' + file; - fs.stat(stat_file, function(err, stats) { - if (stats.isDirectory()) { - all_files.push({'is_dir': true, 'name': file}); - } else if (stats.isFile()) { - all_files.push({'is_dir': false, 'name': file}); - } - if (++total == len) { - callback(err, all_files); - } - }); - }); - }); -}; - -QCore.prototype.getExt = function(file) { - if (typeof file === 'undefined') return ''; - var last = file.lastIndexOf("."); - if (last == -1) return ''; - var ext = file.substr(last+1); - return ext; -}; - -var r_mkdir = function(dir, mode, cb) { - console.log('creating '+dir); - fs.mkdir(dir, mode, function(err) { - if (err && err.code == 'ENOENT') { - r_mkdir(m_path.dirname(dir), mode, r_mkdir.bind(this, dir, mode, cb)); - } else if (typeof cb !== 'undefined') { - cb(err); - } - }); -}; -QCore.prototype.r_mkdir = r_mkdir; - var port = 8080; process.argv.forEach(function (val, index, array) { var parts = val.split('='); @@ -454,4 +208,4 @@ process.argv.forEach(function (val, index, array) { } }); -qwiki.listen(port); +qwiki.listen(port, function() { qwiki.loadWikiIndex(); });