diff options
author | Samy Pessé <samypesse@gmail.com> | 2016-02-26 09:41:26 +0100 |
---|---|---|
committer | Samy Pessé <samypesse@gmail.com> | 2016-02-26 09:41:26 +0100 |
commit | d3d64f636c859f7f01a64f7774cf70bd8ccdc562 (patch) | |
tree | 4f7731f37c3a793d187b0ab1cd77680e69534c6c /lib/utils | |
parent | 4cb9cbb5ae3aa8f9211ffa3ac5e3d34232c0ca4f (diff) | |
parent | eef072693b17526347c37b66078a5059c71caa31 (diff) | |
download | gitbook-d3d64f636c859f7f01a64f7774cf70bd8ccdc562.zip gitbook-d3d64f636c859f7f01a64f7774cf70bd8ccdc562.tar.gz gitbook-d3d64f636c859f7f01a64f7774cf70bd8ccdc562.tar.bz2 |
Merge pull request #1109 from GitbookIO/3.0.0
Version 3.0.0
Diffstat (limited to 'lib/utils')
-rw-r--r-- | lib/utils/batch.js | 52 | ||||
-rw-r--r-- | lib/utils/command.js | 80 | ||||
-rw-r--r-- | lib/utils/error.js | 105 | ||||
-rw-r--r-- | lib/utils/fs.js | 225 | ||||
-rw-r--r-- | lib/utils/git.js | 168 | ||||
-rw-r--r-- | lib/utils/i18n.js | 80 | ||||
-rw-r--r-- | lib/utils/images.js | 71 | ||||
-rw-r--r-- | lib/utils/location.js (renamed from lib/utils/links.js) | 49 | ||||
-rw-r--r-- | lib/utils/logger.js | 188 | ||||
-rw-r--r-- | lib/utils/navigation.js | 79 | ||||
-rw-r--r-- | lib/utils/page.js | 397 | ||||
-rw-r--r-- | lib/utils/path.js | 36 | ||||
-rw-r--r-- | lib/utils/progress.js | 55 | ||||
-rw-r--r-- | lib/utils/promise.js | 62 | ||||
-rw-r--r-- | lib/utils/server.js | 94 | ||||
-rw-r--r-- | lib/utils/string.js | 27 | ||||
-rw-r--r-- | lib/utils/watch.js | 40 |
17 files changed, 599 insertions, 1209 deletions
diff --git a/lib/utils/batch.js b/lib/utils/batch.js deleted file mode 100644 index 9069766..0000000 --- a/lib/utils/batch.js +++ /dev/null @@ -1,52 +0,0 @@ -var Q = require("q"); -var _ = require("lodash"); - -// Execute a method for all element -function execEach(items, options) { - if (_.size(items) === 0) return Q(); - var concurrents = 0, d = Q.defer(), pending = []; - - options = _.defaults(options || {}, { - max: 100, - fn: function() {} - }); - - - function startItem(item, i) { - if (concurrents >= options.max) { - pending.push([item, i]); - return; - } - - concurrents++; - Q() - .then(function() { - return options.fn(item, i); - }) - .then(function() { - concurrents--; - - // Next pending - var next = pending.shift(); - - if (concurrents === 0 && !next) { - d.resolve(); - } else if (next) { - startItem.apply(null, next); - } - }) - .fail(function(err) { - pending = []; - d.reject(err); - }); - } - - _.each(items, startItem); - - return d.promise; -} - -module.exports = { - execEach: execEach -}; - diff --git a/lib/utils/command.js b/lib/utils/command.js new file mode 100644 index 0000000..de240df --- /dev/null +++ b/lib/utils/command.js @@ -0,0 +1,80 @@ +var _ = require('lodash'); +var childProcess = require('child_process'); +var spawn = require("spawn-cmd").spawn; +var Promise = require('./promise'); + +// Execute a command +function exec(command, options) { + var d = Promise.defer(); + + var child = childProcess.exec(command, options, function(err, stdout, stderr) { + if (!err) { + return d.resolve(); + } + + err.message = stdout.toString('utf8') + stderr.toString('utf8'); + d.reject(err); + }); + + child.stdout.on('data', function (data) { + d.notify(data); + }); + + child.stderr.on('data', function (data) { + d.notify(data); + }); + + return d.promise; +} + +// Spawn an executable +function spawnCmd(command, args, options) { + var d = Promise.defer(); + var child = spawn(command, args, options); + + child.on('error', function(error) { + return d.reject(error); + }); + + child.stdout.on('data', function (data) { + d.notify(data); + }); + + child.stderr.on('data', function (data) { + d.notify(data); + }); + + child.on('close', function(code) { + if (code === 0) { + d.resolve(); + } else { + d.reject(new Error('Error with command "'+command+'"')); + } + }); + + return d.promise; +} + +// Transform an option object to a command line string +function escapeShellArg(s) { + s = s.replace(/"/g, '\\"'); + return '"' + s + '"'; +} + +function optionsToShellArgs(options) { + return _.chain(options) + .map(function(value, key) { + if (value === null || value === undefined || value === false) return null; + if (value === true) return key; + return key + '=' + escapeShellArg(value); + }) + .compact() + .value() + .join(' '); +} + +module.exports = { + exec: exec, + spawn: spawnCmd, + optionsToShellArgs: optionsToShellArgs +}; diff --git a/lib/utils/error.js b/lib/utils/error.js new file mode 100644 index 0000000..27fa59d --- /dev/null +++ b/lib/utils/error.js @@ -0,0 +1,105 @@ +var _ = require('lodash'); +var TypedError = require('error/typed'); +var WrappedError = require('error/wrapped'); +var deprecated = require('deprecated'); + +var Logger = require('./logger'); + +var log = new Logger(); + +// Enforce as an Error object, and cleanup message +function enforce(err) { + if (_.isString(err)) err = new Error(err); + err.message = err.message.replace(/^Error: /, ''); + + return err; +} + +// Random error wrappers during parsing/generation +var ParsingError = WrappedError({ + message: 'Parsing Error: {origMessage}', + type: 'parse' +}); +var OutputError = WrappedError({ + message: 'Output Error: {origMessage}', + type: 'generate' +}); + +// A file does not exists +var FileNotFoundError = TypedError({ + type: 'file.not-found', + message: 'No "{filename}" file (or is ignored)', + filename: null +}); + +// A file is outside the scope +var FileOutOfScopeError = TypedError({ + type: 'file.out-of-scope', + message: '"{filename}" not in "{root}"', + filename: null, + root: null, + code: 'EACCESS' +}); + +// A file is outside the scope +var RequireInstallError = TypedError({ + type: 'install.required', + message: '"{cmd}" is not installed.\n{install}', + cmd: null, + code: 'ENOENT', + install: '' +}); + +// Error for nunjucks templates +var TemplateError = WrappedError({ + message: 'Error compiling template "{filename}": {origMessage}', + type: 'template', + filename: null +}); + +// Error for nunjucks templates +var PluginError = WrappedError({ + message: 'Error with plugin "{plugin}": {origMessage}', + type: 'plugin', + plugin: null +}); + +// Error with the book's configuration +var ConfigurationError = WrappedError({ + message: 'Error with book\'s configuration: {origMessage}', + type: 'configuration' +}); + +// Error during ebook generation +var EbookError = WrappedError({ + message: 'Error during ebook generation: {origMessage}\n{stdout}', + type: 'ebook', + stdout: '' +}); + +// Deprecate methods/fields +function deprecateMethod(fn, msg) { + return deprecated.method(msg, log.warn.ln, fn); +} +function deprecateField(obj, prop, value, msg) { + return deprecated.field(msg, log.warn.ln, obj, prop, value); +} + +module.exports = { + enforce: enforce, + + ParsingError: ParsingError, + OutputError: OutputError, + RequireInstallError: RequireInstallError, + + FileNotFoundError: FileNotFoundError, + FileOutOfScopeError: FileOutOfScopeError, + + TemplateError: TemplateError, + PluginError: PluginError, + ConfigurationError: ConfigurationError, + EbookError: EbookError, + + deprecateMethod: deprecateMethod, + deprecateField: deprecateField +}; diff --git a/lib/utils/fs.js b/lib/utils/fs.js index b82701f..42fd3c6 100644 --- a/lib/utils/fs.js +++ b/lib/utils/fs.js @@ -1,71 +1,36 @@ -var _ = require('lodash'); -var Q = require('q'); +var fs = require('graceful-fs'); +var mkdirp = require('mkdirp'); +var destroy = require('destroy'); +var rmdir = require('rmdir'); var tmp = require('tmp'); +var request = require('request'); var path = require('path'); -var fs = require('graceful-fs'); -var fsExtra = require('fs-extra'); -var Ignore = require('fstream-ignore'); - -var fsUtils = { - tmp: { - file: function(opt) { - return Q.nfcall(tmp.file.bind(tmp), opt).get(0); - }, - dir: function() { - return Q.nfcall(tmp.dir.bind(tmp)).get(0); - } - }, - list: listFiles, - stat: Q.denodeify(fs.stat), - readdir: Q.denodeify(fs.readdir), - readFile: Q.denodeify(fs.readFile), - writeFile: writeFile, - writeStream: writeStream, - mkdirp: Q.denodeify(fsExtra.mkdirp), - copy: Q.denodeify(fsExtra.copy), - remove: Q.denodeify(fsExtra.remove), - symlink: Q.denodeify(fsExtra.symlink), - exists: function(path) { - var d = Q.defer(); - fs.exists(path, d.resolve); - return d.promise; - }, - findFile: findFile, - existsSync: fs.existsSync.bind(fs), - readFileSync: fs.readFileSync.bind(fs), - clean: cleanFolder, - getUniqueFilename: getUniqueFilename -}; - -// Write a file -function writeFile(filename, data, options) { - var d = Q.defer(); - - try { - fs.writeFileSync(filename, data, options); - } catch(err) { - d.reject(err); - } - d.resolve(); - +var cp = require('cp'); +var cpr = require('cpr'); - return d.promise; -} +var Promise = require('./promise'); // Write a stream to a file function writeStream(filename, st) { - var d = Q.defer(); + var d = Promise.defer(); var wstream = fs.createWriteStream(filename); + var cleanup = function() { + destroy(wstream); + wstream.removeAllListeners(); + }; wstream.on('finish', function () { + cleanup(); d.resolve(); }); wstream.on('error', function (err) { + cleanup(); d.reject(err); }); st.on('error', function(err) { + cleanup(); d.reject(err); }); @@ -74,120 +39,80 @@ function writeStream(filename, st) { return d.promise; } -// Find a filename available -function getUniqueFilename(base, filename) { - if (!filename) { - filename = base; - base = '/'; - } +// Return a promise resolved with a boolean +function fileExists(filename) { + var d = Promise.defer(); - filename = path.resolve(base, filename); + fs.exists(filename, function(exists) { + d.resolve(exists); + }); + + return d.promise; +} + +// Generate temporary file +function genTmpFile(opts) { + return Promise.nfcall(tmp.file, opts) + .get(0); +} + +// Generate temporary dir +function genTmpDir(opts) { + return Promise.nfcall(tmp.dir, opts) + .get(0); +} + +// Download an image +function download(uri, dest) { + return writeStream(dest, request(uri)); +} + +// Find a filename available in a folder +function uniqueFilename(base, filename) { var ext = path.extname(filename); + filename = path.resolve(base, filename); filename = path.join(path.dirname(filename), path.basename(filename, ext)); var _filename = filename+ext; var i = 0; while (fs.existsSync(filename)) { - _filename = filename+'_'+i+ext; + _filename = filename + '_' + i + ext; i = i + 1; } - return path.relative(base, _filename); -} - - -// List files in a directory -function listFiles(root, options) { - options = _.defaults(options || {}, { - ignoreFiles: [], - ignoreRules: [] - }); - - var d = Q.defer(); - - // Our list of files - var files = []; - - var ig = Ignore({ - path: root, - ignoreFiles: options.ignoreFiles - }); - - // Add extra rules to ignore common folders - ig.addIgnoreRules(options.ignoreRules, '__custom_stuff'); - - // Push each file to our list - ig.on('child', function (c) { - files.push( - c.path.substr(c.root.path.length + 1) + (c.props.Directory === true ? '/' : '') - ); - }); - - ig.on('end', function() { - // Normalize paths on Windows - if(process.platform === 'win32') { - return d.resolve(files.map(function(file) { - return file.replace(/\\/g, '/'); - })); - } - - // Simply return paths otherwise - return d.resolve(files); - }); - - ig.on('error', d.reject); - - return d.promise; + return Promise(path.relative(base, _filename)); } -// Clean a folder without removing .git and .svn -// Creates it if non existant -function cleanFolder(root) { - if (!fs.existsSync(root)) return fsUtils.mkdirp(root); - - return listFiles(root, { - ignoreFiles: [], - ignoreRules: [ - // Skip Git and SVN stuff - '.git/', - '.svn/' - ] - }) - .then(function(files) { - var d = Q.defer(); - - _.reduce(files, function(prev, file, i) { - return prev.then(function() { - var _file = path.join(root, file); - - d.notify({ - i: i+1, - count: files.length, - file: _file - }); - return fsUtils.remove(_file); - }); - }, Q()) - .then(function() { - d.resolve(); - }, function(err) { - d.reject(err); - }); - - return d.promise; - }); +// Create all required folder to create a file +function ensureFile(filename) { + var base = path.dirname(filename); + return Promise.nfcall(mkdirp, base); } -// Find a file in a folder (case incensitive) -// Return the real filename -function findFile(root, filename) { - return Q.nfcall(fs.readdir, root) - .then(function(files) { - return _.find(files, function(file) { - return (file.toLowerCase() == filename.toLowerCase()); - }); +// Remove a folder +function rmDir(base) { + return Promise.nfcall(rmdir, base, { + fs: fs }); } -module.exports = fsUtils; +module.exports = { + exists: fileExists, + existsSync: fs.existsSync, + mkdirp: Promise.nfbind(mkdirp), + readFile: Promise.nfbind(fs.readFile), + writeFile: Promise.nfbind(fs.writeFile), + stat: Promise.nfbind(fs.stat), + statSync: fs.statSync, + readdir: Promise.nfbind(fs.readdir), + writeStream: writeStream, + copy: Promise.nfbind(cp), + copyDir: Promise.nfbind(cpr), + tmpFile: genTmpFile, + tmpDir: genTmpDir, + download: download, + uniqueFilename: uniqueFilename, + ensure: ensureFile, + rmDir: rmDir +}; diff --git a/lib/utils/git.js b/lib/utils/git.js index 72c8818..52b1096 100644 --- a/lib/utils/git.js +++ b/lib/utils/git.js @@ -1,127 +1,129 @@ -var Q = require('q'); var _ = require('lodash'); var path = require('path'); var crc = require('crc'); -var exec = Q.denodeify(require('child_process').exec); var URI = require('urijs'); -var pathUtil = require('./path'); +var pathUtil = require('./path'); +var Promise = require('./promise'); +var command = require('./command'); var fs = require('./fs'); var GIT_PREFIX = 'git+'; -var GIT_TMP = null; - - -// Check if an url is a git dependency url -function checkGitUrl(giturl) { - return (giturl.indexOf(GIT_PREFIX) === 0); -} -// Validates a SHA in hexadecimal -function validateSha(str) { - return (/[0-9a-f]{40}/).test(str); +function Git() { + this.tmpDir; + this.cloned = {}; } -// Parse and extract infos -function parseGitUrl(giturl) { - var ref, uri, fileParts, filepath; - - if (!checkGitUrl(giturl)) return null; - giturl = giturl.slice(GIT_PREFIX.length); - - uri = new URI(giturl); - ref = uri.fragment() || 'master'; - uri.fragment(null); - - // Extract file inside the repo (after the .git) - fileParts =uri.path().split('.git'); - filepath = fileParts.length > 1? fileParts.slice(1).join('.git') : ''; - if (filepath[0] == '/') filepath = filepath.slice(1); - - // Recreate pathname without the real filename - uri.path(_.first(fileParts)+'.git'); +// Return an unique ID for a combinaison host/ref +Git.prototype.repoID = function(host, ref) { + return crc.crc32(host+'#'+(ref || '')).toString(16); +}; - return { - host: uri.toString(), - ref: ref || 'master', - filepath: filepath - }; -} +// Allocate a temporary folder for cloning repos in it +Git.prototype.allocateDir = function() { + var that = this; -// Clone a git repo from a specific ref -function cloneGitRepo(host, ref) { - var isBranch = false; + if (this.tmpDir) return Promise(); - ref = ref || 'master'; - if (!validateSha(ref)) isBranch = true; + return fs.tmpDir() + .then(function(dir) { + that.tmpDir = dir; + }); +}; - return Q() +// Clone a git repository if non existant +Git.prototype.clone = function(host, ref) { + var that = this; - // Create temporary folder to store git repos - .then(function() { - if (GIT_TMP) return; - return fs.tmp.dir() - .then(function(_tmp) { - GIT_TMP = _tmp; - }); - }) + return this.allocateDir() // Return or clone the git repo .then(function() { // Unique ID for repo/ref combinaison - var repoId = crc.crc32(host+'#'+ref).toString(16); + var repoId = that.repoID(host, ref); // Absolute path to the folder - var repoPath = path.resolve(GIT_TMP, repoId); - - return fs.exists(repoPath) - .then(function(doExists) { - if (doExists) return; - - // Clone repo - return exec('git clone '+host+' '+repoPath) - .then(function() { - return exec('git checkout '+ref, { cwd: repoPath }); - }); - }) - .thenResolve(repoPath); + var repoPath = path.join(that.tmpDir, repoId); + + if (that.cloned[repoId]) return repoPath; + + // Clone repo + return command.exec('git clone '+host+' '+repoPath) + + // Checkout reference if specified + .then(function() { + that.cloned[repoId] = true; + + if (!ref) return; + return command.exec('git checkout '+ref, { cwd: repoPath }); + }) + .thenResolve(repoPath); }); -} +}; // Get file from a git repo -function resolveFileFromGit(giturl) { - if (_.isString(giturl)) giturl = parseGitUrl(giturl); - if (!giturl) return Q(null); +Git.prototype.resolve = function(giturl) { + // Path to a file in a git repo? + if (!Git.isUrl(giturl)) { + if (this.resolveRoot(giturl)) return Promise(giturl); + return Promise(null); + } + if (_.isString(giturl)) giturl = Git.parseUrl(giturl); + if (!giturl) return Promise(null); // Clone or get from cache - return cloneGitRepo(giturl.host, giturl.ref) + return this.clone(giturl.host, giturl.ref) .then(function(repo) { - - // Resolve relative path return path.resolve(repo, giturl.filepath); }); -} +}; // Return root of git repo from a filepath -function resolveGitRoot(filepath) { +Git.prototype.resolveRoot = function(filepath) { var relativeToGit, repoId; // No git repo cloned, or file is not in a git repository - if (!GIT_TMP || !pathUtil.isInRoot(GIT_TMP, filepath)) return null; + if (!this.tmpDir || !pathUtil.isInRoot(this.tmpDir, filepath)) return null; // Extract first directory (is the repo id) - relativeToGit = path.relative(GIT_TMP, filepath); + relativeToGit = path.relative(this.tmpDir, filepath); repoId = _.first(relativeToGit.split(path.sep)); if (!repoId) return; // Return an absolute file - return path.resolve(GIT_TMP, repoId); -} + return path.resolve(this.tmpDir, repoId); +}; + +// Check if an url is a git dependency url +Git.isUrl = function(giturl) { + return (giturl.indexOf(GIT_PREFIX) === 0); +}; + +// Parse and extract infos +Git.parseUrl = function(giturl) { + var ref, uri, fileParts, filepath; + if (!Git.isUrl(giturl)) return null; + giturl = giturl.slice(GIT_PREFIX.length); + + uri = new URI(giturl); + ref = uri.fragment() || null; + uri.fragment(null); + + // Extract file inside the repo (after the .git) + fileParts = uri.path().split('.git'); + filepath = fileParts.length > 1? fileParts.slice(1).join('.git') : ''; + if (filepath[0] == '/') filepath = filepath.slice(1); -module.exports = { - checkUrl: checkGitUrl, - parseUrl: parseGitUrl, - resolveFile: resolveFileFromGit, - resolveRoot: resolveGitRoot + // Recreate pathname without the real filename + uri.path(_.first(fileParts)+'.git'); + + return { + host: uri.toString(), + ref: ref, + filepath: filepath + }; }; + +module.exports = Git; diff --git a/lib/utils/i18n.js b/lib/utils/i18n.js deleted file mode 100644 index de64b49..0000000 --- a/lib/utils/i18n.js +++ /dev/null @@ -1,80 +0,0 @@ -var _ = require('lodash'); -var path = require('path'); -var fs = require('fs'); - -var i18n = require('i18n'); - -var I18N_PATH = path.resolve(__dirname, '../../theme/i18n/'); -var DEFAULT_LANGUAGE = 'en'; -var LOCALES = _.map(fs.readdirSync(I18N_PATH), function(lang) { - return path.basename(lang, '.json'); -}); - -i18n.configure({ - locales: LOCALES, - directory: I18N_PATH, - defaultLocale: DEFAULT_LANGUAGE, - updateFiles: false -}); - -function compareLocales(lang, locale) { - var langMain = _.first(lang.split('-')); - var langSecond = _.last(lang.split('-')); - - var localeMain = _.first(locale.split('-')); - var localeSecond = _.last(locale.split('-')); - - if (locale == lang) return 100; - if (localeMain == langMain) return 50; - if (localeSecond == langSecond) return 20; - return 0; -} - -var normalizeLanguage = _.memoize(function(lang) { - var language = _.chain(LOCALES) - .values() - .map(function(locale) { - return { - locale: locale, - score: compareLocales(lang, locale) - }; - }) - .filter(function(lang) { - return lang.score > 0; - }) - .sortBy('score') - .pluck('locale') - .last() - .value(); - return language || lang; -}); - -function translate(locale, phrase) { - var args = Array.prototype.slice.call(arguments, 2); - - return i18n.__.apply({}, [{ - locale: locale, - phrase: phrase - }].concat(args)); -} - -function getCatalog(locale) { - locale = normalizeLanguage(locale); - return i18n.getCatalog(locale); -} - -function getLocales() { - return LOCALES; -} - -function hasLocale(locale) { - return _.contains(LOCALES, locale); -} - -module.exports = { - __: translate, - normalizeLanguage: normalizeLanguage, - getCatalog: getCatalog, - getLocales: getLocales, - hasLocale: hasLocale -}; diff --git a/lib/utils/images.js b/lib/utils/images.js index a82b0a1..e387d6b 100644 --- a/lib/utils/images.js +++ b/lib/utils/images.js @@ -1,37 +1,44 @@ -var _ = require("lodash"); -var Q = require("q"); -var fs = require("./fs"); -var spawn = require("spawn-cmd").spawn; - -// Convert a svg file -var convertSVG = function(source, dest, options) { - if (!fs.existsSync(source)) return Q.reject(new Error("File doesn't exist: "+source)); - var d = Q.defer(); - - options = _.defaults(options || {}, { - - }); - - //var command = shellescape(["svgexport", source, dest]); - var child = spawn("svgexport", [source, dest]); +var Promise = require('./promise'); +var command = require('./command'); +var fs = require('./fs'); +var error = require('./error'); + +// Convert a svg file to a pmg +function convertSVGToPNG(source, dest, options) { + if (!fs.existsSync(source)) return Promise.reject(new error.FileNotFoundError({ filename: source })); + + return command.spawn('svgexport', [source, dest]) + .fail(function(err) { + if (err.code == 'ENOENT') { + err = error.RequireInstallError({ + cmd: 'svgexport', + install: 'Install it using: "npm install svgexport -g"' + }); + } + throw err; + }) + .then(function() { + if (fs.existsSync(dest)) return; - child.on("error", function(error) { - if (error.code == "ENOENT") error = new Error("Need to install \"svgexport\" using \"npm install svgexport -g\""); - return d.reject(error); + throw new Error('Error converting '+source+' into '+dest); }); - - child.on("close", function(code) { - if (code === 0 && fs.existsSync(dest)) { - d.resolve(); - } else { - d.reject(new Error("Error converting "+source+" into "+dest)); - } +} + +// Convert a svg buffer to a png file +function convertSVGBufferToPNG(buf, dest) { + // Create a temporary SVG file to convert + return fs.tmpFile({ + postfix: '.svg' + }) + .then(function(tmpSvg) { + return fs.writeFile(tmpSvg, buf) + .then(function() { + return convertSVGToPNG(tmpSvg, dest); + }); }); - - return d.promise; -}; +} module.exports = { - convertSVG: convertSVG, - INVALID: [".svg"] -}; + convertSVGToPNG: convertSVGToPNG, + convertSVGBufferToPNG: convertSVGBufferToPNG +};
\ No newline at end of file diff --git a/lib/utils/links.js b/lib/utils/location.js index 5122396..ba0c57d 100644 --- a/lib/utils/links.js +++ b/lib/utils/location.js @@ -1,7 +1,7 @@ var url = require('url'); var path = require('path'); -// Is the link an external link +// Is the url an external url function isExternal(href) { try { return Boolean(url.parse(href).protocol); @@ -10,15 +10,9 @@ function isExternal(href) { } } -// Return true if the link is relative +// Inverse of isExternal function isRelative(href) { - try { - var parsed = url.parse(href); - - return !!(!parsed.protocol && parsed.path); - } catch(err) { - return true; - } + return !isExternal(href); } // Return true if the link is an achor @@ -32,15 +26,20 @@ function isAnchor(href) { } // Normalize a path to be a link -function normalizeLink(s) { +function normalize(s) { return s.replace(/\\/g, '/'); } -// Relative to absolute path +// Convert relative to absolute path // dir: directory parent of the file currently in rendering process // outdir: directory parent from the html output function toAbsolute(_href, dir, outdir) { if (isExternal(_href)) return _href; + outdir = outdir == undefined? dir : outdir; + + _href = normalize(_href); + dir = normalize(dir); + outdir = normalize(outdir); // Path "_href" inside the base folder var hrefInRoot = path.normalize(path.join(dir, _href)); @@ -50,32 +49,22 @@ function toAbsolute(_href, dir, outdir) { _href = path.relative(outdir, hrefInRoot); // Normalize windows paths - _href = normalizeLink(_href); + _href = normalize(_href); return _href; } -// Join links -function join() { - var _href = path.join.apply(path, arguments); - - return normalizeLink(_href); -}; - -// Change extension -function changeExtension(filename, newext) { - return path.join( - path.dirname(filename), - path.basename(filename, path.extname(filename))+newext - ); +// Convert an absolute path to a relative path for a specific folder (dir) +// ('test/', 'hello.md') -> '../hello.md' +function relative(dir, file) { + return normalize(path.relative(dir, file)); } module.exports = { - isAnchor: isAnchor, - isRelative: isRelative, isExternal: isExternal, + isRelative: isRelative, + isAnchor: isAnchor, + normalize: normalize, toAbsolute: toAbsolute, - join: join, - changeExtension: changeExtension, - normalize: normalizeLink + relative: relative }; diff --git a/lib/utils/logger.js b/lib/utils/logger.js index db3d90e..60215af 100644 --- a/lib/utils/logger.js +++ b/lib/utils/logger.js @@ -1,6 +1,6 @@ -var _ = require("lodash"); -var util = require("util"); -var color = require("bash-color"); +var _ = require('lodash'); +var util = require('util'); +var color = require('bash-color'); var LEVELS = { DEBUG: 0, @@ -17,86 +17,112 @@ var COLORS = { ERROR: color.red }; -module.exports = function(_write, logLevel) { - var logger = {}; - var lastChar = "\n"; - if (_.isString(logLevel)) logLevel = LEVELS[logLevel.toUpperCase()]; +function Logger(write, logLevel, prefix) { + if (!(this instanceof Logger)) return new Logger(write, logLevel); + + this._write = write || function(msg) { process.stdout.write(msg); }; + this.lastChar = '\n'; + + // Define log level + this.setLevel(logLevel); + + _.bindAll(this); - // Write a simple message - logger.write = function(msg) { - msg = msg.toString(); - lastChar = _.last(msg); - return _write(msg); - }; - - // Format a message - logger.format = function() { - return util.format.apply(util, arguments); - }; - - // Write a line - logger.writeLn = function(msg) { - return this.write((msg || "")+"\n"); - }; - - // Write a message with a certain level - logger.log = function(level) { - if (level < logLevel) return; - - var levelKey = _.findKey(LEVELS, function(v) { return v == level; }); - var args = Array.prototype.slice.apply(arguments, [1]); - var msg = logger.format.apply(logger, args); - - if (lastChar == "\n") { - msg = COLORS[levelKey](levelKey.toLowerCase()+":")+" "+msg; - } - - return logger.write(msg); - }; - logger.logLn = function() { - if (lastChar != "\n") logger.write("\n"); - - var args = Array.prototype.slice.apply(arguments); - args.push("\n"); - logger.log.apply(logger, args); - }; - - // Write a OK - logger.ok = function(level) { - var args = Array.prototype.slice.apply(arguments, [1]); - var msg = logger.format.apply(logger, args); - if (arguments.length > 1) { - logger.logLn(level, color.green(">> ") + msg.trim().replace(/\n/g, color.green("\n>> "))); - } else { - logger.log(level, color.green("OK"), "\n"); - } - }; - - // Write an "FAIL" - logger.fail = function(level) { - return logger.log(level, color.red("ERROR")+"\n"); - }; - - _.each(_.omit(LEVELS, "DISABLED"), function(level, levelKey) { + // Create easy-to-use method like "logger.debug.ln('....')" + _.each(_.omit(LEVELS, 'DISABLED'), function(level, levelKey) { levelKey = levelKey.toLowerCase(); - logger[levelKey] = _.partial(logger.log, level); - logger[levelKey].ln = _.partial(logger.logLn, level); - logger[levelKey].ok = _.partial(logger.ok, level); - logger[levelKey].fail = _.partial(logger.fail, level); - logger[levelKey].promise = function(p) { - return p. - then(function(st) { - logger[levelKey].ok(); - return st; - }, function(err) { - logger[levelKey].fail(); - throw err; - }); - }; - }); + this[levelKey] = _.partial(this.log, level); + this[levelKey].ln = _.partial(this.logLn, level); + this[levelKey].ok = _.partial(this.ok, level); + this[levelKey].fail = _.partial(this.fail, level); + this[levelKey].promise = _.partial(this.promise, level); + }, this); +} + +// Create a new logger prefixed from this logger +Logger.prototype.prefix = function(prefix) { + return (new Logger(this._write, this.logLevel, prefix)); +}; + +// Change minimum level +Logger.prototype.setLevel = function(logLevel) { + if (_.isString(logLevel)) logLevel = LEVELS[logLevel.toUpperCase()]; + this.logLevel = logLevel; +}; + +// Print a simple string +Logger.prototype.write = function(msg) { + msg = msg.toString(); + this.lastChar = _.last(msg); + return this._write(msg); +}; + +// Format a string using the first argument as a printf-like format. +Logger.prototype.format = function() { + return util.format.apply(util, arguments); +}; + +// Print a line +Logger.prototype.writeLn = function(msg) { + return this.write((msg || '')+'\n'); +}; - return logger; +// Log/Print a message if level is allowed +Logger.prototype.log = function(level) { + if (level < this.logLevel) return; + + var levelKey = _.findKey(LEVELS, function(v) { return v == level; }); + var args = Array.prototype.slice.apply(arguments, [1]); + var msg = this.format.apply(this, args); + + if (this.lastChar == '\n') { + msg = COLORS[levelKey](levelKey.toLowerCase()+':')+' '+msg; + } + + return this.write(msg); +}; + +// Log/Print a line if level is allowed +Logger.prototype.logLn = function() { + if (this.lastChar != '\n') this.write('\n'); + + var args = Array.prototype.slice.apply(arguments); + args.push('\n'); + return this.log.apply(this, args); +}; + +// Log a confirmation [OK] +Logger.prototype.ok = function(level) { + var args = Array.prototype.slice.apply(arguments, [1]); + var msg = this.format.apply(this, args); + if (arguments.length > 1) { + this.logLn(level, color.green('>> ') + msg.trim().replace(/\n/g, color.green('\n>> '))); + } else { + this.log(level, color.green('OK'), '\n'); + } }; -module.exports.LEVELS = LEVELS; -module.exports.COLORS = COLORS; + +// Log a "FAIL" +Logger.prototype.fail = function(level) { + return this.log(level, color.red('ERROR') + '\n'); +}; + +// Log state of a promise +Logger.prototype.promise = function(level, p) { + var that = this; + + return p. + then(function(st) { + that.ok(level); + return st; + }, function(err) { + that.fail(level); + throw err; + }); +}; + +Logger.LEVELS = LEVELS; +Logger.COLORS = COLORS; + +module.exports = Logger; diff --git a/lib/utils/navigation.js b/lib/utils/navigation.js deleted file mode 100644 index d07eb35..0000000 --- a/lib/utils/navigation.js +++ /dev/null @@ -1,79 +0,0 @@ -var _ = require("lodash"); - -// Cleans up an article/chapter object -// remove "articles" attributes -function clean(obj) { - return obj && _.omit(obj, ["articles"]); -} - -function flattenChapters(chapters) { - return _.reduce(chapters, function(accu, chapter) { - return accu.concat([clean(chapter)].concat(flattenChapters(chapter.articles))); - }, []); -} - -// Returns from a summary a map of -/* - { - "file/path.md": { - prev: ..., - next: ..., - }, - ... - } -*/ -function navigation(summary, files) { - // Support single files as well as list - files = _.isArray(files) ? files : (_.isString(files) ? [files] : null); - - // List of all navNodes - // Flatten chapters - var navNodes = flattenChapters(summary.chapters); - - // Mapping of prev/next for a give path - var mapping = _.chain(navNodes) - .map(function(current, i) { - var prev = null, next = null; - - // Skip if no path - if(!current.exists) return null; - - // Find prev - prev = _.chain(navNodes.slice(0, i)) - .reverse() - .find(function(node) { - return node.exists && !node.external; - }) - .value(); - - // Find next - next = _.chain(navNodes.slice(i+1)) - .find(function(node) { - return node.exists && !node.external; - }) - .value(); - - return [current.path, { - index: i, - title: current.title, - introduction: current.introduction, - prev: prev, - next: next, - level: current.level, - }]; - }) - .compact() - .object() - .value(); - - // Filter for only files we want - if(files) { - return _.pick(mapping, files); - } - - return mapping; -} - - -// Exports -module.exports = navigation; diff --git a/lib/utils/page.js b/lib/utils/page.js deleted file mode 100644 index 010d703..0000000 --- a/lib/utils/page.js +++ /dev/null @@ -1,397 +0,0 @@ -var Q = require('q'); -var _ = require('lodash'); -var url = require('url'); -var path = require('path'); -var cheerio = require('cheerio'); -var domSerializer = require('dom-serializer'); -var request = require('request'); -var crc = require('crc'); -var slug = require('github-slugid'); - -var links = require('./links'); -var imgUtils = require('./images'); -var fs = require('./fs'); -var batch = require('./batch'); - -var parsableExtensions = require('gitbook-parsers').extensions; - -// Map of images that have been converted -var imgConversionCache = {}; - -// Render a cheerio dom as html -function renderDom($, dom, options) { - if (!dom && $._root && $._root.children) { - dom = $._root.children; - } - - options = options|| dom.options || $._options; - return domSerializer(dom, options); -} - -function replaceText($, el, search, replace, text_only ) { - return $(el).each(function(){ - var node = this.firstChild, - val, - new_val, - - // Elements to be removed at the end. - remove = []; - - // Only continue if firstChild exists. - if ( node ) { - - // Loop over all childNodes. - while (node) { - - // Only process text nodes. - if ( node.nodeType === 3 ) { - - // The original node value. - val = node.nodeValue; - - // The new value. - new_val = val.replace( search, replace ); - - // Only replace text if the new value is actually different! - if ( new_val !== val ) { - - if ( !text_only && /</.test( new_val ) ) { - // The new value contains HTML, set it in a slower but far more - // robust way. - $(node).before( new_val ); - - // Don't remove the node yet, or the loop will lose its place. - remove.push( node ); - } else { - // The new value contains no HTML, so it can be set in this - // very fast, simple way. - node.nodeValue = new_val; - } - } - } - - node = node.nextSibling; - } - } - - // Time to remove those elements! - if (remove.length) $(remove).remove(); - }); -} - -function pregQuote( str ) { - return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1'); -} - - -// Adapt an html snippet to be relative to a base folder -function normalizeHtml(src, options) { - var $ = cheerio.load(src, { - // We should parse html without trying to normalize too much - xmlMode: false, - - // SVG need some attributes to use uppercases - lowerCaseAttributeNames: false, - lowerCaseTags: false - }); - var toConvert = []; - var svgContent = {}; - var outputRoot = options.book.options.output; - - imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {}; - - // Find svg images to extract and process - if (options.convertImages) { - $('svg').each(function() { - var content = renderDom($, $(this)); - var svgId = _.uniqueId('svg'); - var dest = svgId+'.svg'; - - // Generate filename - dest = '/'+fs.getUniqueFilename(outputRoot, dest); - - svgContent[dest] = '<?xml version="1.0" encoding="UTF-8"?>'+content; - $(this).replaceWith($('<img>').attr('src', dest)); - }); - } - - // Generate ID for headings - $('h1,h2,h3,h4,h5,h6').each(function() { - if ($(this).attr('id')) return; - - $(this).attr('id', slug($(this).text())); - }); - - // Find images to normalize - $('img').each(function() { - var origin; - var src = $(this).attr('src'); - - if (!src) return; - var isExternal = links.isExternal(src); - - // Transform as relative to the bases - if (links.isRelative(src)) { - src = links.toAbsolute(src, options.base, options.output); - } - - // Convert if needed - if (options.convertImages) { - // If image is external and ebook, then downlaod the images - if (isExternal) { - origin = src; - src = '/'+crc.crc32(origin).toString(16)+path.extname(url.parse(origin).pathname); - src = links.toAbsolute(src, options.base, options.output); - isExternal = false; - } - - var ext = path.extname(src); - var srcAbs = links.join('/', options.base, src); - - // Test image extension - if (_.contains(imgUtils.INVALID, ext)) { - if (imgConversionCache[outputRoot][srcAbs]) { - // Already converted - src = imgConversionCache[outputRoot][srcAbs]; - } else { - // Not converted yet - var dest = ''; - - // Replace extension - dest = links.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+'.png'); - dest = dest[0] == '/'? dest.slice(1) : dest; - - // Get a name that doesn't exists - dest = fs.getUniqueFilename(outputRoot, dest); - - options.book.log.debug.ln('detect invalid image (will be converted to png):', srcAbs); - - // Add to cache - imgConversionCache[outputRoot][srcAbs] = '/'+dest; - - // Push to convert - toConvert.push({ - origin: origin, - content: svgContent[srcAbs], - source: isExternal? srcAbs : path.join('./', srcAbs), - dest: path.join('./', dest) - }); - - src = links.join('/', dest); - } - - // Reset as relative to output - src = links.toAbsolute(src, options.base, options.output); - } - - else if (origin) { - // Need to downlaod image - toConvert.push({ - origin: origin, - source: path.join('./', srcAbs) - }); - } - } - - $(this).attr('src', src); - }); - - // Normalize links - $('a').each(function() { - var href = $(this).attr('href'); - if (!href) return; - - if (links.isAnchor(href)) { - // Keep it as it is - } else if (links.isRelative(href)) { - var parts = url.parse(href); - - var pathName = decodeURIComponent(parts.pathname); - var anchor = parts.hash || ''; - - // Calcul absolute path for this file (without the anchor) - var absolutePath = links.join(options.base, pathName); - - // If is in navigation relative: transform as content - if (options.navigation[absolutePath]) { - absolutePath = options.book.contentLink(absolutePath); - } - - // If md/adoc/rst files is not in summary - // or for ebook, signal all files that are outside the summary - else if (_.contains(parsableExtensions, path.extname(absolutePath)) || - _.contains(['epub', 'pdf', 'mobi'], options.book.options.generator)) { - options.book.log.warn.ln('page', options.input, 'contains an hyperlink to resource outside spine \''+href+'\''); - } - - // Transform as absolute - href = links.toAbsolute('/'+absolutePath, options.base, options.output)+anchor; - } else { - // External links - $(this).attr('target', '_blank'); - } - - // Transform extension - $(this).attr('href', href); - }); - - // Highlight code blocks - $('code').each(function() { - // Normalize language - var lang = _.chain( - ($(this).attr('class') || '').split(' ') - ) - .map(function(cl) { - // Markdown - if (cl.search('lang-') === 0) return cl.slice('lang-'.length); - - // Asciidoc - if (cl.search('language-') === 0) return cl.slice('language-'.length); - - return null; - }) - .compact() - .first() - .value(); - - var source = $(this).text(); - var blk = options.book.template.applyBlock('code', { - body: source, - kwargs: { - language: lang - } - }); - - if (blk.html === false) $(this).text(blk.body); - else $(this).html(blk.body); - }); - - // Replace glossary terms - var glossary = _.sortBy(options.glossary, function(term) { - return -term.name.length; - }); - - _.each(glossary, function(term) { - var r = new RegExp( '\\b(' + pregQuote(term.name.toLowerCase()) + ')\\b' , 'gi' ); - var includedInFiles = false; - - $('*').each(function() { - // Ignore codeblocks - if (_.contains(['code', 'pre', 'a', 'script'], this.name.toLowerCase())) return; - - replaceText($, this, r, function(match) { - // Add to files index in glossary - if (!includedInFiles) { - includedInFiles = true; - term.files = term.files || []; - term.files.push(options.navigation[options.input]); - } - return '<a href=\''+links.toAbsolute('/GLOSSARY.html', options.base, options.output) + '#' + term.id+'\' class=\'glossary-term\' title=\''+_.escape(term.description)+'\'>'+match+'</a>'; - }); - }); - }); - - return { - html: renderDom($), - images: toConvert - }; -} - -// Convert svg images to png -function convertImages(images, options) { - if (!options.convertImages) return Q(); - - var downloaded = []; - options.book.log.debug.ln('convert ', images.length, 'images to png'); - - return batch.execEach(images, { - max: 100, - fn: function(image) { - var imgin = path.resolve(options.book.options.output, image.source); - - return Q() - - // Write image if need to be download - .then(function() { - if (!image.origin && !_.contains(downloaded, image.origin)) return; - options.book.log.debug('download image', image.origin, '...'); - downloaded.push(image.origin); - return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin))) - .fail(function(err) { - if (!_.isError(err)) err = new Error(err); - - err.message = 'Fail downloading '+image.origin+': '+err.message; - throw err; - }); - }) - - // Write svg if content - .then(function() { - if (!image.content) return; - return fs.writeFile(imgin, image.content); - }) - - // Convert - .then(function() { - if (!image.dest) return; - var imgout = path.resolve(options.book.options.output, image.dest); - options.book.log.debug('convert image', image.source, 'to', image.dest, '...'); - return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout)); - }); - } - }) - .then(function() { - options.book.log.debug.ok(images.length+' images converted with success'); - }); -} - -// Adapt page content to be relative to a base folder -function normalizePage(sections, options) { - options = _.defaults(options || {}, { - // Current book - book: null, - - // Do we need to convert svg? - convertImages: false, - - // Current file path - input: '.', - - // Navigation to use to transform path - navigation: {}, - - // Directory parent of the file currently in rendering process - base: './', - - // Directory parent from the html output - output: './', - - // Glossary terms - glossary: [] - }); - - // List of images to convert - var toConvert = []; - - sections = _.map(sections, function(section) { - if (section.type != 'normal') return section; - - var out = normalizeHtml(section.content, options); - - toConvert = toConvert.concat(out.images); - section.content = out.html; - return section; - }); - - return Q() - .then(function() { - toConvert = _.uniq(toConvert, 'source'); - return convertImages(toConvert, options); - }) - .thenResolve(sections); -} - - -module.exports = { - normalize: normalizePage -}; diff --git a/lib/utils/path.js b/lib/utils/path.js index 5285896..c233c92 100644 --- a/lib/utils/path.js +++ b/lib/utils/path.js @@ -1,5 +1,12 @@ -var _ = require("lodash"); -var path = require("path"); +var _ = require('lodash'); +var path = require('path'); + +var error = require('./error'); + +// Normalize a filename +function normalizePath(filename) { + return path.normalize(filename); +} // Return true if file path is inside a folder function isInRoot(root, filename) { @@ -10,31 +17,42 @@ function isInRoot(root, filename) { // Resolve paths in a specific folder // Throw error if file is outside this folder function resolveInRoot(root) { - var input, result, err; + var input, result; input = _.chain(arguments) .toArray() .slice(1) .reduce(function(current, p) { // Handle path relative to book root ("/README.md") - if (p[0] == "/" || p[0] == "\\") return p.slice(1); + if (p[0] == '/' || p[0] == '\\') return p.slice(1); return current? path.join(current, p) : path.normalize(p); - }, "") + }, '') .value(); result = path.resolve(root, input); if (!isInRoot(root, result)) { - err = new Error("EACCESS: \"" + result + "\" not in \"" + root + "\""); - err.code = "EACCESS"; - throw err; + throw new error.FileOutOfScopeError({ + filename: result, + root: root + }); } return result; } +// Chnage extension +function setExtension(filename, ext) { + return path.join( + path.dirname(filename), + path.basename(filename, path.extname(filename)) + ext + ); +} + module.exports = { isInRoot: isInRoot, - resolveInRoot: resolveInRoot + resolveInRoot: resolveInRoot, + normalize: normalizePath, + setExtension: setExtension }; diff --git a/lib/utils/progress.js b/lib/utils/progress.js deleted file mode 100644 index 8dda892..0000000 --- a/lib/utils/progress.js +++ /dev/null @@ -1,55 +0,0 @@ -var _ = require('lodash'); - -// Returns from a navigation and a current file, a snapshot of current detailed state -function calculProgress(navigation, current) { - var n = _.size(navigation); - var percent = 0, prevPercent = 0, currentChapter = null; - var done = true; - - var chapters = _.chain(navigation) - - // Transform as array - .map(function(nav, path) { - nav.path = path; - return nav; - }) - - // Sort entries - .sortBy(function(nav) { - return nav.index; - }) - - .map(function(nav, i) { - // Calcul percent - nav.percent = (i * 100) / Math.max((n - 1), 1); - - // Is it done - nav.done = done; - if (nav.path == current) { - currentChapter = nav; - percent = nav.percent; - done = false; - } else if (done) { - prevPercent = nav.percent; - } - - return nav; - }) - .value(); - - return { - // Previous percent - prevPercent: prevPercent, - - // Current percent - percent: percent, - - // List of chapter with progress - chapters: chapters, - - // Current chapter - current: currentChapter - }; -} - -module.exports = calculProgress; diff --git a/lib/utils/promise.js b/lib/utils/promise.js new file mode 100644 index 0000000..d49cf27 --- /dev/null +++ b/lib/utils/promise.js @@ -0,0 +1,62 @@ +var Q = require('q'); +var _ = require('lodash'); + +// Reduce an array to a promise +function reduce(arr, iter, base) { + return _.reduce(arr, function(prev, elem, i) { + return prev.then(function(val) { + return iter(val, elem, i); + }); + }, Q(base)); +} + +// Transform an array +function serie(arr, iter, base) { + return reduce(arr, function(before, item, i) { + return Q(iter(item, i)) + .then(function(r) { + before.push(r); + return before; + }); + }, []); +} + +// Iter over an array and return first result (not null) +function some(arr, iter) { + return _.reduce(arr, function(prev, elem, i) { + return prev.then(function(val) { + if (val) return val; + + return iter(elem, i); + }); + }, Q()); +} + +// Map an array using an async (promised) iterator +function map(arr, iter) { + return reduce(arr, function(prev, entry, i) { + return Q(iter(entry, i)) + .then(function(out) { + prev.push(out); + return prev; + }); + }, []); +} + +// Wrap a fucntion in a promise +function wrap(func) { + return _.wrap(func, function(_func) { + var args = Array.prototype.slice.call(arguments, 1); + return Q() + .then(function() { + return _func.apply(null, args); + }); + }); +} + +module.exports = Q; +module.exports.reduce = reduce; +module.exports.map = map; +module.exports.serie = serie; +module.exports.some = some; +module.exports.wrapfn = wrap; diff --git a/lib/utils/server.js b/lib/utils/server.js deleted file mode 100644 index 1d6822f..0000000 --- a/lib/utils/server.js +++ /dev/null @@ -1,94 +0,0 @@ -var Q = require("q"); -var events = require("events"); -var http = require("http"); -var send = require("send"); -var util = require("util"); -var url = require("url"); - -var Server = function() { - this.running = null; - this.dir = null; - this.port = 0; - this.sockets = []; -}; -util.inherits(Server, events.EventEmitter); - -// Return true if the server is running -Server.prototype.isRunning = function() { - return !!this.running; -}; - -// Stop the server -Server.prototype.stop = function() { - var that = this; - if (!this.isRunning()) return Q(); - - var d = Q.defer(); - this.running.close(function(err) { - that.running = null; - that.emit("state", false); - - if (err) d.reject(err); - else d.resolve(); - }); - - for (var i = 0; i < this.sockets.length; i++) { - this.sockets[i].destroy(); - } - - return d.promise; -}; - -Server.prototype.start = function(dir, port) { - var that = this, pre = Q(); - port = port || 8004; - - if (that.isRunning()) pre = this.stop(); - return pre - .then(function() { - var d = Q.defer(); - - that.running = http.createServer(function(req, res){ - // Render error - function error(err) { - res.statusCode = err.status || 500; - res.end(err.message); - } - - // Redirect to directory"s index.html - function redirect() { - res.statusCode = 301; - res.setHeader("Location", req.url + "/"); - res.end("Redirecting to " + req.url + "/"); - } - - // Send file - send(req, url.parse(req.url).pathname) - .root(dir) - .on("error", error) - .on("directory", redirect) - .pipe(res); - }); - - that.running.on("connection", function (socket) { - that.sockets.push(socket); - socket.setTimeout(4000); - socket.on("close", function () { - that.sockets.splice(that.sockets.indexOf(socket), 1); - }); - }); - - that.running.listen(port, function(err) { - if (err) return d.reject(err); - - that.port = port; - that.dir = dir; - that.emit("state", true); - d.resolve(); - }); - - return d.promise; - }); -}; - -module.exports = Server; diff --git a/lib/utils/string.js b/lib/utils/string.js deleted file mode 100644 index caa2364..0000000 --- a/lib/utils/string.js +++ /dev/null @@ -1,27 +0,0 @@ -var _ = require("lodash"); - -function escapeShellArg(arg) { - var ret = ""; - - ret = arg.replace(/"/g, '\\"'); - - return "\"" + ret + "\""; -} - -function optionsToShellArgs(options) { - return _.chain(options) - .map(function(value, key) { - if (value === null || value === undefined || value === false) return null; - if (value === true) return key; - return key+"="+escapeShellArg(value); - }) - .compact() - .value() - .join(" "); -} - -module.exports = { - escapeShellArg: escapeShellArg, - optionsToShellArgs: optionsToShellArgs, - toLowerCase: String.prototype.toLowerCase.call.bind(String.prototype.toLowerCase) -}; diff --git a/lib/utils/watch.js b/lib/utils/watch.js deleted file mode 100644 index 4d1a752..0000000 --- a/lib/utils/watch.js +++ /dev/null @@ -1,40 +0,0 @@ -var Q = require("q"); -var _ = require("lodash"); -var path = require("path"); -var chokidar = require("chokidar"); - -var parsers = require("gitbook-parsers"); - -function watch(dir) { - var d = Q.defer(); - dir = path.resolve(dir); - - var toWatch = [ - "book.json", "book.js" - ]; - - _.each(parsers.extensions, function(ext) { - toWatch.push("**/*"+ext); - }); - - var watcher = chokidar.watch(toWatch, { - cwd: dir, - ignored: "_book/**", - ignoreInitial: true - }); - - watcher.once("all", function(e, filepath) { - watcher.close(); - - d.resolve(filepath); - }); - watcher.once("error", function(err) { - watcher.close(); - - d.reject(err); - }); - - return d.promise; -} - -module.exports = watch; |