diff options
-rw-r--r-- | README.md | 15 | ||||
-rw-r--r-- | bin/build.js | 16 | ||||
-rw-r--r-- | lib/generate/index.js | 30 | ||||
-rw-r--r-- | lib/generate/site/index.js | 1 | ||||
-rw-r--r-- | lib/parse/code_include.js | 20 | ||||
-rw-r--r-- | lib/parse/langs.js | 14 | ||||
-rw-r--r-- | lib/parse/page.js | 12 | ||||
-rw-r--r-- | lib/parse/progress.js | 3 | ||||
-rw-r--r-- | lib/parse/renderer.js | 15 | ||||
-rw-r--r-- | lib/parse/summary.js | 32 | ||||
-rw-r--r-- | lib/utils/lang.js | 1 | ||||
-rw-r--r-- | lib/utils/links.js | 14 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | test/fixtures/INCLUDES.md | 29 | ||||
-rw-r--r-- | test/fixtures/included.c | 7 | ||||
-rw-r--r-- | test/includes.js | 38 | ||||
-rw-r--r-- | theme/templates/includes/book/summary.html | 11 |
17 files changed, 221 insertions, 39 deletions
@@ -92,6 +92,11 @@ Here are the options that can be stored in this file: "issues": null, "contribute": null, + // Custom links at top of sidebar + "custom": { + "Custom link name": "https://customlink.com" + }, + // Sharing links "sharing": { "google": null, @@ -253,3 +258,13 @@ Plugins can used to extend your book's functionality. Read [GitbookIO/plugin](ht * [Markdown within HTML](https://github.com/mrpotes/gitbook-plugin-nestedmd): Process markdown within HTML blocks - allows custom layout options for individual pages * [Bootstrap JavaScript plugins](https://github.com/mrpotes/gitbook-plugin-bootstrapjs): Use the [Bootstrap JavaScript plugins](http://getbootstrap.com/javascript) in your online GitBook * [Piwik Open Analytics](https://github.com/emmanuel-keller/gitbook-plugin-piwik): Piwik Open Analytics tracking for your book +* [Heading Anchors](https://github.com/rlmv/gitbook-plugin-anchors): Add linkable Github-style anchors to headings + +#### Debugging + +You can use the environment variable `DEBUG=true` to get better error messages (with stack trace). For example: + +``` +$ export DEBUG=true +$ gitbook build ./ +``` diff --git a/bin/build.js b/bin/build.js index 4bb6bb4..965a211 100644 --- a/bin/build.js +++ b/bin/build.js @@ -10,9 +10,10 @@ var generators = require("../lib/generate").generators; var buildCommand = function(command) { return command + .option('-v, --verbose', 'Activate verbose mode, useful for debugging errors') .option('-o, --output <directory>', 'Path to output directory, defaults to ./_book') .option('-f, --format <name>', 'Change generation format, defaults to site, availables are: '+_.keys(generators).join(", ")) - .option('--config <config file>', 'Configuration file to use, defaults to book.js or book.json') + .option('--config <config file>', 'Configuration file to use, defaults to book.js or book.json'); }; @@ -26,6 +27,11 @@ var makeBuildFunc = function(converter) { dir = dir || process.cwd(); outputDir = options.output; + // Set debugging + if(options.verbose) { + process.env.DEBUG = "true"; + } + console.log('Starting build ...'); return converter( _.extend({}, options || {}, { @@ -38,8 +44,12 @@ var makeBuildFunc = function(converter) { .then(function(output) { console.log("Successfully built!"); return output; - }, utils.logError) - .fail(function() { + }) + .fail(function(err) { + // Log error + utils.logError(err); + + // Exit process with failure code process.exit(-1); }); }; diff --git a/lib/generate/index.js b/lib/generate/index.js index 444f75f..53de646 100644 --- a/lib/generate/index.js +++ b/lib/generate/index.js @@ -271,13 +271,31 @@ var generateBook = function(options) { // Get summary .then(function() { - return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8") - .then(function(_summary) { - options.summary = parse.summary(_summary); + var summary = { + path: path.join(options.input, "SUMMARY.md") + }; - // Parse navigation - options.navigation = parse.navigation(options.summary); - }); + 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); + }) }) // Skip processing some files diff --git a/lib/generate/site/index.js b/lib/generate/site/index.js index b59c01c..dcc48d7 100644 --- a/lib/generate/site/index.js +++ b/lib/generate/site/index.js @@ -102,6 +102,7 @@ Generator.prototype.convertFile = function(content, _input) { var page = { path: _input, + rawPath: input, // path to raw md file content: content, progress: parse.progress(this.options.navigation, _input) }; diff --git a/lib/parse/code_include.js b/lib/parse/code_include.js new file mode 100644 index 0000000..f4656f1 --- /dev/null +++ b/lib/parse/code_include.js @@ -0,0 +1,20 @@ +var fs = require('fs'); +var path = require('path'); + +module.exports = function(code, folder) { + folder = folder || ''; + + return code.replace(/{{([\s\S]+?)}}/g, function(match, filename) { + // Normalize filename + var fname = path.join(folder, filename.trim()); + + // Try including snippet from FS + try { + // Trim trailing newlines/space of imported snippets + return fs.readFileSync(fname, 'utf8').trimRight(); + } catch(err) {} + + // If fails leave content as is + return match; + }); +}; diff --git a/lib/parse/langs.js b/lib/parse/langs.js index 46c3a46..01b7c8c 100644 --- a/lib/parse/langs.js +++ b/lib/parse/langs.js @@ -1,13 +1,14 @@ -var _ = require("lodash") -var parseSummary = require("./summary"); +var _ = require("lodash"); +var parseEntries = require("./summary").entries; + var parseLangs = function(content) { - var summary = parseSummary(content); + var entries = parseEntries(content); return { - list: _.chain(summary.chapters) + list: _.chain(entries) .filter(function(entry) { - return entry.path != null; + return Boolean(entry.path); }) .map(function(entry) { return { @@ -17,7 +18,8 @@ var parseLangs = function(content) { }; }) .value() - } + }; }; + module.exports = parseLangs;
\ No newline at end of file diff --git a/lib/parse/page.js b/lib/parse/page.js index 2db4476..b459798 100644 --- a/lib/parse/page.js +++ b/lib/parse/page.js @@ -5,6 +5,7 @@ var hljs = require('highlight.js'); var lex = require('./lex'); var renderer = require('./renderer'); +var codeInclude = require('./code_include'); var lnormalize = require('../utils/lang').normalize; @@ -73,15 +74,20 @@ function parsePage(src, options) { // Main language var lang = validLangs ? langs[0] : null; + // codeInclude shortcut + var ci = function(code) { + return codeInclude(code, options.dir); + }; + return { id: section.id, type: section.type, content: render(nonCodeNodes), lang: lang, code: { - base: codeNodes[0].text, - solution: codeNodes[1].text, - validation: codeNodes[2].text, + base: ci(codeNodes[0].text), + solution: ci(codeNodes[1].text), + validation: ci(codeNodes[2].text), // Context is optional context: codeNodes[3] ? codeNodes[3].text : null, } diff --git a/lib/parse/progress.js b/lib/parse/progress.js index 0abdf8d..10a06d2 100644 --- a/lib/parse/progress.js +++ b/lib/parse/progress.js @@ -11,9 +11,6 @@ var calculProgress = function(navigation, current) { nav.path = path; return nav; }) - .sortBy(function(nav) { - return parseFloat(nav.level); - }) .map(function(nav, i) { // Calcul percent nav.percent = (i * 100) / Math.max((n - 1), 1); diff --git a/lib/parse/renderer.js b/lib/parse/renderer.js index 4da312e..dcbc261 100644 --- a/lib/parse/renderer.js +++ b/lib/parse/renderer.js @@ -1,6 +1,7 @@ var url = require('url'); var inherits = require('util').inherits; var links = require('../utils').links; +var codeInclude = require('./code_include'); var path = require('path'); @@ -56,7 +57,7 @@ GitBookRenderer.prototype.link = function(href, title, text) { // Relative link, rewrite it to point to github repo if(links.isRelative(_href)) { - if (path.extname(_href) == ".md") { + if (path.extname(parsed.path) == ".md") { _href = links.toAbsolute(_href, o.dir || "./", o.outdir || "./"); if (o.singleFile) { @@ -125,7 +126,7 @@ GitBookRenderer.prototype._createCheckboxAndRadios = function(text) { 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); @@ -135,5 +136,15 @@ 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, + // Import code snippets + codeInclude(code, this._extra_options.dir), + lang, + escaped + ); +}; + // Exports module.exports = GitBookRenderer; diff --git a/lib/parse/summary.js b/lib/parse/summary.js index 7e54df0..fd300bc 100644 --- a/lib/parse/summary.js +++ b/lib/parse/summary.js @@ -87,6 +87,9 @@ function parseTitle(src, nums) { } 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)); @@ -97,11 +100,13 @@ function parseChapter(nodes, nums) { function defaultChapterList(chapterList) { var first = _.first(chapterList); - var chapter = parseChapter(first, [0]); + if (first) { + var chapter = parseChapter(first, [0]); - // Already have README node, we're good to go - if(chapter.path === 'README.md') { - return chapterList; + // Already have README node, we're good to go + if(chapter.path === 'README.md') { + return chapterList; + } } return [ @@ -109,26 +114,31 @@ function defaultChapterList(chapterList) { ].concat(chapterList); } -function parseSummary(src) { +function listGroups(src) { var nodes = marked.lexer(src); - // Get out list of chapters - var chapterList = listSplit( + // Get out groups of lists + return listSplit( filterList(nodes), 'list_item_start', 'list_item_end' ); +} +function parseSummary(src) { // Split out chapter sections - var chapters = defaultChapterList(chapterList) - .map(function(nodes, i) { - return parseChapter(nodes, [i]); - }); + var chapters = defaultChapterList(listGroups(src)) + .map(parseChapter); return { chapters: chapters }; } +function parseEntries (src) { + return listGroups(src).map(parseChapter); +} + // Exports module.exports = parseSummary; +module.exports.entries = parseEntries; diff --git a/lib/utils/lang.js b/lib/utils/lang.js index 9eabbb5..9da737b 100644 --- a/lib/utils/lang.js +++ b/lib/utils/lang.js @@ -2,6 +2,7 @@ var MAP = { 'py': 'python', 'js': 'javascript', 'rb': 'ruby', + 'csharp': 'cs', }; function normalize(lang) { diff --git a/lib/utils/links.js b/lib/utils/links.js index 6606bbf..b4d2fb7 100644 --- a/lib/utils/links.js +++ b/lib/utils/links.js @@ -3,14 +3,22 @@ var path = require('path'); // Is the link an external link var isExternal = function(href) { - return Boolean(url.parse(href).protocol); + try { + return Boolean(url.parse(href).protocol); + } catch(err) { } + + return false; }; // Return true if the link is relative var isRelative = function(href) { - var parsed = url.parse(href); + try { + var parsed = url.parse(href); + + return !parsed.protocol && parsed.path && parsed.path[0] != '/'; + } catch(err) {} - return !parsed.protocol && parsed.path && parsed.path[0] != '/'; + return true; }; // Relative to absolute path diff --git a/package.json b/package.json index 0e1df77..d06fe20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitbook", - "version": "0.5.3", + "version": "0.5.6", "homepage": "http://www.gitbook.io/", "description": "Library and cmd utility to generate GitBooks", "main": "lib/index.js", diff --git a/test/fixtures/INCLUDES.md b/test/fixtures/INCLUDES.md new file mode 100644 index 0000000..22e3a61 --- /dev/null +++ b/test/fixtures/INCLUDES.md @@ -0,0 +1,29 @@ +# Beautiful chapter + +Here is a nice included snippet : + +```c +{{ included.c }} +``` + +---- + +An exercise using includes + +```c +{{ included.c }} + +Remove this extra code at the end +``` + +```c +{{ included.c }} +``` + +```c +{{ included.c }} + +This validation code is wrong but who cares ? +``` + +---- diff --git a/test/fixtures/included.c b/test/fixtures/included.c new file mode 100644 index 0000000..d9323e3 --- /dev/null +++ b/test/fixtures/included.c @@ -0,0 +1,7 @@ +#include <stdio.h> + +int main(int argc, char *argv[]) { + printf("All is well\n"); + + return 0; +} diff --git a/test/includes.js b/test/includes.js new file mode 100644 index 0000000..69adc11 --- /dev/null +++ b/test/includes.js @@ -0,0 +1,38 @@ +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); + +var page = require('../').parse.page; + +var FIXTURES_DIR = path.join(__dirname, './fixtures/'); + +function loadPage (name, options) { + var CONTENT = fs.readFileSync(FIXTURES_DIR + name + '.md', 'utf8'); + return page(CONTENT, options); +} + + +describe('Code includes', function() { + + var LEXED = loadPage('INCLUDES', { + 'dir': FIXTURES_DIR, + }); + + var INCLUDED_C = fs.readFileSync(path.join(FIXTURES_DIR, 'included.c'), 'utf8'); + + it('should work for snippets', function() { + assert.equal(LEXED[0].type, 'normal'); + // Has replaced include + assert.equal( + LEXED[0].content.indexOf('{{ included.c }}'), + -1 + ); + }); + + it('should work for exercises', function() { + assert.equal(LEXED[1].type, 'exercise'); + + // Solution is trimmed version of source + assert.equal(LEXED[1].code.solution, INCLUDED_C.trim()); + }); +}); diff --git a/theme/templates/includes/book/summary.html b/theme/templates/includes/book/summary.html index 2f9dc3f..8014d7e 100644 --- a/theme/templates/includes/book/summary.html +++ b/theme/templates/includes/book/summary.html @@ -1,7 +1,7 @@ {% macro articles(_articles) %} {% for item in _articles %} {% set externalLink = item.path|isExternalLink %} - <li class="chapter {% if item._path == _input %}active{% endif %}" data-level="{{ item.level }}" {% if item.path && !externalLink %}data-path="{{ item.path|mdLink }}"{% endif %}> + <li class="chapter {% if item.path == _input %}active{% endif %}" data-level="{{ item.level }}" {% if item.path && !externalLink %}data-path="{{ item.path|mdLink }}"{% endif %}> {% if item.path %} {% if !externalLink %} <a href="{{ basePath }}/{{ item.path|mdLink }}"> @@ -59,6 +59,15 @@ </li> {% endif %} + {% if options.links.custom %} + {% for link in options.links.custom %} + {% set _divider = true %} + <li> + <a href="{{ options.links.custom[loop.key] }}" target="blank" class="custom-link">{{ loop.key }}</a> + </li> + {% endfor %} + {% endif %} + {% if _divider %} <li class="divider"></li> {% endif %} |