diff --git a/bin/gitbook.js b/bin/gitbook.js index 6e29986b1..3abeafdd1 100755 --- a/bin/gitbook.js +++ b/bin/gitbook.js @@ -1,6 +1,7 @@ #! /usr/bin/env node // Requires +var Q = require('q'); var _ = require('lodash'); var path = require('path'); var prog = require('commander'); @@ -10,9 +11,14 @@ var fs = require('fs'); var pkg = require('../package.json'); var generate = require("../lib/generate"); var parse = require("../lib/parse"); +var generators = require("../lib/generate").generators; var utils = require('./utils'); +var logError = function(err) { + console.log(err.message || err); + return Q.reject(err); +}; // General options prog @@ -25,6 +31,7 @@ prog .command('build [source_dir]') .description('Build a gitbook from a directory') .option('-o, --output ', 'Path to output directory, defaults to ./_book') +.option('-g, --generator ', 'Change generator, defaults to site, availables are: '+_.keys(generators).join(", ")) .option('-t, --title ', 'Name of the book to generate, defaults to repo name') .option('-i, --intro ', 'Description of the book to generate') .option('-g, --github ', 'ID of github repo like : username/repo') @@ -55,20 +62,19 @@ prog var title = options.title || utils.titleCase(repo); return generate.folder( - dir, - outputDir, { + input: dir, + output: outputDir, title: title, description: options.intro, - github: githubID + github: githubID, + generator: options.generator } ); }) .then(function(output) { console.log("Successfuly built !"); - }, function(err) { - throw err; - }) + }, logError) .then(_.constant(outputDir)); }); @@ -85,15 +91,13 @@ prog .then(function(outputDir) { console.log(); console.log('Starting server ...'); - return utils.serveDir(outputDir, options.port); + return utils.serveDir(outputDir, options.port) + .fail(logError); }) .then(function() { console.log('Serving book on http://localhost:'+options.port); console.log(); console.log('Press CTRL+C to quit ...'); - }) - .fail(function(err) { - console.error(err); }); }); diff --git a/lib/generate/generator.js b/lib/generate/generator.js new file mode 100644 index 000000000..3fe080e90 --- /dev/null +++ b/lib/generate/generator.js @@ -0,0 +1,32 @@ +var path = require("path"); +var Q = require("q"); +var fs = require("./fs"); + +var BaseGenerator = function(options) { + this.options = options; +}; + +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.finish = function() { + return Q.reject(new Error("Could not finish generation")); +}; + + + +module.exports = BaseGenerator; \ No newline at end of file diff --git a/lib/generate/generator_json.js b/lib/generate/generator_json.js new file mode 100644 index 000000000..b331bfd4c --- /dev/null +++ b/lib/generate/generator_json.js @@ -0,0 +1,47 @@ +var util = require("util"); +var path = require("path"); +var Q = require("q"); + +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, { + repo: that.options.githubId, + dir: path.dirname(input) || '/' + }); + }) + .then(function(sections) { + json.sections = sections; + }) + .then(function() { + return fs.writeFile( + path.join(that.options.output, input.replace(".md", ".json")), + JSON.stringify(json, null, 4) + ); + }); +}; + +Generator.prototype.finish = function() { + // ignore +}; + +module.exports = Generator; \ No newline at end of file diff --git a/lib/generate/generator_site.js b/lib/generate/generator_site.js new file mode 100644 index 000000000..7d40a1a4b --- /dev/null +++ b/lib/generate/generator_site.js @@ -0,0 +1,91 @@ +var util = require("util"); +var path = require("path"); +var Q = require("q"); +var swig = require('swig'); + +var fs = require("./fs"); +var parse = require("../parse"); +var BaseGenerator = require("./generator"); + +// 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) { + return link.replace(".md", ".html"); +}); + +var Generator = function() { + BaseGenerator.apply(this, arguments); + + // Load base template + this.template = swig.compileFile(path.resolve(__dirname, '../../templates/page.html')); +}; +util.inherits(Generator, BaseGenerator); + + +// Convert a markdown file to html +Generator.prototype.convertFile = function(content, _input) { + var that = this; + var progress = parse.progress(this.options.navigation, _input); + + _output = _input.replace(".md", ".html"); + + var input = path.join(this.options.input, _input); + var output = path.join(this.options.output, _output); + var basePath = path.relative(path.dirname(output), this.options.output) || "."; + + return Q() + .then(function() { + return parse.page(content, { + repo: that.options.githubId, + dir: path.dirname(input) || '/' + }); + }) + .then(function(sections) { + return that.template({ + title: that.options.title, + description: that.options.description, + + githubAuthor: that.options.github.split("/")[0], + githubId: that.options.github, + githubHost: that.options.githubHost, + + summary: that.options.summary, + allNavigation: that.options.navigation, + progress: progress, + + _input: _input, + content: sections, + + basePath: basePath, + staticBase: path.join(basePath, "gitbook"), + }); + }) + .then(function(html) { + return fs.writeFile( + output, + html + ); + }); +}; + +// Symlink index.html and copy assets +Generator.prototype.finish = function() { + var that = this; + + return fs.symlink( + path.join(that.options.output, 'README.html'), + path.join(that.options.output, 'index.html') + ) + .then(function() { + return fs.copy( + path.join(__dirname, "../../assets/static"), + path.join(that.options.output, "gitbook") + ); + }); +}; + +module.exports = Generator; \ No newline at end of file diff --git a/lib/generate/index.js b/lib/generate/index.js index ee5ffc46e..95f325ce4 100644 --- a/lib/generate/index.js +++ b/lib/generate/index.js @@ -5,12 +5,24 @@ var path = require("path"); var fs = require("./fs"); var parse = require("../parse"); -var template = require("./template"); -var generate = function(root, output, options) { - var files, summary, navigation, tpl; +var generators = { + "site": require("./generator_site"), + "json": require("./generator_json") +}; + +var generate = function(options) { + var generator = null; + var files; options = _.defaults(options || {}, { + // Folders to use + input: null, + output: null, + + // Output generator + generator: "site", + // Book title, keyword, description title: null, description: "Book generated using GitBook", @@ -20,20 +32,24 @@ var generate = function(root, output, options) { githubHost: 'https://github.com/' }); - if (!options.github || !options.title) { - return Q.reject(new Error("Need options.github and options.title")); + if (!options.github || !options.title || !options.input || !options.output) { + return Q.reject(new Error("Need options: github, title, input, output")); + } + + if (!generators[options.generator]) { + return Q.reject(new Error("Invalid generator (availables are: "+_.keys(generators).join(", "))); } // Clean output folder - return fs.remove(output) + return fs.remove(options.output) .then(function() { - return fs.mkdirp(output); + return fs.mkdirp(options.output); }) // List all files in the repository .then(function() { - return fs.list(root); + return fs.list(options.input); }) // Check repository is valid @@ -47,32 +63,18 @@ var generate = function(root, output, options) { // Get summary .then(function() { - return fs.readFile(path.join(root, "SUMMARY.md"), "utf-8") + return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8") .then(function(_summary) { - summary = parse.summary(_summary); + options.summary = parse.summary(_summary); // Parse navigation - navigation = parse.navigation(summary); + options.navigation = parse.navigation(options.summary); }); }) - // Create template + // Create the generator .then(function() { - tpl = template({ - root: root, - output: output, - locals: { - title: options.title, - description: options.description, - - githubAuthor: options.github.split("/")[0], - githubId: options.github, - githubHost: options.githubHost, - - summary: summary, - allNavigation: navigation - } - }); + generator = new generators[options.generator](options); }) // Copy file and replace markdown file @@ -81,49 +83,29 @@ var generate = function(root, output, options) { _.chain(files) .map(function(file) { if (!file) return; - var _html = file.replace(".md", ".html"); - // Folder if (file[file.length -1] == "/") { - return fs.mkdirp( - path.join(output, file) - ); - } - - // Markdown file (only from the summary) - else if (path.extname(file) == ".md" && navigation[_html] != null) { - return tpl(file, file.replace(".md", ".html")); - } - - // Copy file - else { - return fs.copy( - path.join(root, file), - path.join(output, file) - ); + 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() ); }) - // Symlink index.html to README.html + // Finish gneration .then(function() { - return fs.symlink( - path.join(output, 'README.html'), - path.join(output, 'index.html') - ); - }) - - // Copy assets - .then(function() { - return fs.copy( - path.join(__dirname, "../../assets/static"), - path.join(output, "gitbook") - ); + return generator.finish(); }); }; module.exports = { + generators: generators, folder: generate }; diff --git a/lib/generate/template.js b/lib/generate/template.js deleted file mode 100644 index d4e8cae25..000000000 --- a/lib/generate/template.js +++ /dev/null @@ -1,66 +0,0 @@ -var swig = require('swig'); -var path = require('path'); -var _ = require("lodash"); - -var parse = require("../parse"); - -var fs = require('./fs'); - -swig.setFilter('lines', function(content) { - return content.split('\n').length; -}); - -// return a templeter for page -var initTemplate = function(options) { - var tpl = swig.compileFile(path.resolve(__dirname, '../../templates/page.html')); - - options = _.defaults(options || {}, { - // Base folder for input - root: "./", - - // Base folder for output - output: "./", - - // Locals for templates - locals: {} - }); - - return function(input, output, local) { - var _input = input, _output = output; - input = path.join(options.root, input); - output = path.join(options.output, output); - - var basePath = path.relative(path.dirname(output), options.output) || "."; - - // Read markdown file - return fs.readFile(input, "utf-8") - - // Parse sections - .then(function(markdown) { - return parse.page(markdown, { - repo: options.locals.githubId, - dir: path.dirname(_input) || '/' - }); - }) - - //Calcul template - .then(function(sections) { - return tpl( - _.extend(local || {}, options.locals, { - _input: _input, - content: sections, - basePath: basePath, - staticBase: path.join(basePath, "gitbook"), - progress: parse.progress(options.locals.allNavigation, _output) - }) - ); - }) - - // Write html - .then(function(html) { - return fs.writeFile(output, html); - }) - } -}; - -module.exports = initTemplate; \ No newline at end of file diff --git a/lib/parse/navigation.js b/lib/parse/navigation.js index 145c87396..bba25cec8 100644 --- a/lib/parse/navigation.js +++ b/lib/parse/navigation.js @@ -1,15 +1,15 @@ var _ = require('lodash'); // Cleans up an article/chapter object -// remove 'articles' and '_path' attributes +// remove 'articles' attributes function clean(obj) { - return obj && _.omit(obj, ['articles', '_path']); + return obj && _.omit(obj, ['articles']); } // Returns from a summary a map of /* { - "file/path.html": { + "file/path.md": { prev: ..., next: ..., }, @@ -25,7 +25,7 @@ function navigation(summary, files) { // Special README nav var README_NAV = { - path: 'README.html', + path: 'README.md', title: 'Introduction', }; @@ -69,7 +69,7 @@ function navigation(summary, files) { }); // Hack for README.html - mapping['README.html'] = { + mapping['README.md'] = { title: README_NAV.title, prev: null, next: clean(summary.chapters[0]), diff --git a/lib/parse/summary.js b/lib/parse/summary.js index 0995c4055..7725b5901 100644 --- a/lib/parse/summary.js +++ b/lib/parse/summary.js @@ -82,10 +82,7 @@ function parseTitle(src, nums) { level: level, // Replace .md references with .html - path: matches[2].replace(/\.md$/, '.html'), - - // Original, non normalized path - _path: matches[2], + path: matches[2], }; } diff --git a/templates/includes/book/footer.html b/templates/includes/book/footer.html index 48f4bf90f..1112b14a2 100644 --- a/templates/includes/book/footer.html +++ b/templates/includes/book/footer.html @@ -1,10 +1,10 @@
{% for p in progress.chapters %} - + {% endfor %}
\ No newline at end of file diff --git a/templates/includes/book/summary.html b/templates/includes/book/summary.html index d74fb1842..996ff3d72 100644 --- a/templates/includes/book/summary.html +++ b/templates/includes/book/summary.html @@ -16,7 +16,7 @@ {% for item in summary.chapters %}
  • {% if item.path %} - + {{ item.level }}) {{ item.title }} {% else %} @@ -27,7 +27,7 @@ {% for article in item.articles %}
  • {% if article.path %} - + {{ article.level }}) {{ article.title }} {% else %} diff --git a/templates/layout.html b/templates/layout.html index c0f52cb75..47184b3e5 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -13,10 +13,10 @@ {% if progress.current.next and progress.current.next.path %} - + {% endif %} {% if progress.current.prev and progress.current.prev.path %} - + {% endif %} diff --git a/test/navigation.js b/test/navigation.js index 23d98ac7f..b0e5b98dc 100644 --- a/test/navigation.js +++ b/test/navigation.js @@ -13,57 +13,57 @@ var LEXED = summary(CONTENT); describe('Summary navigation', function() { it('should provide next & prev entries for a file', function() { var nav = navigation(LEXED, [ - 'README.html', - 'chapter-1/README.html', - 'chapter-1/ARTICLE1.html', - 'chapter-1/ARTICLE2.html', - 'chapter-2/README.html', + 'README.md', + 'chapter-1/README.md', + 'chapter-1/ARTICLE1.md', + 'chapter-1/ARTICLE2.md', + 'chapter-2/README.md', ]); // Make sure it found the files we gave it - assert(nav['README.html']); - assert(nav['chapter-1/README.html']); - assert(nav['chapter-1/ARTICLE1.html']); - assert(nav['chapter-1/ARTICLE2.html']); - assert(nav['chapter-2/README.html']); + assert(nav['README.md']); + assert(nav['chapter-1/README.md']); + assert(nav['chapter-1/ARTICLE1.md']); + assert(nav['chapter-1/ARTICLE2.md']); + assert(nav['chapter-2/README.md']); - assert.equal(nav['README.html'].prev, null); - assert.equal(nav['README.html'].next.path, 'chapter-1/README.html'); + assert.equal(nav['README.md'].prev, null); + assert.equal(nav['README.md'].next.path, 'chapter-1/README.md'); - assert.equal(nav['chapter-1/README.html'].prev.path, 'README.html'); - assert.equal(nav['chapter-1/README.html'].next.path, 'chapter-1/ARTICLE1.html'); + assert.equal(nav['chapter-1/README.md'].prev.path, 'README.md'); + assert.equal(nav['chapter-1/README.md'].next.path, 'chapter-1/ARTICLE1.md'); - assert.equal(nav['chapter-1/ARTICLE1.html'].prev.path, 'chapter-1/README.html'); - assert.equal(nav['chapter-1/ARTICLE1.html'].next.path, 'chapter-1/ARTICLE2.html'); + assert.equal(nav['chapter-1/ARTICLE1.md'].prev.path, 'chapter-1/README.md'); + assert.equal(nav['chapter-1/ARTICLE1.md'].next.path, 'chapter-1/ARTICLE2.md'); - assert.equal(nav['chapter-1/ARTICLE2.html'].prev.path, 'chapter-1/ARTICLE1.html'); - assert.equal(nav['chapter-1/ARTICLE2.html'].next.path, 'chapter-2/README.html'); + assert.equal(nav['chapter-1/ARTICLE2.md'].prev.path, 'chapter-1/ARTICLE1.md'); + assert.equal(nav['chapter-1/ARTICLE2.md'].next.path, 'chapter-2/README.md'); - assert.equal(nav['chapter-2/README.html'].prev.path, 'chapter-1/ARTICLE2.html'); - assert.equal(nav['chapter-2/README.html'].next.path, 'chapter-3/README.html'); + assert.equal(nav['chapter-2/README.md'].prev.path, 'chapter-1/ARTICLE2.md'); + assert.equal(nav['chapter-2/README.md'].next.path, 'chapter-3/README.md'); }); it('should give full tree, when not limited', function() { var nav = navigation(LEXED); - assert(nav['README.html']); - assert(nav['chapter-1/README.html']); - assert(nav['chapter-1/ARTICLE1.html']); - assert(nav['chapter-1/ARTICLE2.html']); - assert(nav['chapter-2/README.html']); - assert(nav['chapter-3/README.html']); + assert(nav['README.md']); + assert(nav['chapter-1/README.md']); + assert(nav['chapter-1/ARTICLE1.md']); + assert(nav['chapter-1/ARTICLE2.md']); + assert(nav['chapter-2/README.md']); + assert(nav['chapter-3/README.md']); }); it('should detect levels correctly', function() { var nav = navigation(LEXED); - assert.equal(nav['README.html'].level, '0'); - assert.equal(nav['chapter-1/README.html'].level, '1'); - assert.equal(nav['chapter-1/ARTICLE1.html'].level, '1.1'); - assert.equal(nav['chapter-1/ARTICLE2.html'].level, '1.2'); - assert.equal(nav['chapter-2/README.html'].level, '2'); - assert.equal(nav['chapter-3/README.html'].level, '3'); + assert.equal(nav['README.md'].level, '0'); + assert.equal(nav['chapter-1/README.md'].level, '1'); + assert.equal(nav['chapter-1/ARTICLE1.md'].level, '1.1'); + assert.equal(nav['chapter-1/ARTICLE2.md'].level, '1.2'); + assert.equal(nav['chapter-2/README.md'].level, '2'); + assert.equal(nav['chapter-3/README.md'].level, '3'); }); it('should not accept null paths', function() { diff --git a/test/summary.js b/test/summary.js index 51169d585..d1ef3dd39 100644 --- a/test/summary.js +++ b/test/summary.js @@ -37,12 +37,9 @@ describe('Summary parsing', function () { }); it('should normalize paths from .md to .html', function() { - assert.equal(LEXED.chapters[0].path,'chapter-1/README.html'); - assert.equal(LEXED.chapters[0]._path,'chapter-1/README.md'); - assert.equal(LEXED.chapters[1].path,'chapter-2/README.html'); - assert.equal(LEXED.chapters[1]._path,'chapter-2/README.md'); - assert.equal(LEXED.chapters[2].path,'chapter-3/README.html'); - assert.equal(LEXED.chapters[2]._path,'chapter-3/README.md'); + assert.equal(LEXED.chapters[0].path,'chapter-1/README.md'); + assert.equal(LEXED.chapters[1].path,'chapter-2/README.md'); + assert.equal(LEXED.chapters[2].path,'chapter-3/README.md'); }); it('should detect levels correctly', function() {