diff options
author | Samy Pessé <samypesse@gmail.com> | 2015-01-19 09:47:36 +0100 |
---|---|---|
committer | Samy Pessé <samypesse@gmail.com> | 2015-01-19 09:47:36 +0100 |
commit | ec586dd3cdf06e9567f5d3e4961022ddc3c94778 (patch) | |
tree | cc7825ab73110b4e6fbedee404427b052edffa17 /lib | |
parent | 80432161708357bdcf0e00533d9e6d327636dab6 (diff) | |
download | gitbook-ec586dd3cdf06e9567f5d3e4961022ddc3c94778.zip gitbook-ec586dd3cdf06e9567f5d3e4961022ddc3c94778.tar.gz gitbook-ec586dd3cdf06e9567f5d3e4961022ddc3c94778.tar.bz2 |
Clear folder
Diffstat (limited to 'lib')
32 files changed, 0 insertions, 2907 deletions
diff --git a/lib/generate/config.js b/lib/generate/config.js deleted file mode 100644 index e198c91..0000000 --- a/lib/generate/config.js +++ /dev/null @@ -1,137 +0,0 @@ -var Q = require('q'); -var _ = require('lodash'); -var path = require('path'); - -// Default configuration for gitbook -var CONFIG = { - // Folders to use for output - // Caution: it overrides the value from the command line - // It's not advised this option in the book.json - "output": null, - - // Generator to use for building - // Caution: it overrides the value from the command line - // It's not advised this option in the book.json - "generator": "site", - - // Configuration file to use - "configFile": "book", - - // Book metadats (somes are extracted from the README by default) - "title": null, - "description": null, - "isbn": null, - - // For ebook format, the extension to use for generation (default is detected from output extension) - // "epub", "pdf", "mobi" - // Caution: it overrides the value from the command line - // It's not advised this option in the book.json - "extension": null, - - // Plugins list, can contain "-name" for removing default plugins - "plugins": [], - - // Global configuration for plugins - "pluginsConfig": { - "fontSettings": { - "theme": null, //"sepia", "night" or "white", - "family": "sans",// "serif" or "sans", - "size": 2 // 1 - 4 - } - }, - - // Variables for templating - "variables": {}, - - // Set another theme with your own layout - // It's recommended to use plugins or add more options for default theme, though - // See https://github.com/GitbookIO/gitbook/issues/209 - "theme": path.resolve(__dirname, '../../theme'), - - // Links in template (null: default, false: remove, string: new value) - "links": { - // Custom links at top of sidebar - "sidebar": { - //"Custom link name": "https://customlink.com" - }, - - // Sharing links - "sharing": { - "google": null, - "facebook": null, - "twitter": null, - "weibo": null, - "all": null - } - }, - - // CSS Styles - "styles": { - "website": "styles/website.css", - "ebook": "styles/ebook.css", - "pdf": "styles/pdf.css", - "mobi": "styles/mobi.css", - "epub": "styles/epub.css" - }, - - // Options for PDF generation - "pdf": { - // Add toc at the end of the file - "toc": true, - - // Add page numbers to the bottom of every page - "pageNumbers": false, - - // Font size for the file content - "fontSize": 12, - - // Paper size for the pdf - // Choices are [u’a0’, u’a1’, u’a2’, u’a3’, u’a4’, u’a5’, u’a6’, u’b0’, u’b1’, u’b2’, u’b3’, u’b4’, u’b5’, u’b6’, u’legal’, u’letter’] - "paperSize": "a4", - - // Margin (in pts) - // Note: 72 pts equals 1 inch - "margin": { - "right": 62, - "left": 62, - "top": 36, - "bottom": 36 - }, - - //Header HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_. - "headerTemplate": "", - - //Footer HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_. - "footerTemplate": "" - } -}; - -// Return complete configuration -var defaultsConfig = function(options) { - return _.merge(options || {}, CONFIG, _.defaults); -}; - -// Read configuration from book.json -var readConfig = function(options) { - options = defaultsConfig(options); - - return Q() - .then(function() { - try { - var _config = require(path.resolve(options.input, options.configFile)); - options = _.merge(options, _.omit(_config, 'input', 'configFile', 'defaultsPlugins', 'generator')); - } - catch(err) { - // No config file: not a big deal - return Q(); - } - }) - .thenResolve(options); -}; - -module.exports = { - CONFIG: CONFIG, - defaults: defaultsConfig, - read: readConfig -} - diff --git a/lib/generate/ebook/index.js b/lib/generate/ebook/index.js deleted file mode 100644 index c74ffcd..0000000 --- a/lib/generate/ebook/index.js +++ /dev/null @@ -1,92 +0,0 @@ -var util = require("util"); -var path = require("path"); -var Q = require("q"); -var _ = require("lodash"); -var exec = require('child_process').exec; - -var fs = require('graceful-fs'); -var parse = require("../../parse"); -var BaseGenerator = require("../page"); -var stringUtils = require("../../utils/string"); - -var Generator = function() { - BaseGenerator.apply(this, arguments); - - // eBook format - this.ebookFormat = this.options.extension || path.extname(this.options.output).replace("\.", "") || "pdf"; - - // Styles to use - this.styles = ["ebook", this.ebookFormat]; -}; -util.inherits(Generator, BaseGenerator); - -Generator.prototype.finish = function() { - var that = this; - - return BaseGenerator.prototype.finish.apply(this) - .then(function() { - var d = Q.defer(); - - if (!that.options.cover && fs.existsSync(path.join(that.options.output, "cover.jpg"))) { - that.options.cover = path.join(that.options.output, "cover.jpg"); - } - - var _options = { - "--cover": that.options.cover, - "--title": that.options.title, - "--comments": that.options.description, - "--isbn": that.options.isbn, - "--authors": that.options.author, - "--publisher": "GitBook", - "--chapter": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter ')]", - "--chapter-mark": "pagebreak", - "--page-breaks-before": "/", - "--level1-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-1 ')]", - "--level2-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-2 ')]", - "--level3-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-3 ')]", - "--no-chapters-in-toc": true, - "--max-levels": "1", - "--breadth-first": true - }; - - if (that.ebookFormat == "pdf") { - var pdfOptions = that.options.pdf; - - _.extend(_options, { - "--margin-left": String(pdfOptions.margin.left), - "--margin-right": String(pdfOptions.margin.right), - "--margin-top": String(pdfOptions.margin.top), - "--margin-bottom": String(pdfOptions.margin.bottom), - "--pdf-default-font-size": String(pdfOptions.fontSize), - "--pdf-mono-font-size": String(pdfOptions.fontSize), - "--paper-size": String(pdfOptions.paperSize), - "--pdf-page-numbers": Boolean(pdfOptions.pageNumbers), - "--pdf-header-template": String(pdfOptions.headerTemplate), - "--pdf-footer-template": String(pdfOptions.footerTemplate) - }); - } - - var command = [ - "ebook-convert", - path.join(that.options.output, "SUMMARY.html"), - path.join(that.options.output, "index."+that.ebookFormat), - stringUtils.optionsToShellArgs(_options) - ].join(" "); - - exec(command, function (error, stdout, stderr) { - if (error) { - if (error.code == 127) { - error.message = "Need to install ebook-convert from Calibre"; - } else { - error.message = error.message + " "+stdout; - } - return d.reject(error); - } - d.resolve(); - }); - - return d.promise; - }); -}; - -module.exports = Generator; diff --git a/lib/generate/fs.js b/lib/generate/fs.js deleted file mode 100644 index 371051c..0000000 --- a/lib/generate/fs.js +++ /dev/null @@ -1,92 +0,0 @@ -var Q = require("q"); -var fs = require('graceful-fs'); -var fsExtra = require("fs-extra"); -var Ignore = require("fstream-ignore"); - -var getFiles = function(path) { - var d = Q.defer(); - - // Our list of files - var files = []; - - var ig = Ignore({ - path: path, - ignoreFiles: ['.ignore', '.gitignore', '.bookignore'] - }); - - // Add extra rules to ignore common folders - ig.addIgnoreRules([ - // Skip Git stuff - '.git/', - '.gitignore', - - // Skip OS X meta data - '.DS_Store', - - // Skip stuff installed by plugins - 'node_modules', - - // Skip book outputs - '*.pdf', - '*.epub', - '*.mobi', - - // Skip config files - '.ignore', - '.bookignore', - 'book.json', - ], '__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; -}; - -module.exports = { - list: getFiles, - readFile: Q.denodeify(fs.readFile), - //writeFile: Q.denodeify(fs.writeFile), - writeFile: function(filename, data, options) { - var d = Q.defer(); - - try { - fs.writeFileSync(filename, data, options) - } catch(err) { - d.reject(err); - } - d.resolve(); - - - return d.promise; - }, - 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; - }, - existsSync: fs.existsSync, - readFileSync: fs.readFileSync.bind(fs) -}; diff --git a/lib/generate/generator.js b/lib/generate/generator.js deleted file mode 100644 index 4791c98..0000000 --- a/lib/generate/generator.js +++ /dev/null @@ -1,87 +0,0 @@ -var _ = require("lodash"); -var path = require("path"); -var Q = require("q"); -var fs = require("./fs"); - -var Plugin = require("./plugin"); - -var BaseGenerator = function(options) { - this.options = options; - - // Base for assets in plugins - this.pluginAssetsBase = "book"; - - this.options.plugins = Plugin.normalizeNames(this.options.plugins); - this.options.plugins = _.union(this.options.plugins, this.options.defaultsPlugins); - this.plugins = []; -}; - -BaseGenerator.prototype.callHook = function(name, data) { - return this.plugins.hook(name, data); -}; - -// Sets up generator -BaseGenerator.prototype.load = function() { - return this.loadPlugins(); -}; - -BaseGenerator.prototype.loadPlugins = function() { - var that = this; - - return Plugin.fromList(this.options.plugins, this.options.input, this, { - assetsBase: this.pluginAssetsBase - }) - .then(function(_plugins) { - that.plugins = _plugins; - - return that.callHook("init"); - }); -}; - -BaseGenerator.prototype.convertFile = function(content, input) { - return Q.reject(new Error("Could not convert "+input)); -}; - -BaseGenerator.prototype.transferFile = function(input) { - return fs.copy( - path.join(this.options.input, input), - path.join(this.options.output, input) - ); -}; - -BaseGenerator.prototype.transferFolder = function(input) { - return fs.mkdirp( - path.join(this.options.output, input) - ); -}; - -BaseGenerator.prototype.copyCover = function() { - var that = this; - - return Q.all([ - fs.copy(path.join(this.options.input, "cover.jpg"), path.join(this.options.output, "cover.jpg")), - fs.copy(path.join(this.options.input, "cover_small.jpg"), path.join(this.options.output, "cover_small.jpg")) - ]) - .fail(function() { - // If orignally from multi-lang, try copy from originalInput - if (!that.options.originalInput) return; - - return Q.all([ - fs.copy(path.join(that.options.originalInput, "cover.jpg"), path.join(that.options.output, "cover.jpg")), - fs.copy(path.join(that.options.originalInput, "cover_small.jpg"), path.join(that.options.output, "cover_small.jpg")) - ]); - }) - .fail(function(err) { - return Q(); - }); -}; - -BaseGenerator.prototype.langsIndex = function(langs) { - return Q.reject(new Error("Langs index is not supported in this generator")); -}; - -BaseGenerator.prototype.finish = function() { - return Q.reject(new Error("Could not finish generation")); -}; - -module.exports = BaseGenerator; diff --git a/lib/generate/index.js b/lib/generate/index.js deleted file mode 100644 index de1fc0e..0000000 --- a/lib/generate/index.js +++ /dev/null @@ -1,374 +0,0 @@ -var Q = require("q"); -var _ = require("lodash"); -var path = require("path"); -var tmp = require('tmp'); - -var swig = require('./template'); -var fs = require("./fs"); -var parse = require("../parse"); -var Plugin = require("./plugin"); -var defaultConfig = require("./config"); - -var generators = { - "site": require("./site"), - "page": require("./page"), - "ebook": require("./ebook"), - "json": require("./json") -}; - -var defaultDescription = "Book generated using GitBook"; - - -var containsFiles = function(dir, files) { - return Q.all(_.map(files, function(file) { - return fs.exists(path.join(dir, file)); - })) - .then(_.all); -}; - -// Test if generator exists -var checkGenerator = function(options) { - if (!generators[options.generator]) { - return Q.reject(new Error("Invalid generator (availables are: "+_.keys(generators).join(", ")+")")); - } - return Q(); -}; - -// Create the generator and load plugins -var loadGenerator = function(options) { - return checkGenerator(options) - .then(function() { - var generator = new generators[options.generator](options); - - return generator.load() - .then(_.constant(generator)); - }); -}; - - - -var generate = function(options) { - // Read config file - return defaultConfig.read(options) - .then(function(_options) { - options = _options; - - // Validate options - if (!options.input) { - return Q.reject(new Error("Need option input (book input directory)")); - } - - // Check files to get folder type (book, multilanguage book or neither) - return checkGenerator(options); - }) - - // Read readme - .then(function() { - return fs.readFile(path.join(options.input, "README.md"), "utf-8") - .then(function(_readme) { - _readme = parse.readme(_readme); - - options.title = options.title || _readme.title; - options.description = options.description || _readme.description || defaultDescription; - }); - }) - - // Detect multi-languages book - .then(function() { - return containsFiles(options.input, ['LANGS.md']) - }) - - .then(function(isMultiLang) { - // Multi language book - if(isMultiLang) { - return generateMultiLang(options); - } - - // Book - return generateBook(options); - }); -}; - -/* - * Generate a multilanguage book by generating a book for each folder. - */ -var generateMultiLang = function(options) { - var langsSummary; - options.output = options.output || path.join(options.input, "_book"); - - return checkGenerator(options) - - // Multi-languages book - .then(function() { - return fs.readFile(path.join(options.input, "LANGS.md"), "utf-8") - }) - - // Clean output folder - .then(function(_langsSummary) { - langsSummary = _langsSummary; - return fs.remove(options.output); - }) - .then(function() { - return fs.mkdirp(options.output); - }) - - // Generate sub-books - .then(function() { - options.langsSummary = parse.langs(langsSummary); - - // Generated a book for each valid entry - return _.reduce(options.langsSummary.list, function(prev, entry) { - return prev.then(function() { - return generate(_.extend({}, options, { - input: path.join(options.input, entry.path), - output: path.join(options.output, entry.path), - originalInput: options.input, - originalOutput: options.output - })); - }) - }, Q()); - }) - - .then(function() { - return loadGenerator(options); - }) - - // Generate languages index - .then(function(generator) { - return generator.langsIndex(options.langsSummary); - }) - - // Copy cover file - .then(function() { - return Q.all([ - fs.copy(path.join(options.input, "cover.jpg"), path.join(options.output, "cover.jpg")), - fs.copy(path.join(options.input, "cover_small.jpg"), path.join(options.output, "cover_small.jpg")) - ]) - .fail(function() { - return Q(); - }) - }) - - // Return options to caller - .then(_.constant(options)); -}; - -/* - * Use a specific generator to convert a gitbook to a site/pdf/ebook/ - * output is always a folder - */ -var generateBook = function(options) { - var files; - - options.output = options.output || path.join(options.input, "_book"); - - // Check if it's a book - return containsFiles(options.input, ['SUMMARY.md', 'README.md']) - - // Fail if not a book - .then(function(isBook) { - if(!isBook) { - return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md")); - } - }) - - // Clean output folder - .then(function() { - return fs.remove(options.output); - }) - - .then(function() { - return fs.mkdirp(options.output); - }) - - // List all files in the repository - .then(function() { - return fs.list(options.input) - .then(function(_files) { - files = _files; - }); - }) - - .then(function() { - return loadGenerator(options); - }) - - // Convert files - .then(function(generator) { - // Generate the book - return Q() - - // Get summary - .then(function() { - var summary = { - path: path.join(options.input, "SUMMARY.md") - }; - - var _callHook = function(name) { - return generator.callHook(name, summary) - .then(function(_summary) { - summary = _summary; - return summary; - }); - }; - - return fs.readFile(summary.path, "utf-8") - .then(function(_content) { - summary.content = _content; - return _callHook("summary:before"); - }) - .then(function() { - summary.content = parse.summary(summary.content); - return _callHook("summary:after"); - }) - .then(function() { - options.summary = summary.content; - options.navigation = parse.navigation(options.summary); - }); - }) - - // Read glossary - .then(function() { - var glossary = {}; - - var _callHook = function(name) { - return generator.callHook(name, glossary) - .then(function(_glossary) { - glossary = _glossary; - return glossary; - }); - }; - - return fs.readFile(path.join(options.input, "GLOSSARY.md"), "utf-8") - .fail(function() { - return ""; - }) - .then(function(_content) { - glossary.content = _content; - return _callHook("glossary:before"); - }) - .then(function() { - glossary.content = parse.glossary(glossary.content); - return _callHook("glossary:after"); - }) - .then(function() { - options.glossary = glossary.content; - }); - }) - - // Skip processing some files - .then(function() { - files = _.filter(files, function (file) { - return !( - file === 'SUMMARY.md' || - file === 'GLOSSARY.md' - ); - }); - }) - - // Copy file and replace markdown file - .then(function() { - return Q.all( - _.chain(files) - .map(function(file) { - if (!file) return; - - if (file[file.length -1] == "/") { - return Q(generator.transferFolder(file)); - } else if (path.extname(file) == ".md" && options.navigation[file] != null) { - return fs.readFile(path.join(options.input, file), "utf-8") - .then(function(content) { - return Q(generator.convertFile(content, file)); - }); - } else { - return Q(generator.transferFile(file)); - } - }) - .value() - ); - }) - - // Finish generation - .then(function() { - return generator.callHook("finish:before"); - }) - .then(function() { - return generator.finish(); - }) - .then(function() { - return generator.callHook("finish"); - }); - }) - - // Return all options - .then(function() { - return options; - }); -}; - -/* - * Extract files from generate output in a temporary folder - */ -var generateFile = function(options) { - options = _.defaults(options || {}, { - input: null, - output: null, - extension: null - }); - - return Q.nfcall(tmp.dir) - .then(function(tmpDir) { - return generate( - _.extend({}, - options, - { - output: tmpDir - }) - ) - .then(function(_options) { - var ext = options.extension; - var outputFile = options.output || path.resolve(options.input, "book."+ext); - - var copyFile = function(lang) { - var _outputFile = outputFile; - var _tmpDir = tmpDir; - - if (lang) { - _outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+"_"+lang+path.extname(_outputFile); - _tmpDir = path.join(_tmpDir, lang); - } - - return fs.copy( - path.join(_tmpDir, "index."+ext), - _outputFile - ); - }; - - // Multi-langs book - return Q() - .then(function() { - if (_options.langsSummary) { - return Q.all( - _.map(_options.langsSummary.list, function(lang) { - return copyFile(lang.lang); - }) - ); - } else { - return copyFile(); - } - }) - .then(function() { - return fs.remove(tmpDir); - }); - }); - }); -}; - -module.exports = { - generators: generators, - folder: generate, - file: generateFile, - book: generateBook, - Plugin: Plugin, - config: defaultConfig -}; diff --git a/lib/generate/init.js b/lib/generate/init.js deleted file mode 100644 index 705e6e7..0000000 --- a/lib/generate/init.js +++ /dev/null @@ -1,69 +0,0 @@ -var Q = require('q'); -var _ = require('lodash'); - -var path = require('path'); - -var fs = require('./fs'); -var parse = require('../parse'); - - -// Extract paths out of a summary -function paths(summary) { - return _.reduce(summary.chapters, function(accu, chapter) { - return accu.concat( - _.filter([chapter.path].concat(_.pluck(chapter.articles, 'path'))) - ); - }, []); -} - -// Get the parent folders out of a group of files -function folders(files) { - return _.chain(files) - .map(function(file) { - return path.dirname(file); - }) - .uniq() - .value(); -} - -function initDir(dir) { - return fs.readFile(path.join(dir, 'SUMMARY.md'), 'utf8') - .then(function(src) { - // Parse summary - return parse.summary(src); - }) - .then(function(summary) { - // Extract paths from summary - return paths(summary); - }) - .then(function(paths) { - // Convert to absolute paths - return _.map(paths, function(file) { - return path.resolve(file); - }); - }) - .then(function(files) { - // Create folders - return Q.all(_.map(folders(files), function(folder) { - return fs.mkdirp(folder); - })) - .then(_.constant(files)); - }) - .then(function(files) { - // Create files that don't exist - return Q.all(_.map(files, function(file) { - return fs.exists(file) - .then(function(exists) { - if(exists) return; - return fs.writeFile(file, ''); - }); - })); - }) - .fail(function(err) { - console.error(err.stack); - }); -} - - -// Exports -module.exports = initDir; diff --git a/lib/generate/json/index.js b/lib/generate/json/index.js deleted file mode 100644 index a252ed3..0000000 --- a/lib/generate/json/index.js +++ /dev/null @@ -1,77 +0,0 @@ -var util = require("util"); -var path = require("path"); -var Q = require("q"); -var _ = require("lodash"); - -var fs = require("../fs"); -var parse = require("../../parse"); -var BaseGenerator = require("../generator"); - - -var Generator = function() { - BaseGenerator.apply(this, arguments); -}; -util.inherits(Generator, BaseGenerator); - -Generator.prototype.transferFile = function(input) { - // ignore -}; - -Generator.prototype.convertFile = function(content, input) { - var that = this; - var json = { - progress: parse.progress(this.options.navigation, input) - }; - - return Q() - .then(function() { - return parse.page(content, { - dir: path.dirname(input) || '/' - }); - }) - .then(function(parsed) { - json.lexed = parsed.lexed; - json.sections = parsed.sections; - }) - .then(function() { - return fs.writeFile( - path.join(that.options.output, input.replace(".md", ".json")), - JSON.stringify(json, null, 4) - ); - }); -}; - -// Generate languages index -// Contains the first languages readme and langs infos -Generator.prototype.langsIndex = function(langs) { - var that = this; - - if (langs.list.length == 0) return Q.reject("Need at least one language"); - - var mainLang = _.first(langs.list).lang; - console.log("Main language is", mainLang); - - return Q() - .then(function() { - return fs.readFile( - path.join(that.options.output, mainLang, "README.json") - ); - }) - .then(function(content) { - var json = JSON.parse(content); - _.extend(json, { - langs: langs.list - }); - - return fs.writeFile( - path.join(that.options.output, "README.json"), - JSON.stringify(json, null, 4) - ); - }); -}; - -Generator.prototype.finish = function() { - // ignore -}; - -module.exports = Generator; diff --git a/lib/generate/page/index.js b/lib/generate/page/index.js deleted file mode 100644 index 8054fe6..0000000 --- a/lib/generate/page/index.js +++ /dev/null @@ -1,84 +0,0 @@ -var _ = require("lodash"); -var util = require("util"); -var path = require("path"); -var Q = require("q"); -var swig = require("../template"); - -var fs = require("../fs"); -var parse = require("../../parse"); -var BaseGenerator = require("../site"); - -var Generator = function() { - BaseGenerator.apply(this, arguments); - - // Styles to use - this.styles = ["ebook"]; - - // Base for assets in plugins - this.pluginAssetsBase = "ebook"; - - // List of pages content - this.pages = {}; -}; -util.inherits(Generator, BaseGenerator); - -Generator.prototype.loadTemplates = function() { - this.template = swig.compileFile( - this.plugins.template("ebook:page") || path.resolve(this.options.theme, 'templates/ebook/page.html') - ); - this.summaryTemplate = swig.compileFile( - this.plugins.template("ebook:sumary") || path.resolve(this.options.theme, 'templates/ebook/summary.html') - ); - this.glossaryTemplate = swig.compileFile( - this.plugins.template("ebook:glossary") || path.resolve(this.options.theme, 'templates/ebook/glossary.html') - ); -}; - -// Generate table of contents -Generator.prototype.writeToc = function() { - var that = this; - var basePath = "."; - - return this._writeTemplate(this.summaryTemplate, { - toc: parse.progress(this.options.navigation, "README.md").chapters, - basePath: basePath, - staticBase: path.join(basePath, "gitbook"), - }, path.join(this.options.output, "SUMMARY.html")); -}; - -Generator.prototype.finish = function() { - var that = this; - var basePath = "."; - var output = path.join(this.options.output, "index.html"); - - var progress = parse.progress(this.options.navigation, "README.md"); - - return Q() - - // Write table of contents - .then(function() { - return that.writeToc(); - }) - - // Write glossary - .then(function() { - return that.writeGlossary(); - }) - - // Copy cover - .then(function() { - return that.copyCover(); - }) - - // Copy assets - .then(function() { - return that.copyAssets(); - }); -}; - -// Generate languages index -Generator.prototype.langsIndex = function(langs) { - return Q(); -}; - -module.exports = Generator; diff --git a/lib/generate/plugin.js b/lib/generate/plugin.js deleted file mode 100644 index 5ca5e92..0000000 --- a/lib/generate/plugin.js +++ /dev/null @@ -1,325 +0,0 @@ -var _ = require("lodash"); -var Q = require("q"); -var semver = require("semver"); -var path = require("path"); -var url = require("url"); -var fs = require("./fs"); -var npmi = require('npmi'); -var resolve = require('resolve'); - -var pkg = require("../../package.json"); - -var RESOURCES = ["js", "css"]; - -var Plugin = function(name, root, generator) { - this.name = name; - this.root = root; - this.packageInfos = {}; - this.infos = {}; - this.generator = generator; - - // Bind methods - _.bindAll(this); - - _.each([ - "gitbook-plugin-"+name, - "gitbook-theme-"+name, - "gitbook-"+name, - name, - ], function(_name) { - if (this.load(_name, __dirname)) return false; - if (this.load(_name, path.resolve(root))) return false; - }, this); -}; - -// Load from a name -Plugin.prototype.load = function(name, baseDir) { - try { - var res = resolve.sync(name+"/package.json", { basedir: baseDir }); - - this.baseDir = path.dirname(res); - this.packageInfos = require(res); - this.infos = require(resolve.sync(name, { basedir: baseDir })); - this.name = name; - - return true; - } catch (e) { - return false; - } -}; - -Plugin.prototype.normalizeResource = function(resource) { - // Parse the resource path - var parsed = url.parse(resource); - - // This is a remote resource - // so we will simply link to using it's URL - if (parsed.protocol) { - return { - "url": resource - }; - } - - // This will be copied over from disk - // and shipped with the book's build - return { "path": this.name+"/"+resource }; -}; - -// Return resources -Plugin.prototype._getResources = function(base) { - base = base || "book"; - var book = this.infos[base]; - - // Nothing specified, fallback to default - if (!book) { - return Q({}); - } - - // Dynamic function - if(typeof book === "function") { - // Call giving it the context of our generator - return Q().then(book.bind(this.generator)); - } - - // Plain data object - return Q(_.cloneDeep(book)); -}; - -// Normalize resources and return them -Plugin.prototype.getResources = function(base) { - var that = this; - - return this._getResources(base) - .then(function(resources) { - - _.each(RESOURCES, function(resourceType) { - resources[resourceType] = (resources[resourceType] || []).map(that.normalizeResource); - }); - - return resources; - }); -}; - -// Test if it's a valid plugin -Plugin.prototype.isValid = function() { - return ( - this.packageInfos && - this.packageInfos.name && - this.packageInfos.engines && - this.packageInfos.engines.gitbook && - semver.satisfies(pkg.version, this.packageInfos.engines.gitbook) - ); -}; - -// Resolve file path -Plugin.prototype.resolveFile = function(filename) { - return path.resolve(this.baseDir, filename); -}; - -// Resolve file path -Plugin.prototype.callHook = function(name, data) { - // Our generator will be the context to apply - var context = this.generator; - - var hookFunc = this.infos.hooks? this.infos.hooks[name] : null; - data = data || {}; - - if (!hookFunc) return Q(data); - - return Q() - .then(function() { - return hookFunc.apply(context, [data]); - }); -}; - -// Copy plugin assets fodler -Plugin.prototype.copyAssets = function(out, options) { - var that = this; - options = _.defaults(options || {}, { - base: "book" - }); - - return this.getResources(options.base) - .get('assets') - .then(function(assets) { - // Assets are undefined - if(!assets) return false; - - return fs.copy( - that.resolveFile(assets), - out - ).then(_.constant(true)); - }, _.constant(false)); -}; - - -// Install a list of plugin -Plugin.install = function(options) { - // Normalize list of plugins - var plugins = Plugin.normalizeList(options.plugins); - - // Install plugins one by one - return _.reduce(plugins, function(prev, plugin) { - return prev.then(function() { - var fullname = "gitbook-plugin-"+plugin.name; - console.log("Install plugin", plugin.name, "from npm ("+fullname+") with version", (plugin.version || "*")); - return Q.nfcall(npmi, { - 'name': fullname, - 'version': plugin.version, - 'path': options.input, - 'npmLoad': { - 'loglevel': 'silent', - 'loaded': false, - 'prefix': options.input - } - }); - }); - }, Q()); -}; - -// Normalize a list of plugins to use -Plugin.normalizeList = function(plugins) { - // Normalize list to an array - plugins = _.isString(plugins) ? plugins.split(",") : (plugins || []); - - // Divide as {name, version} to handle format like "myplugin@1.0.0" - plugins = _.map(plugins, function(plugin) { - var parts = plugin.split("@"); - return { - 'name': parts[0], - 'version': parts[1] // optional - } - }); - - // List plugins to remove - var toremove = _.chain(plugins) - .filter(function(plugin) { - return plugin.name.length > 0 && plugin.name[0] == "-"; - }) - .map(function(plugin) { - return plugin.name.slice(1); - }) - .value(); - - // Merge with defaults - plugins = _.chain(plugins) - .concat(_.map(Plugin.defaults, function(plugin) { - return { 'name': plugin } - })) - .uniq() - .value(); - - // Build final list - plugins = _.filter(plugins, function(plugin) { - return !_.contains(toremove, plugin.name) && !(plugin.name.length > 0 && plugin.name[0] == "-"); - }); - - return plugins; -}; - -// Normalize a list of plugin name to use -Plugin.normalizeNames = function(plugins) { - return _.pluck(Plugin.normalizeList(plugins), "name"); -}; - -// Extract data from a list of plugin -Plugin.fromList = function(names, root, generator, options) { - options = _.defaults(options || {}, { - assetsBase: "book" - }); - - var failed = []; - - // Load plugins - var plugins = _.map(names, function(name) { - var plugin = new Plugin(name, root, generator); - if (!plugin.isValid()) failed.push(name); - return plugin; - }); - - if (_.size(failed) > 0) return Q.reject(new Error("Error loading plugins: "+failed.join(",")+". Run 'gitbook install' to install plugins from NPM.")); - - // The raw resources extracted from each plugin - var pluginResources; - - // Get resources of plugins - return Q.all(_.map(plugins, function(plugin) { - return plugin.getResources(options.assetsBase); - })) - // Extract resources out - // css, js, etc ... - .then(function(resources) { - pluginResources = resources; - // Group by resource types - return _.chain(RESOURCES) - .map(function(resourceType) { - // Get resources from all the plugins for this current type - return [ - // Key - resourceType, - // Value - _.chain(resources) - .pluck(resourceType) - .compact() - .flatten() - .value() - ]; - }) - .object() - .value(); - }) - // Extract html snippets - .then(function(resources) { - // Map of html resources by name added by each plugin - resources.html = pluginResources.reduce(function(accu, resource) { - var html = (resource && resource.html) || {}; - _.each(html, function(code, key) { - // Turn into function if not one already - if (!_.isFunction(code)) code = _.constant(code); - // Append - accu[key] = (accu[key] || []).concat([code]); - }); - - return accu; - }, {}); - - return resources; - }) - // Return big multi-plugin object - .then(function(resources) { - return { - 'list': plugins, - 'resources': resources, - 'hook': function(name, data) { - return _.reduce(plugins, function(prev, plugin) { - return prev.then(function(ret) { - return plugin.callHook(name, ret); - }); - }, Q(data)); - }, - 'template': function(name) { - var withTpl = _.find(plugins, function(plugin) { - return ( - plugin.infos.templates && - plugin.infos.templates[name] - ); - }); - - if (!withTpl) return null; - return withTpl.resolveFile(withTpl.infos.templates[name]); - }, - 'html': function(tag, context, options) { - return _.map(resources.html[tag] || [], function(code) { - return code.call(context, options); - }).join("\n"); - } - }; - }); -}; - -// Default plugins added to each books -Plugin.defaults = [ - "mathjax" -]; - -module.exports = Plugin; diff --git a/lib/generate/site/glossary_indexer.js b/lib/generate/site/glossary_indexer.js deleted file mode 100644 index 46ac9a4..0000000 --- a/lib/generate/site/glossary_indexer.js +++ /dev/null @@ -1,101 +0,0 @@ -var _ = require("lodash"); -var kramed = require('kramed'); -var textRenderer = require('kramed-text-renderer'); - -var entryId = require('../../parse/glossary').entryId; - - -function Indexer(glossary) { - if(!(this instanceof Indexer)) { - return new Indexer(glossary); - } - - _.bindAll(this); - - this.glossary = glossary || []; - - this.glossaryTerms = _.pluck(this.glossary, "id"); - - // Regex for searching for terms through body - this.termsRegex = new RegExp( - // Match any of the terms - "("+ - this.glossaryTerms.map(regexEscape).join('|') + - ")", - - // Flags - "gi" - ); - - // page url => terms - this.idx = { - /* - "a/b.html": ["one word", "second word"] - */ - }; - - // term => page urls - this.invertedIdx = { - /* - "word1": ["page1.html", "page2.html"] - */ - }; - - // Use text renderer - this.renderer = textRenderer(); -} - -Indexer.prototype.text = function(nodes) { - // Copy section - var section = _.toArray(nodes); - - // kramed's Render expects this, we don't use it yet - section.links = {}; - - var options = _.extend({}, kramed.defaults, { - renderer: this.renderer - }); - - return kramed.parser(section, options); -}; - -// Add page to glossary index -Indexer.prototype.add = function(sections, url) { - if(!(this.glossary && this.glossary.length > 0)) { - return; - } - - var textblob = - _.where(sections, { type: 'normal' }) - .map(this.text) - .join('\n'); - - var matches = _(textblob.match(this.termsRegex) || []) - .map(entryId) - .uniq() - .value(); - - // Add idx for book - this.idx[url] = matches; - - // Add to inverted idx - matches.forEach(function(match) { - if(!this.invertedIdx[match]) { - this.invertedIdx[match] = []; - } - this.invertedIdx[match].push(url); - }.bind(this)); -}; - -// Dump index as a string -Indexer.prototype.dump = function() { - return JSON.stringify(this.idx); -}; - - -function regexEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); -} - -// Exports -module.exports = Indexer; diff --git a/lib/generate/site/index.js b/lib/generate/site/index.js deleted file mode 100644 index dfdbf68..0000000 --- a/lib/generate/site/index.js +++ /dev/null @@ -1,314 +0,0 @@ -var util = require("util"); -var path = require("path"); -var Q = require("q"); -var _ = require("lodash"); -var swig = require("../template"); - -var fs = require("../fs"); -var parse = require("../../parse"); -var BaseGenerator = require("../generator"); -var links = require("../../utils/links"); -var indexer = require('./search_indexer'); -var glossaryIndexer = require('./glossary_indexer'); - - -var Generator = function() { - BaseGenerator.apply(this, arguments); - - // Attach methods to instance - _.bindAll(this); - - this.styles = ["website"]; - this.revision = Date.now(); - this.indexer = indexer(); -}; -util.inherits(Generator, BaseGenerator); - -// Add template loading to load -Generator.prototype.load = function() { - var that = this; - - return BaseGenerator.prototype.load.apply(this) - .then(function() { - return that.loadStyles(); - }) - .then(function() { - return that.loadTemplates(); - }); -}; - -// Load all styles -Generator.prototype.loadStyles = function() { - var that = this; - this.styles = _.chain(this.styles) - .map(function(style) { - var stylePath = that.options.styles[style]; - if (fs.existsSync(path.resolve(that.options.input, stylePath))) { - return stylePath; - } - return null; - }) - .compact() - .value(); -}; - -// Load all templates -Generator.prototype.loadTemplates = function() { - this.template = swig.compileFile( - this.plugins.template("site:page") || path.resolve(this.options.theme, 'templates/website/page.html') - ); - this.langsTemplate = swig.compileFile( - this.plugins.template("site:langs") || path.resolve(this.options.theme, 'templates/website/langs.html') - ); - this.glossaryTemplate = swig.compileFile( - this.plugins.template("site:glossary") || path.resolve(this.options.theme, 'templates/website/glossary.html') - ); -}; - -// Generate a template -Generator.prototype._writeTemplate = function(tpl, options, output, interpolate) { - var that = this; - - interpolate = interpolate || _.identity; - return Q() - .then(function(sections) { - return tpl(_.extend({ - styles: that.styles, - - revision: that.revision, - - title: that.options.title, - description: that.options.description, - - glossary: that.options.glossary, - - summary: that.options.summary, - allNavigation: that.options.navigation, - - plugins: that.plugins, - pluginsConfig: JSON.stringify(that.options.pluginsConfig), - htmlSnippet: _.partialRight(that.plugins.html, that, options), - - options: that.options - }, options)); - }) - .then(interpolate) - .then(function(html) { - return fs.writeFile( - output, - html - ); - }); -}; - -Generator.prototype.indexPage = function(lexed, pagePath) { - // Setup glossary indexer if not yet setup - if(!this.glossaryIndexer) { - this.glossaryIndexer = glossaryIndexer(this.options.glossary); - } - - this.indexer.add(lexed, pagePath); - this.glossaryIndexer.add(lexed, pagePath); - return Q(); -}; - -// Convert a markdown file into a normalized data set -Generator.prototype.prepareFile = function(content, _input) { - var that = this; - - var input = path.join(this.options.input, _input); - - var page = { - path: _input, - rawPath: input, - content: content, - progress: parse.progress(this.options.navigation, _input) - }; - - var _callHook = function(name) { - return that.callHook(name, page) - .then(function(_page) { - page = _page; - return page; - }); - }; - - return Q() - .then(function() { - // Send content to plugins - return _callHook("page:before"); - }) - .then(function() { - // Lex, parse includes and get - // Get HTML generated sections - return parse.page(page.content, { - // Local files path - dir: path.dirname(_input) || '/', - - // Output directory - outdir: path.dirname(_input) || '/', - - // Includer for templating - includer: parse.includer(that.options.variables, [ - path.dirname(_input) || '/', - path.join(that.options.input, '_includes'), - ], path.join, fs.readFileSync) - }); - }) - .then(function(parsed) { - page.lexed = parsed.lexed; - page.sections = parsed.sections; - - // Use plugin hook - return _callHook("page"); - }) - .then(function() { - return page; - }); -}; - -// Convert a markdown file to html -Generator.prototype.convertFile = function(content, _input) { - var that = this; - - var _output = _input.replace(".md", ".html"); - if (_output == "README.html") _output = "index.html"; - var output = path.join(this.options.output, _output); - var basePath = path.relative(path.dirname(output), this.options.output) || "."; - - // Bug fix for issue #493 which would occur when relative-links are 2-level or more deep in win32 - if (process.platform === 'win32') { - basePath = basePath.replace(/\\/g, '/'); - } - - return this.prepareFile(content, _input) - .then(function(page) { - // Index page in search - return that.indexPage(page.lexed, _output).thenResolve(page); - }) - .then(function(page) { - // Write file - return that._writeTemplate(that.template, { - progress: page.progress, - - _input: page.path, - content: page.sections, - - basePath: basePath, - staticBase: links.join(basePath, "gitbook"), - }, output, function(html) { - page.content = html; - - return that.callHook("page:after", page).get("content") - }); - }); -}; - -// Generate languages index -Generator.prototype.langsIndex = function(langs) { - var that = this; - var basePath = "."; - - return this._writeTemplate(this.langsTemplate, { - langs: langs.list, - - basePath: basePath, - staticBase: path.join(basePath, "gitbook"), - }, path.join(this.options.output, "index.html")) - .then(function() { - // Copy assets - return that.copyAssets(); - }); -}; - -// Generate glossary -Generator.prototype.writeGlossary = function() { - var that = this; - var basePath = "."; - - // No glossary - if (!this.glossaryIndexer) return Q(); - - // Transform the glossary to get term, description, files - var glossary = _.chain(this.glossaryIndexer.invertedIdx) - .map(function(links, id) { - var term = _.find(that.options.glossary, { 'id': id }); - - return { - id: id, - name: term.name, - description: term.description, - files: _.chain(links) - .map(function(link) { - var name = link.slice(0, -5); - - if (name == "index") { - name = "README"; - } - return that.options.navigation[name+".md"]; - }) - .sortBy("percent") - .value() - } - }) - .sortBy("name") - .value(); - - return this._writeTemplate(this.glossaryTemplate, { - glossaryIndex: glossary, - basePath: basePath, - staticBase: path.join(basePath, "gitbook"), - }, path.join(this.options.output, "GLOSSARY.html")); -}; - -// Copy assets -Generator.prototype.copyAssets = function() { - var that = this; - - // Copy gitbook assets - return fs.copy( - path.join(that.options.theme, "assets"), - path.join(that.options.output, "gitbook") - ) - - // Copy plugins assets - .then(function() { - return Q.all( - _.map(that.plugins.list, function(plugin) { - var pluginAssets = path.join(that.options.output, "gitbook/plugins/", plugin.name); - return plugin.copyAssets(pluginAssets, { - base: that.pluginAssetsBase - }); - }) - ); - }); -}; - -// Dump search index to disk -Generator.prototype.writeSearchIndex = function() { - return fs.writeFile( - path.join(this.options.output, 'search_index.json'), - this.indexer.dump() - ); -}; - -// Dump glossary index to disk -Generator.prototype.writeGlossaryIndex = function() { - if (!this.glossaryIndexer) return Q(); - - return fs.writeFile( - path.join(this.options.output, 'glossary_index.json'), - JSON.stringify(this.options.glossary) - ); -}; - - -Generator.prototype.finish = function() { - return this.copyAssets() - .then(this.copyCover) - .then(this.writeGlossary) - .then(this.writeGlossaryIndex) - .then(this.writeSearchIndex); -}; - -module.exports = Generator; diff --git a/lib/generate/site/search_indexer.js b/lib/generate/site/search_indexer.js deleted file mode 100644 index 7cfe29a..0000000 --- a/lib/generate/site/search_indexer.js +++ /dev/null @@ -1,71 +0,0 @@ -var Q = require("q"); -var _ = require("lodash"); - -var lunr = require('lunr'); -var kramed = require('kramed'); -var textRenderer = require('kramed-text-renderer'); - - -function Indexer() { - if(!(this instanceof Indexer)) { - return new Indexer(); - } - - _.bindAll(this); - - // Setup lunr index - this.idx = lunr(function () { - this.ref('url'); - - this.field('title', { boost: 10 }); - this.field('body'); - }); - - this.renderer = textRenderer(); -} - -Indexer.prototype.text = function(nodes) { - // Copy section - var section = _.toArray(nodes); - - // kramed's Render expects this, we don't use it yet - section.links = {}; - - var options = _.extend({}, kramed.defaults, { - renderer: this.renderer - }); - - return kramed.parser(section, options); -}; - -Indexer.prototype.addSection = function(path, section) { - var url = [path, section.id].join('#'); - - var title = this.text( - _.filter(section, {'type': 'heading'}) - ); - - var body = this.text( - _.omit(section, {'type': 'heading'}) - ); - - // Add to lunr index - this.idx.add({ - url: url, - title: title, - body: body, - }); -}; - -Indexer.prototype.add = function(lexedPage, url) { - var sections = lexedPage; - - _.map(sections, _.partial(this.addSection, url)); -}; - -Indexer.prototype.dump = function() { - return JSON.stringify(this.idx); -}; - -// Exports -module.exports = Indexer; diff --git a/lib/generate/template.js b/lib/generate/template.js deleted file mode 100644 index acfa580..0000000 --- a/lib/generate/template.js +++ /dev/null @@ -1,52 +0,0 @@ -var path = require("path"); -var swig = require('swig'); -var hljs = require('highlight.js'); - -var links = require('../utils/').links; -var pkg = require('../../package.json'); - -swig.setDefaults({ - locals: { - gitbook: { - version: pkg.version - } - } -}); - -// Swig filter for returning the count of lines in a code section -swig.setFilter('lines', function(content) { - return content.split('\n').length; -}); - -// Swig filter for returning a link to the associated html file of a markdown file -swig.setFilter('mdLink', function(link) { - var link = link.replace(".md", ".html"); - if (link == "README.html") link = "index.html"; - return link; -}); - -// Swig filter: highlight coloration -swig.setFilter('code', function(code, lang) { - try { - return hljs.highlight(lang, code).value; - } catch(e) { - return hljs.highlightAuto(code).value; - } -}); - -// Convert a level into a deep level -swig.setFilter('lvl', function(lvl) { - return lvl.split(".").length; -}); - -// Join path -swig.setFilter('pathJoin', function(base, _path) { - return links.join(base, _path); -}); - -// Is a link an absolute link -swig.setFilter('isExternalLink', function(link) { - return links.isExternal(link); -}); - -module.exports = swig; diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index ba240ce..0000000 --- a/lib/index.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - parse: require('./parse/'), - generate: require('./generate/') -}; - diff --git a/lib/parse/glossary.js b/lib/parse/glossary.js deleted file mode 100644 index 549e9fd..0000000 --- a/lib/parse/glossary.js +++ /dev/null @@ -1,48 +0,0 @@ -var _ = require('lodash'); -var kramed = require('kramed'); - -// Get all the pairs of header + paragraph in a list of nodes -function groups(nodes) { - // A list of next nodes - var next = nodes.slice(1).concat(null); - - return _.reduce(nodes, function(accu, node, idx) { - // Skip - if(!( - node.type === 'heading' && - (next[idx] && next[idx].type === 'paragraph') - )) { - return accu; - } - - // Add group - accu.push([ - node, - next[idx] - ]); - - return accu; - }, []); -} - -function parseGlossary(src) { - var nodes = kramed.lexer(src); - - return groups(nodes) - .map(function(pair) { - // Simplify each group to a simple object with name/description - return { - name: pair[0].text, - id: entryId(pair[0].text), - description: pair[1].text, - }; - }); -} - -// Normalizes a glossary entry's name to create an ID -function entryId(name) { - return name.toLowerCase(); -} - -module.exports = parseGlossary; -module.exports.entryId = entryId; diff --git a/lib/parse/include.js b/lib/parse/include.js deleted file mode 100644 index 483b184..0000000 --- a/lib/parse/include.js +++ /dev/null @@ -1,12 +0,0 @@ -var _ = require('lodash'); - -module.exports = function(markdown, includer) { - // Memoized include function (to cache lookups) - var _include = _.memoize(includer); - - return markdown.replace(/{{([\s\S]+?)}}/g, function(match, key) { - // If fails leave content as is - key = key.trim(); - return _include(key) || match; - }); -}; diff --git a/lib/parse/includer.js b/lib/parse/includer.js deleted file mode 100644 index f7f20e0..0000000 --- a/lib/parse/includer.js +++ /dev/null @@ -1,15 +0,0 @@ -// Return a fs inclduer -module.exports = function(ctx, folders, resolveFile, readFile) { - return function(name) { - return ctx[name] || - folders.map(function(folder) { - // Try including snippet from FS - try { - var fname = resolveFile(folder, name); - // Trim trailing newlines/space of imported snippets - return readFile(fname, 'utf8').trimRight(); - } catch(err) {} - }) - .filter(Boolean)[0]; - } -}; diff --git a/lib/parse/index.js b/lib/parse/index.js deleted file mode 100644 index 23471af..0000000 --- a/lib/parse/index.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - summary: require('./summary'), - glossary: require('./glossary'), - langs: require('./langs'), - page: require('./page'), - lex: require('./lex'), - progress: require('./progress'), - navigation: require('./navigation'), - readme: require('./readme'), - includer: require('./includer') -}; diff --git a/lib/parse/is_exercise.js b/lib/parse/is_exercise.js deleted file mode 100644 index 74ed753..0000000 --- a/lib/parse/is_exercise.js +++ /dev/null @@ -1,17 +0,0 @@ -var _ = require('lodash'); - -function isExercise(nodes) { - var codeType = { type: 'code' }; - - // Number of code nodes in section - var len = _.filter(nodes, codeType).length; - - return ( - // Got 3 or 4 code blocks - (len === 3 || len === 4) && - // Ensure all nodes are at the end - _.all(_.last(nodes, len), codeType) - ); -} - -module.exports = isExercise; diff --git a/lib/parse/is_quiz.js b/lib/parse/is_quiz.js deleted file mode 100644 index 3322ff0..0000000 --- a/lib/parse/is_quiz.js +++ /dev/null @@ -1,87 +0,0 @@ -var _ = require('lodash'); - -function isQuizNode(node) { - return (/^[(\[][ x][)\]]/).test(node.text || node); -} - -function isTableQuestion(nodes) { - var block = questionBlock(nodes); - return ( - block.length === 1 && - block[0].type === 'table' && - _.all(block[0].cells[0].slice(1), isQuizNode) - ); -} - -function isListQuestion(nodes) { - var block = questionBlock(nodes); - // Counter of when we go in and out of lists - var inlist = 0; - // Number of lists we found - var lists = 0; - // Elements found outside a list - var outsiders = 0; - // Ensure that we have nothing except lists - _.each(block, function(node) { - if(node.type === 'list_start') { - inlist++; - } else if(node.type === 'list_end') { - inlist--; - lists++; - } else if(inlist === 0) { - // Found non list_start or list_end whilst outside a list - outsiders++; - } - }); - return lists > 0 && outsiders === 0; -} - -function isQuestion(nodes) { - return isListQuestion(nodes) || isTableQuestion(nodes); -} - -// Remove (optional) paragraph header node and blockquote -function questionBlock(nodes) { - return nodes.slice( - nodes[0].type === 'paragraph' ? 1 : 0, - _.findIndex(nodes, { type: 'blockquote_start' }) - ); -} - -function splitQuestions(nodes) { - // Represents nodes in current question - var buffer = []; - return _.reduce(nodes, function(accu, node) { - // Add node to buffer - buffer.push(node); - - // Flush buffer once we hit the end of a question - if(node.type === 'blockquote_end') { - accu.push(buffer); - // Clear buffer - buffer = []; - } - - return accu; - }, []); -} - -function isQuiz(nodes) { - // Extract potential questions - var questions = splitQuestions( - // Skip quiz title if there - nodes.slice( - (nodes[0] && nodes[0].type) === 'paragraph' ? 1 : 0 - ) - ); - - // Nothing that looks like questions - if(questions.length === 0) { - return false; - } - - // Ensure all questions are correctly structured - return _.all(questions, isQuestion); -} - -module.exports = isQuiz; diff --git a/lib/parse/langs.js b/lib/parse/langs.js deleted file mode 100644 index 01b7c8c..0000000 --- a/lib/parse/langs.js +++ /dev/null @@ -1,25 +0,0 @@ -var _ = require("lodash"); -var parseEntries = require("./summary").entries; - - -var parseLangs = function(content) { - var entries = parseEntries(content); - - return { - list: _.chain(entries) - .filter(function(entry) { - return Boolean(entry.path); - }) - .map(function(entry) { - return { - title: entry.title, - path: entry.path, - lang: entry.path.replace("/", "") - }; - }) - .value() - }; -}; - - -module.exports = parseLangs;
\ No newline at end of file diff --git a/lib/parse/lex.js b/lib/parse/lex.js deleted file mode 100644 index 3391acf..0000000 --- a/lib/parse/lex.js +++ /dev/null @@ -1,79 +0,0 @@ -var _ = require('lodash'); -var kramed = require('kramed'); - -var isExercise = require('./is_exercise'); -var isQuiz = require('./is_quiz'); - -// Split a page up into sections (lesson, exercises, ...) -function splitSections(nodes) { - var section = []; - - return _.reduce(nodes, function(sections, el) { - if(el.type === 'hr') { - sections.push(section); - section = []; - } else { - section.push(el); - } - - return sections; - }, []).concat([section]); // Add remaining nodes -} - -// What is the type of this section -function sectionType(nodes, idx) { - if(isExercise(nodes)) { - return 'exercise'; - } else if(isQuiz(nodes)) { - return 'quiz'; - } - - return 'normal'; -} - -// Generate a uniqueId to identify this section in our code -function sectionId(section, idx) { - return _.uniqueId('gitbook_'); -} - -function lexPage(src) { - // Lex file - var nodes = kramed.lexer(src); - - return _.chain(splitSections(nodes)) - .map(function(section, idx) { - // Detect section type - section.type = sectionType(section, idx); - return section; - }) - .map(function(section, idx) { - // Give each section an ID - section.id = sectionId(section, idx); - return section; - - }) - .filter(function(section) { - return !_.isEmpty(section); - }) - .reduce(function(sections, section) { - var last = _.last(sections); - - // Merge normal sections together - if(last && last.type === section.type && last.type === 'normal') { - last.push.apply(last, [{'type': 'hr'}].concat(section)); - } else { - // Add to list of sections - sections.push(section); - } - - return sections; - }, []) - .map(function(section) { - section.links = nodes.links; - return section; - }) - .value(); -} - -// Exports -module.exports = lexPage; diff --git a/lib/parse/navigation.js b/lib/parse/navigation.js deleted file mode 100644 index ae4eb9d..0000000 --- a/lib/parse/navigation.js +++ /dev/null @@ -1,64 +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, then add in default README node if ndeeded etc ... - var navNodes = flattenChapters(summary.chapters); - var prevNodes = [null].concat(navNodes.slice(0, -1)); - var nextNodes = navNodes.slice(1).concat([null]); - - // Mapping of prev/next for a give path - var mapping = _.chain(_.zip(navNodes, prevNodes, nextNodes)) - .map(function(nodes) { - var current = nodes[0], prev = nodes[1], next = nodes[2]; - - // Skip if no path - if(!current.path) return null; - - return [current.path, { - title: current.title, - prev: prev, - next: next, - level: current.level, - }]; - }) - .filter() - .object() - .value(); - - // Filter for only files we want - if(files) { - return _.pick(mapping, files); - } - - return mapping; -} - - -// Exports -module.exports = navigation; diff --git a/lib/parse/page.js b/lib/parse/page.js deleted file mode 100644 index 5fb2081..0000000 --- a/lib/parse/page.js +++ /dev/null @@ -1,160 +0,0 @@ -var _ = require('lodash'); -var kramed = require('kramed'); -var hljs = require('highlight.js'); - -var lex = require('./lex'); -var renderer = require('./renderer'); - -var include = require('./include'); -var lnormalize = require('../utils/lang').normalize; - - - -// Render a section using our custom renderer -function render(section, _options) { - // Copy section - var links = section.links || {}; - section = _.toArray(section); - section.links = links; - - // Build options using defaults and our custom renderer - var options = _.extend({}, kramed.defaults, { - renderer: renderer(null, _options), - - // Synchronous highlighting with highlight.js - highlight: function (code, lang) { - if(!lang) return code; - - // Normalize lang - lang = lnormalize(lang); - - try { - return hljs.highlight(lang, code).value; - } catch(e) { } - - return code; - } - }); - - return kramed.parser(section, options); -} - -function quizQuestion(node) { - if (node.text) { - node.text = node.text.replace(/^([\[(])x([\])])/, "$1 $2"); - } else { - return node.replace(/^([\[(])x([\])])/, "$1 $2"); - } -} - -function parsePage(src, options) { - options = options || {}; - - // Lex if not already lexed - var parsed = { - lexed: (_.isArray(src) ? src : lex(include(src, options.includer || function() { return undefined; }))) - }; - parsed.sections = parsed.lexed.map(function(section) { - // Transform given type - if(section.type === 'exercise') { - var nonCodeNodes = _.reject(section, { - 'type': 'code' - }); - - var codeNodes = _.filter(section, { - 'type': 'code' - }); - - // Languages in code blocks - var langs = _.pluck(codeNodes, 'lang').map(lnormalize); - - // Check that they are all the same - var validLangs = _.all(_.map(langs, function(lang) { - return lang && lang === langs[0]; - })); - - // Main language - var lang = validLangs ? langs[0] : null; - - return { - id: section.id, - type: section.type, - content: render(nonCodeNodes, options), - lang: lang, - code: { - base: codeNodes[0].text, - solution: codeNodes[1].text, - validation: codeNodes[2].text, - // Context is optional - context: codeNodes[3] ? codeNodes[3].text : null, - } - }; - } else if (section.type === 'quiz') { - var quiz = [], question, foundFeedback = false; - var nonQuizNodes = section[0].type === 'paragraph' && section[1].type !== 'list_start' ? [section[0]] : []; - var quizNodes = section.slice(0); - quizNodes.splice(0, nonQuizNodes.length); - - for (var i = 0; i < quizNodes.length; i++) { - var node = quizNodes[i]; - - if (question && (((node.type === 'list_end' || node.type === 'blockquote_end') && i === quizNodes.length - 1) - || node.type === 'table' || (node.type === 'paragraph' && !foundFeedback))) { - quiz.push({ - base: render(question.questionNodes, options), - solution: render(question.solutionNodes, options), - feedback: render(question.feedbackNodes, options) - }); - } - - if (node.type === 'table' || (node.type === 'paragraph' && !foundFeedback)) { - question = { questionNodes: [], solutionNodes: [], feedbackNodes: [] }; - } - - if (node.type === 'blockquote_start') { - foundFeedback = true; - } else if (node.type === 'blockquote_end') { - foundFeedback = false; - } - - if (node.type === 'table') { - question.solutionNodes.push(_.cloneDeep(node)); - node.cells = node.cells.map(function(row) { - return row.map(quizQuestion); - }); - question.questionNodes.push(node); - } else if (!/blockquote/.test(node.type)) { - if (foundFeedback) { - question.feedbackNodes.push(node); - } else if (node.type === 'paragraph' || node.type === 'text'){ - question.solutionNodes.push(_.cloneDeep(node)); - quizQuestion(node); - question.questionNodes.push(node); - } else { - question.solutionNodes.push(node); - question.questionNodes.push(node); - } - } - } - - return { - id: section.id, - type: section.type, - content: render(nonQuizNodes, options), - quiz: quiz - }; - } - - // Render normal pages - return { - id: section.id, - type: section.type, - content: render(section, options) - }; - }); - - return parsed; -} - -// Exports -module.exports = parsePage; diff --git a/lib/parse/progress.js b/lib/parse/progress.js deleted file mode 100644 index 10a06d2..0000000 --- a/lib/parse/progress.js +++ /dev/null @@ -1,47 +0,0 @@ -var _ = require("lodash"); - -// Returns from a navigation and a current file, a snapshot of current detailed state -var calculProgress = function(navigation, current) { - var n = _.size(navigation); - var percent = 0, prevPercent = 0, currentChapter = null; - var done = true; - - var chapters = _.chain(navigation) - .map(function(nav, path) { - nav.path = path; - return nav; - }) - .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;
\ No newline at end of file diff --git a/lib/parse/readme.js b/lib/parse/readme.js deleted file mode 100644 index 9d8f552..0000000 --- a/lib/parse/readme.js +++ /dev/null @@ -1,45 +0,0 @@ -var _ = require('lodash'); -var kramed = require('kramed'); -var textRenderer = require('kramed-text-renderer'); - -function extractFirstNode(nodes, nType) { - return _.chain(nodes) - .filter(function(node) { - return node.type == nType; - }) - .pluck("text") - .first() - .value(); -} - - -function parseReadme(src) { - var nodes, title, description; - var renderer = textRenderer(); - - // Parse content - nodes = kramed.lexer(src); - - title = extractFirstNode(nodes, "heading") || ''; - description = extractFirstNode(nodes, "paragraph") || ''; - - var convert = _.compose( - function(text) { - return _.unescape(text.replace(/(\r\n|\n|\r)/gm, "")); - }, - function(text) { - return kramed.parse(text, _.extend({}, kramed.defaults, { - renderer: renderer - })); - } - ); - - return { - title: convert(title), - description: convert(description) - }; -} - - -// Exports -module.exports = parseReadme; diff --git a/lib/parse/renderer.js b/lib/parse/renderer.js deleted file mode 100644 index 5b6a79d..0000000 --- a/lib/parse/renderer.js +++ /dev/null @@ -1,141 +0,0 @@ -var url = require('url'); -var _ = require('lodash'); -var inherits = require('util').inherits; -var links = require('../utils').links; -var kramed = require('kramed'); - -var rendererId = 0; - -function GitBookRenderer(options, extra_options) { - if(!(this instanceof GitBookRenderer)) { - return new GitBookRenderer(options, extra_options); - } - GitBookRenderer.super_.call(this, options); - - this._extra_options = extra_options; - this.quizRowId = 0; - this.id = rendererId++; - this.quizIndex = 0; -} -inherits(GitBookRenderer, kramed.Renderer); - -GitBookRenderer.prototype._unsanitized = function(href) { - var prot = ''; - try { - prot = decodeURIComponent(unescape(href)) - .replace(/[^\w:]/g, '') - .toLowerCase(); - - } catch (e) { - return true; - } - - if(prot.indexOf('javascript:') === 0) { - return true; - } - - return false; -}; - -GitBookRenderer.prototype.link = function(href, title, text) { - // Our "fixed" href - var _href = href; - - // Don't build if it looks malicious - if (this.options.sanitize && this._unsanitized(href)) { - return text; - } - - // Parsed version of the url - var parsed = url.parse(href); - var o = this._extra_options; - var extname = parsed.path? _.last(parsed.path.split(".")) : ""; - - // Relative link, rewrite it to point to github repo - if(links.isRelative(_href) && extname == "md") { - _href = links.toAbsolute(_href, o.dir || "./", o.outdir || "./"); - _href = _href.replace(".md", ".html"); - } - - // Generate HTML for link - var out = '<a href="' + _href + '"'; - // Title if no null - if (title) { - out += ' title="' + title + '"'; - } - // Target blank if external - if(parsed.protocol) { - out += ' target="_blank"'; - } - out += '>' + text + '</a>'; - return out; -}; - -GitBookRenderer.prototype.image = function(href, title, text) { - // Our "fixed" href - var _href = href; - - // Parsed version of the url - var parsed = url.parse(href); - - // Options - var o = this._extra_options; - - // Relative image, rewrite it depending output - if(links.isRelative(href) && o && o.dir && o.outdir) { - // o.dir: directory parent of the file currently in rendering process - // o.outdir: directory parent from the html output - - _href = links.toAbsolute(_href, o.dir, o.outdir); - } - - return GitBookRenderer.super_.prototype.image.call(this, _href, title, text); -}; - -GitBookRenderer.prototype.tablerow = function(content) { - this.quizRowId += 1; - return GitBookRenderer.super_.prototype.tablerow(content); -}; - -var fieldRegex = /^([(\[])([ x])[\])]/; -GitBookRenderer.prototype._createCheckboxAndRadios = function(text) { - var match = fieldRegex.exec(text); - if (!match) { - return text; - } - //fix radio input uncheck failed - var quizFieldName='quiz-row-' + this.id + '-' + this.quizRowId ; - var quizIdentifier = quizFieldName + '-' + this.quizIndex++; - var field = "<input name='" + quizFieldName + "' id='" + quizIdentifier + "' type='"; - field += match[1] === '(' ? "radio" : "checkbox"; - field += match[2] === 'x' ? "' checked/>" : "'/>"; - var splittedText = text.split(fieldRegex); - var length = splittedText.length; - var label = '<label class="quiz-label" for="' + quizIdentifier + '">' + splittedText[length - 1] + '</label>'; - return text.replace(fieldRegex, field).replace(splittedText[length - 1], label); -}; - -GitBookRenderer.prototype.tablecell = function(content, flags) { - return GitBookRenderer.super_.prototype.tablecell(this._createCheckboxAndRadios(content), flags); -}; - -GitBookRenderer.prototype.listitem = function(text) { - return GitBookRenderer.super_.prototype.listitem(this._createCheckboxAndRadios(text)); -}; - -GitBookRenderer.prototype.code = function(code, lang, escaped) { - return GitBookRenderer.super_.prototype.code.call( - this, - code, - lang, - escaped - ); -}; - -GitBookRenderer.prototype.heading = function(text, level, raw) { - var id = this.options.headerPrefix + raw.toLowerCase().replace(/[^\w -]+/g, '').replace(/ /g, '-'); - return '<h' + level + ' id="' + id + '">' + text + '</h' + level + '>\n'; -}; - -// Exports -module.exports = GitBookRenderer; diff --git a/lib/parse/summary.js b/lib/parse/summary.js deleted file mode 100644 index 2fdec8a..0000000 --- a/lib/parse/summary.js +++ /dev/null @@ -1,167 +0,0 @@ -var _ = require('lodash'); -var kramed = require('kramed'); - - -// Utility function for splitting a list into groups -function splitBy(list, starter, ender) { - var starts = 0; - var ends = 0; - var group = []; - - // Groups - return _.reduce(list, function(groups, value) { - // Ignore start and end delimiters in resulted groups - if(starter(value)) { - starts++; - } else if(ender(value)) { - ends++; - } - - // Add current value to group - group.push(value); - - // We've got a matching - if(starts === ends && starts !== 0) { - // Add group to end groups - // (remove starter and ender token) - groups.push(group.slice(1, -1)); - - // Reset group - group = []; - } - - return groups; - }, []); -} - -function listSplit(nodes, start_type, end_type) { - return splitBy(nodes, function(el) { - return el.type === start_type; - }, function(el) { - return el.type === end_type; - }); -} - -// Get the biggest list -// out of a list of kramed nodes -function filterList(nodes) { - return _.chain(nodes) - .toArray() - .rest(function(el) { - // Get everything after list_start - return el.type !== 'list_start'; - }) - .reverse() - .rest(function(el) { - // Get everything after list_end (remember we're reversed) - return el.type !== 'list_end'; - }) - .reverse() - .value().slice(1, -1); -} - -function skipSpace(nodes) { - return _.filter(nodes, function(node) { - return node && node.type != 'space'; - }); -} - -function correctLoose(nodes) { - return _.map(nodes, function(node) { - // Return normal nodes - if(!node || node.type != 'loose_item_start') { - return node - } - - // Correct loose items - node.type = 'list_item_start'; - - return node; - }) -} - -// Parses an Article or Chapter title -// supports extracting links -function parseTitle(src, nums) { - // Check if it's a link - var matches = kramed.InlineLexer.rules.link.exec(src); - - var level = nums.join('.'); - - // Not a link, return plain text - if(!matches) { - return { - title: src, - level: level, - path: null, - }; - } - - return { - title: matches[1], - level: level, - - // Normalize path - // 1. Convert Window's "\" to "/" - // 2. Remove leading "/" if exists - path: matches[2].replace(/\\/g, '/').replace(/^\/+/, ''), - }; -} - -function parseChapter(nodes, nums) { - // Convert single number to an array - nums = _.isArray(nums) ? nums : [nums]; - - return _.extend(parseTitle(_.first(nodes).text, nums), { - articles: _.map(listSplit(filterList(nodes), 'list_item_start', 'list_item_end'), function(nodes, i) { - return parseChapter(nodes, nums.concat(i + 1)); - }) - }); -} - -function defaultChapterList(chapterList) { - var first = _.first(chapterList); - - // Check if introduction node was specified in SUMMARY.md - if (first) { - var chapter = parseChapter(first, [0]); - - // Already have README node, we're good to go - if(chapter.path === 'README.md') { - return chapterList; - } - } - - // It wasn't specified, so add in default - return [ - [ { type: 'text', text: '[Introduction](README.md)' } ] - ].concat(chapterList); -} - -function listGroups(src) { - var nodes = kramed.lexer(src); - - // Get out groups of lists - return listSplit( - filterList(correctLoose(skipSpace(nodes))), - 'list_item_start', 'list_item_end' - ); -} - -function parseSummary(src) { - // Split out chapter sections - var chapters = defaultChapterList(listGroups(src)); - - return { - chapters: chapters.map(parseChapter) - }; -} - -function parseEntries (src) { - return listGroups(src).map(parseChapter); -} - - -// Exports -module.exports = parseSummary; -module.exports.entries = parseEntries; diff --git a/lib/utils/index.js b/lib/utils/index.js deleted file mode 100644 index dbc4087..0000000 --- a/lib/utils/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - lang: require('./lang'), - links: require('./links') -}; diff --git a/lib/utils/lang.js b/lib/utils/lang.js deleted file mode 100644 index 9da737b..0000000 --- a/lib/utils/lang.js +++ /dev/null @@ -1,19 +0,0 @@ -var MAP = { - 'py': 'python', - 'js': 'javascript', - 'rb': 'ruby', - 'csharp': 'cs', -}; - -function normalize(lang) { - if(!lang) { return null; } - - var lower = lang.toLowerCase(); - return MAP[lower] || lower; -} - -// Exports -module.exports = { - normalize: normalize, - MAP: MAP -}; diff --git a/lib/utils/links.js b/lib/utils/links.js deleted file mode 100644 index b4d2fb7..0000000 --- a/lib/utils/links.js +++ /dev/null @@ -1,60 +0,0 @@ -var url = require('url'); -var path = require('path'); - -// Is the link an external link -var isExternal = function(href) { - try { - return Boolean(url.parse(href).protocol); - } catch(err) { } - - return false; -}; - -// Return true if the link is relative -var isRelative = function(href) { - try { - var parsed = url.parse(href); - - return !parsed.protocol && parsed.path && parsed.path[0] != '/'; - } catch(err) {} - - return true; -}; - -// Relative to absolute path -// dir: directory parent of the file currently in rendering process -// outdir: directory parent from the html output - -var toAbsolute = function(_href, dir, outdir) { - // Absolute file in source - _href = path.join(dir, _href); - - // make it relative to output - _href = path.relative(outdir, _href); - - if (process.platform === 'win32') { - _href = _href.replace(/\\/g, '/'); - } - - return _href; -}; - -// Join links - -var join = function() { - var _href = path.join.apply(path, arguments); - - if (process.platform === 'win32') { - _href = _href.replace(/\\/g, '/'); - } - - return _href; -}; - - -module.exports = { - isRelative: isRelative, - isExternal: isExternal, - toAbsolute: toAbsolute, - join: join -}; diff --git a/lib/utils/string.js b/lib/utils/string.js deleted file mode 100644 index 54c4c66..0000000 --- a/lib/utils/string.js +++ /dev/null @@ -1,26 +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 === false) return null; - if (value === true) return key; - return key+"="+escapeShellArg(value); - }) - .compact() - .value() - .join(" "); -} - -module.exports = { - escapeShellArg: escapeShellArg, - optionsToShellArgs: optionsToShellArgs -}; |