Compare commits

..

391 Commits

Author SHA1 Message Date
Samy Pessé 315556310f Bump version to 2.0.0-beta.4 2015-03-17 10:48:25 +01:00
Samy Pessé 9c0b26304b Merge branch master 2015-03-17 10:46:17 +01:00
Samy Pessé acdaea9f6c Fix #649: add option "author" in book.json to set meta author in html 2015-03-17 10:38:56 +01:00
Samy Pessé e481deb5bb Merge pull request #650 from wcandillon/patch-2
Update default generator
2015-03-14 16:00:09 -07:00
William Candillon 6d3ac6074c 💄 Update default generator 2015-03-14 15:45:15 -07:00
Samy Pessé 5e611bde48 Merge pull request #648 from wcandillon/patch-1
Bump to lodash 3.5.0
2015-03-14 15:25:39 -07:00
Samy Pessé 9448562156 Add editorconfig and normalize indent 2015-03-14 23:23:28 +01:00
William Candillon e7e8fe4d08 ⬆️ Bump to lodash 3.5.0 2015-03-14 11:35:28 -07:00
Samy Pessé 8320484b88 Fix #647: Fix links normalization (content and hash) 2015-03-13 23:59:37 +01:00
Samy Pessé 0e243e6f23 Bump version to 2.0.0-beta.3 2015-03-12 21:23:25 +01:00
Samy Pessé 5cd25b0d21 Update gitbook-parsers@0.5.4 2015-03-12 21:20:51 +01:00
Samy Pessé 3218ee7455 Fix tests to adapt to changes in parsers 2015-03-11 22:05:58 +01:00
Samy Pessé 0552007b8a Bump version to 2.0.0-beta.2 2015-03-11 22:03:54 +01:00
Samy Pessé 50bc89d9d9 Fix define of introduction from summary 2015-03-11 22:02:46 +01:00
Samy Pessé 743a88f53a Fix typo in bin message 2015-03-11 11:40:56 +01:00
Samy Pessé ee92c6dd99 Add changelog for 2.0.0-beta.1 2015-03-11 11:23:48 +01:00
Samy Pessé b6c54c17d1 Bump version to 2.0.0-beta.1 2015-03-11 11:17:56 +01:00
Samy Pessé 992ef1ab74 Use latest commit from nunjucks windows fix branch 2015-03-11 11:17:43 +01:00
Samy Pessé 960848ba34 Ensure inline svgs have xml header 2015-03-11 09:38:18 +01:00
Samy Pessé d9aca7a9df Fix #639: update nunjucks to support windows 2015-03-11 00:57:07 +01:00
Samy Pessé 05a0de2d86 Remove require of old dependency "shell-escape" 2015-03-11 00:50:48 +01:00
Samy Pessé 19e4f498cf Fix inline svg extraction on windows 2015-03-10 16:45:22 -07:00
Samy Pessé 3055139297 Fix run of svgexport on windows 2015-03-10 16:31:43 -07:00
Samy Pessé 3d6b099dea Fix links normalization on windows 2015-03-10 22:44:54 +01:00
Samy Pessé dd970e33b8 Remove --no-exit option from mocha tests runner 2015-03-10 21:31:43 +01:00
Samy Pessé 59ba479465 Test option --no-exit for mocha tests 2015-03-10 16:14:04 +01:00
Samy Pessé 54c9ecf3c2 Use mocha from node_modules/.bin 2015-03-10 15:44:39 +01:00
Samy Pessé 8308ae69a2 Remove command export from "npm test" 2015-03-10 15:01:51 +01:00
Samy Pessé bd6c3287ed Add badge for windows testing 2015-03-10 14:58:23 +01:00
Samy Pessé 24fc36deb1 Add appveyor.yml for windows testing
Related to #639
2015-03-10 14:52:09 +01:00
Samy Pessé 2f44b61cec Merge pull request #638 from wapmorgan/patch-1
Update README.md
2015-03-09 21:13:29 +01:00
wapmorgan 45ee503db1 Update README.md
Fix link
2015-03-10 00:12:53 +04:00
Samy Pessé dc826da99b Update README.md 2015-03-09 10:58:11 +01:00
Samy Pessé 3ea659d582 Improve readme presentation 2015-03-09 10:57:23 +01:00
Samy Pessé 34fc2831e0 Merge branch 'version/2.0' 2015-03-09 10:43:12 +01:00
Samy Pessé 1bf68a5aa0 Merge branch 'master' into version/2.0
Conflicts:
	README.md
2015-03-09 10:42:42 +01:00
Samy Pessé d72c397f23 Update gitbook-parsers@0.5.2 2015-03-08 23:09:50 +01:00
Samy Pessé 2c69525148 Add GitBook as book producer in ebook-convert 2015-03-08 22:53:18 +01:00
Samy Pessé ee3a0ba801 Fix #633: update markdown parser to fix codeblocks
Sort glossary and fix #634
2015-03-08 22:53:18 +01:00
Samy Pessé 7a4179a2a2 Merge pull request #631 from MHM5000/version/2.0
[i18n] - fa: Persian/Farsi
2015-03-07 18:48:05 +01:00
Mohammad Hossein Mojtahedi 1d0083c5f4 added @mhm5000 in translators 2015-03-07 18:22:40 +03:30
Mohammad Hossein Mojtahedi 33a6111a30 [i18n] - fa: Persian/Farsi 2015-03-07 16:48:44 +03:30
Samy Pessé a5d7a87e31 Enable rtl in ebooks 2015-03-06 22:45:53 +01:00
Samy Pessé c7f7c5b5ab Base translation for Arabic (with rtl support) 2015-03-06 22:32:45 +01:00
Samy Pessé 55a7b99b4d Fix #227: Add base rtl support for website 2015-03-06 22:32:25 +01:00
Samy Pessé 8bfba604b3 Update gitbook-parsers@0.5.0 and include page.prepare
Fix #625: escape code blocks in markdown
2015-03-06 16:54:46 +01:00
Samy Pessé 2a5fa1c5fb Fix #629: add generator name in template context 2015-03-06 14:34:55 +01:00
Samy Pessé a3e95feb26 Add instructions to use version from git 2015-03-06 13:58:23 +01:00
Samy Pessé 145b00f590 Fix #628: preserve pure anchor links and hash in relative links 2015-03-06 10:25:06 +01:00
Samy Pessé 5f2785f0a8 Escape svgexport args
Fixes shakiba/svgexport#5 and #624
2015-03-05 10:06:45 +01:00
Samy Pessé 412529c548 Merge pull request #621 from nasirkhan/version/2.0
Bengali (bn) added in theme/i18n
2015-02-27 17:48:07 +01:00
nasirkhan 547ec6ade8 bn language added 2015-02-27 22:29:10 +06:00
nasirkhan eac513cfa3 Merge remote-tracking branch 'upstream/version/2.0' into version/2.0 2015-02-27 22:16:11 +06:00
Aaron O'Mullan 5ec8a1f18c Add en.json
Fixes #620
2015-02-27 16:02:31 +01:00
Samy Pessé c923a7e01a Fix transformation of README.md links in index.html 2015-02-26 01:14:27 +01:00
Samy Pessé 366e6ed105 Bump version to 2.0.0-alpha.9 2015-02-24 13:57:20 +01:00
Samy Pessé 52078eb666 Use latest nunjucks from mozilla/nunjucks 2015-02-24 13:54:08 +01:00
Samy Pessé d9ddbff443 Fix links in sidebar 2015-02-24 13:51:10 +01:00
Samy Pessé 01b160f9a1 Fix normalization of html link (README to index) 2015-02-21 13:40:38 +01:00
Samy Pessé 23fcb817a4 Fix html snippets escaping 2015-02-20 20:52:07 +01:00
Samy Pessé f531a3f984 Update nunjucks-autoescape 2015-02-20 12:24:33 +01:00
Samy Pessé c777023e9f Add introduction to changelog 2015-02-17 14:18:49 +01:00
Samy Pessé 62c99759c1 Bump version to 2.0.0-alpha.8 2015-02-14 16:43:19 +01:00
Samy Pessé 6951c21d19 Update gitbook-parsers@0.4.1 2015-02-14 16:26:16 +01:00
Samy Pessé 3a8289483e Improve language detection from book.json 2015-02-14 15:34:19 +01:00
Samy Pessé 4079de9587 Fix #608: fix chapter name for glossary 2015-02-14 15:29:41 +01:00
Samy Pessé b86d171082 Escape description in title attr of glossary link
Related to #609
2015-02-14 11:53:30 +01:00
Samy Pessé 959b272e65 Fix #609: don't escape html in glossary 2015-02-14 11:52:33 +01:00
Samy Pessé 684440e2d1 Fix generation of multilingual book as ebook 2015-02-14 11:49:42 +01:00
Samy Pessé 4740cd3b06 Merge pull request #605 from GitbookIO/update/lodash
Update lodash to 3.x.x
2015-02-13 08:18:45 +01:00
Samy Pessé b3131d99d2 Update lodash@3.2.0 2015-02-13 08:16:28 +01:00
Samy Pessé b0fd2a67ea Fix test with post in blocks 2015-02-13 08:09:28 +01:00
Samy Pessé 7c83869869 Limit concurrent blocks processed 2015-02-13 00:04:27 +01:00
Samy Pessé 57d5c23eaf Accept post function from block to post process content 2015-02-12 23:36:22 +01:00
Samy Pessé 8d92b74e61 Fix page normalization with empty links or images 2015-02-12 18:43:06 +01:00
Samy Pessé 4e2cda99cc Complete changelog for 2.0.0-alpha.7 2015-02-12 16:40:50 +01:00
Samy Pessé dd250e0339 Bump version to 2.0.0-alpha.7 2015-02-12 16:30:55 +01:00
Samy Pessé 741b158631 Fix #596: fix display of glossary in ebook format 2015-02-12 16:27:52 +01:00
Samy Pessé 843889303e Improve pdf by adding default header and footer 2015-02-12 12:47:08 +01:00
Samy Pessé 4ad2b040b4 Always generate README.json with langs index
remove method langsIndex form generators (moved to finish)
2015-02-12 12:18:52 +01:00
Samy Pessé f5916b5c59 Remove log from test 2015-02-12 11:44:44 +01:00
Samy Pessé d9da4e9a08 Fix GitbookIO/plugin-youtube#2: hash remote image for local path 2015-02-12 11:40:17 +01:00
Samy Pessé 1d0f6370ad Fix default language to english 2015-02-12 08:19:38 +01:00
Samy Pessé 12a7a4c4cf Fix test for lodash 3.1.0 (from git) 2015-02-11 17:53:46 +01:00
Samy Pessé e4127d961e Update to gitbook-parsers@0.4.0 2015-02-09 18:10:46 +01:00
Samy Pessé 8e97655040 Complete zh translations 2015-02-09 11:38:12 +01:00
Samy Pessé 9b61b87873 Add Serif and Sans to all translations 2015-02-09 11:14:47 +01:00
Samy Pessé 4a99e37b2f Fix spaces in zh-tw translation 2015-02-09 11:12:07 +01:00
Samy Pessé 752b5e9b7c Merge pull request #602 from howiehu/version/2.0
add zh-tw (Traditional Chinese) i18n file
2015-02-09 11:09:04 +01:00
Samy Pessé b318f2fe94 Improve i18n to support locale normalization 2015-02-09 11:08:46 +01:00
Hao Hu 01c46030b6 Add @howiehu to authors and normalize zh-tw translation 2015-02-09 17:59:54 +08:00
Hao Hu 9803b05d8a add zh-tw (Traditional Chinese) i18n file 2015-02-09 17:56:25 +08:00
Samy Pessé 1337858fe6 Add @howiehu to authors and normalize zh translation 2015-02-09 10:16:37 +01:00
Aaron O'Mullan 925b9e88ad Merge pull request #601 from howiehu/version/2.0
Add Simplifiled Chinese (zh-cn) i18n file.
2015-02-09 09:58:36 +01:00
Hao Hu 7305011a8b add zh-cn (Simplifiled Chinese) i18n file 2015-02-09 16:53:30 +08:00
Samy Pessé 489f23f74e Fix #595: calcul link to glossary as an absolute path 2015-02-07 20:39:15 +01:00
Samy Pessé 370799ac55 Add test for #595: glossary links in subpages 2015-02-07 20:35:24 +01:00
Samy Pessé 4a9adb9440 Bump version to 2.0.0-alpha.6 2015-02-06 19:42:55 +01:00
Samy Pessé d8898b7a1f Add changelog for version 2.0.0-alpha.* 2015-02-06 15:54:40 +01:00
Samy Pessé bb7e081a37 Fix #588: Fix replacement of glossary terms by links 2015-02-06 15:28:53 +01:00
Samy Pessé c3ffa5e0ef Add test for glossary replacement #588 2015-02-06 15:07:01 +01:00
Samy Pessé 724bc2b59c Add capital to german translation of share links 2015-02-06 15:00:13 +01:00
Samy Pessé 178d2e2a48 Add @RyanOM as spanish translator 2015-02-06 14:35:23 +01:00
Samy Pessé 4d710c129c Merge pull request #590 from RyanOM/patch-2
Created es.json
2015-02-06 14:34:25 +01:00
RyanOM e4dbf62c0f Create es.json 2015-02-06 11:33:26 -02:00
Samy Pessé b0cd23fd34 Add @RyanOM as a translator 2015-02-06 14:32:20 +01:00
Samy Pessé af5764bc3b Merge pull request #589 from RyanOM/patch-1
Create pt.json
2015-02-06 14:29:31 +01:00
RyanOM 6309da1214 Create pt.json 2015-02-06 11:21:22 -02:00
Samy Pessé 38c9bfa357 Fix dom-serializer dependency 2015-02-05 21:10:17 +01:00
Samy Pessé f194cb513c Merge pull request #586 from ludovicofischer/version/2.0
Use tiny-lr instead of old unmaintained fork.
2015-02-05 20:50:02 +01:00
Ludovico Fischer 03ff892727 Use tiny-lr instead of old unmaintained fork. 2015-02-05 19:43:29 +01:00
Samy Pessé 8cc0f64f54 Handle SVG correctly in page normalization
Related to mathjax/MathJax-node#65
2015-02-05 17:36:44 +01:00
Samy Pessé 532e8b0388 Bump version to 2.0.0-alpha.5 2015-02-05 13:56:52 +01:00
Samy Pessé 35b3d50012 Add "finish:before" to the list of valid hooks 2015-02-05 13:56:43 +01:00
Samy Pessé 72ff9996ce Update fs-extra to fix fs.copy issue 2015-02-05 13:51:53 +01:00
Samy Pessé 0759c4dd78 Bump version to 2.0.0-alpha.4 2015-02-04 20:49:09 +01:00
Samy Pessé 82a1a151e4 Correctly copy cover for sub books 2015-02-04 20:29:49 +01:00
Samy Pessé ff6e8387fb Bump version to 2.0.0-alpha.3 2015-02-04 18:31:26 +01:00
Samy Pessé f71601e8d3 Fix generation of multilingual books as ebook 2015-02-04 18:31:07 +01:00
Samy Pessé 234a37726f Improve logs for images and ebook copy 2015-02-04 16:54:02 +01:00
Samy Pessé 65a7979874 Load plugins from the book in priority 2015-02-04 15:25:40 +01:00
Samy Pessé 6a00bf0fc0 Remove plugin list 2015-02-04 11:54:52 +01:00
Samy Pessé 853f3d6482 Add translators on AUTHORS.md
@gbonanome, @kmelve, @AndreyAkinshin, @winniehell
2015-02-04 11:52:00 +01:00
Samy Pessé 27de7cd914 Merge pull request #582 from kmelve/version/2.0
Added Norwegian translation
2015-02-04 11:42:46 +01:00
Knut Melvær 2318cae1e6 Added Norwegian translation 2015-02-04 11:39:40 +01:00
Samy Pessé cb313c0fab Bump version to 2.0.0-alpha.2 2015-02-04 10:02:51 +01:00
Samy Pessé 4f25056017 Update gitbook-parsers@0.3.1 2015-02-03 14:34:20 +01:00
Samy Pessé 5baebb8754 Set "info" as default log level 2015-02-03 10:53:33 +01:00
Samy Pessé 1eea994e73 Fix init command 2015-02-03 10:52:39 +01:00
Samy Pessé 8a597ff3f1 Fix installation instruction 2015-02-03 10:40:22 +01:00
Samy Pessé 42fd7b12c0 Bump version to 2.0.0-alpha.1 2015-02-03 10:37:25 +01:00
Samy Pessé fd78a06b11 Add message in gitbook command line 2015-02-03 10:37:21 +01:00
Samy Pessé 80efc8e086 Accept alpha and beta version of plugins 2015-02-02 22:07:40 +01:00
Samy Pessé c0dcb886ef Improve install command to resolve best plugin version 2015-02-02 21:45:14 +01:00
Samy Pessé df39128894 Fix #575: update logo and use retina favicon 2015-02-02 17:17:54 +01:00
Samy Pessé 3ab5b57b4f Merge pull request #573 from gbonanome/version/2.0
Add italian translation
2015-01-31 09:38:24 +01:00
Giulio Bonanome 49174d1731 Add italian translation 2015-01-31 00:51:10 +01:00
Samy Pessé ed3b77b655 Don't use xmlMode to normalize html 2015-01-30 17:47:19 +01:00
Samy Pessé 55a1aee164 Fix validation of semi-loaded plugins 2015-01-30 17:31:47 +01:00
Samy Pessé d7caf6686e Fix namespacing of plugins resources 2015-01-30 16:53:42 +01:00
Samy Pessé 71dad602ec Merge pull request #572 from AndreyAkinshin/version/2.0
Add theme/i18n/ru.json
2015-01-30 15:42:02 +01:00
Andrey Akinshin 30d8719c4b Add theme/i18n/ru.json 2015-01-30 16:39:16 +02:00
Samy Pessé ad81d38d07 Fix support of iframe in html normalization 2015-01-30 14:29:34 +01:00
Samy Pessé 6740b1b99c Use spaces identation 2015-01-30 13:56:21 +01:00
Samy Pessé f5f2cccbf4 Add "generator" to plugins blocks and filters ctx 2015-01-30 13:16:27 +01:00
Samy Pessé f45a1faa63 Fix #515: Update parsers and add reStructuredText support 2015-01-30 12:51:57 +01:00
Samy Pessé 5a0dbe8cb7 Remove useless log 2015-01-30 11:42:18 +01:00
Samy Pessé 2eb45e4699 Fix #567: Fail hard if book.json is invalid 2015-01-30 11:37:35 +01:00
Samy Pessé f7039a174a Add warning message when gitbook version to use is not fixed 2015-01-30 11:33:31 +01:00
Samy Pessé 5690704444 Download external images in ebook format 2015-01-29 22:42:41 +01:00
Samy Pessé 8a8ffb6134 Fix #565: fix relative path to fonts 2015-01-29 21:22:20 +01:00
Samy Pessé 5b23dd8ffd Fix ebook commands 2015-01-29 18:41:51 +01:00
Samy Pessé 5fc0c6b507 Convert inline svg as png if needed 2015-01-29 18:16:23 +01:00
Samy Pessé c8e72092c7 Fix inline svg image in page 2015-01-29 16:21:36 +01:00
Samy Pessé 9172948c18 Remove used blocks from templates 2015-01-29 14:18:13 +01:00
Samy Pessé 2bec7c5e10 Add option to block to not parse output 2015-01-29 14:09:27 +01:00
Samy Pessé 907ce6dbcd Fix copy of plugins assets to output 2015-01-28 22:54:19 +01:00
Samy Pessé 898000a580 Fix resources loading in plugins 2015-01-28 19:39:41 +01:00
Samy Pessé f95beed58b Fix log option to command line commands 2015-01-28 19:18:01 +01:00
Samy Pessé 4baaa41ac9 Fix for good path resolution of svg image 2015-01-28 18:34:09 +01:00
Samy Pessé 1f7d349aed Fix conversion of svg when cache is empty 2015-01-28 18:27:27 +01:00
Samy Pessé c0d922560d Improve svg conversion and links normalization 2015-01-28 18:23:48 +01:00
Samy Pessé f5ded5e5e1 Fix absolute path calcul
Add tests for links module
2015-01-28 17:52:10 +01:00
Samy Pessé 56d1720aae Improve image conversion to png to avoid doublons 2015-01-28 16:36:17 +01:00
Samy Pessé 02d7102c68 Add svgexport to travis tests 2015-01-28 14:55:59 +01:00
Samy Pessé 592b32b036 Fix error message when svgexport is not installed 2015-01-28 14:54:29 +01:00
Samy Pessé 1213997341 Use svgexport instead of imagemagick 2015-01-28 14:53:27 +01:00
Samy Pessé 4e620f34ad Handle correctly path starting with "/" 2015-01-28 13:17:41 +01:00
Samy Pessé 534edaceac Handle absolute url for images conversion (svg -> png) 2015-01-28 12:46:15 +01:00
Samy Pessé ee13b4bf75 Improve error message when imagemagick is not installed 2015-01-28 12:22:26 +01:00
Samy Pessé 924006d508 Add test for svg conversion 2015-01-28 12:02:36 +01:00
Samy Pessé 350f34561a Show log from ebook-convert in realtime for debugging 2015-01-28 11:52:24 +01:00
Samy Pessé 324fbb2f69 Convert svg images to png in ebook 2015-01-28 11:43:44 +01:00
Samy Pessé 5539ab48a5 Fix init of livereload plugin on serve 2015-01-28 11:28:21 +01:00
Samy Pessé c25157fc40 Fix display of plugins count 2015-01-28 11:10:30 +01:00
Samy Pessé d1b2950993 Check parser used before applying template shortcuts 2015-01-27 22:34:48 +01:00
Samy Pessé d8995200e6 Add options "shortcuts" for blocks from plugins 2015-01-27 22:27:59 +01:00
Samy Pessé bc389ab4a1 Improve tests on blocks extensions 2015-01-27 21:47:14 +01:00
Samy Pessé cf12d09092 Complete parsing of blocks from plugin extensions 2015-01-27 21:39:41 +01:00
Samy Pessé 8e18adbe4c Parse correctly list of blocks and args 2015-01-27 21:21:01 +01:00
Samy Pessé f1b5e3de0c Add test for block defined with special end tag 2015-01-27 17:16:59 +01:00
Samy Pessé 0e0a23b794 Add blocks from plugins to template engine 2015-01-27 17:07:25 +01:00
Samy Pessé d9329295d6 Add test for extending template blocks 2015-01-27 16:04:49 +01:00
Samy Pessé 750fb862d4 Improve tests for plugins with filters 2015-01-27 15:58:28 +01:00
Samy Pessé 131c19dee6 Move plugin helper methods to a specific class 2015-01-27 15:22:42 +01:00
Samy Pessé 870ca4da0c Extend templates filters
Fully remove mathjax from plugins
2015-01-27 12:56:04 +01:00
Samy Pessé 54e1be1e77 Parse plugins in book instead of generator 2015-01-27 12:49:08 +01:00
Samy Pessé ce259eb4ec Remove mathjax plugin from default installed 2015-01-27 12:35:37 +01:00
Samy Pessé 7424395171 Fix invalid log call in website generator 2015-01-27 12:17:20 +01:00
Samy Pessé 6125df5834 Add back "page:before" hook 2015-01-27 11:15:16 +01:00
Samy Pessé 99cd3148ba Log output from ebook-convert when debug is enabled 2015-01-27 09:37:38 +01:00
Samy Pessé 1a1ace0ae1 Transform as absolute output path in configuration 2015-01-26 22:21:09 +01:00
Samy Pessé 22bae559db Fix #273: clean the output without removing .git and .svn 2015-01-26 22:19:17 +01:00
Samy Pessé 5cee07ee8c fs.list take option for files to ignores 2015-01-26 21:59:55 +01:00
Samy Pessé 4250f559b0 Extract filters from plugins 2015-01-26 21:42:30 +01:00
Samy Pessé 3293c39450 Add test for plugin resources 2015-01-26 16:08:39 +01:00
Samy Pessé 5b447d030b Add some tests for plugins validation 2015-01-26 15:51:33 +01:00
Samy Pessé f02680dc73 Transform page after hook "page" 2015-01-26 13:19:42 +01:00
Samy Pessé cee7643f00 Call hook "init" when preparing generator 2015-01-26 12:54:29 +01:00
Samy Pessé a53ec62e4a Fix logs for deprecated hooks 2015-01-26 12:42:01 +01:00
Samy Pessé 795da31af8 Add warning message about deprecated hooks 2015-01-26 12:05:50 +01:00
Samy Pessé f70c29fea5 Improve logs for plugins installation 2015-01-26 11:57:28 +01:00
Samy Pessé decd8dbdc5 Add infos in readme about variables and templating 2015-01-26 10:32:19 +01:00
Samy Pessé edc954f2ac Add chapter in readme about conrefs 2015-01-26 10:19:10 +01:00
Samy Pessé 93bc86a0fe Fix git url parsing for content references 2015-01-26 10:12:26 +01:00
Samy Pessé 75b217fc24 Support ssh urls in git parsing 2015-01-26 09:12:00 +01:00
Samy Pessé d6b44cc149 Add tests for parsing git url 2015-01-26 08:05:07 +01:00
Samy Pessé 0f0ec4cf3d Improve logger 2015-01-25 15:27:40 +01:00
Samy Pessé 06ab84abe3 Improve logger module 2015-01-25 14:55:37 +01:00
Samy Pessé bb5761e885 Change comment start and end in templates
(to avoid conflicts with maths)
2015-01-25 13:54:25 +01:00
Samy Pessé d60a9763cd Fix loading of plugins when book root is relative 2015-01-25 12:08:52 +01:00
Samy Pessé e366c05238 Add logs on plugins loading 2015-01-25 12:06:52 +01:00
Samy Pessé 690cdbe505 Improve output of book.log 2015-01-25 12:03:19 +01:00
Samy Pessé fd701fc37c Add color to output 2015-01-25 11:55:33 +01:00
Samy Pessé 4d38baf7a2 Add _book to list of files to ignore 2015-01-25 11:55:25 +01:00
Samy Pessé 7a999e01d5 Fix link in features list 2015-01-25 11:35:32 +01:00
Samy Pessé f3a5464306 Add AsciiDoc to readme 2015-01-25 11:32:54 +01:00
Samy Pessé 2c57a37afb Add command to init a book 2015-01-24 23:14:32 +01:00
Samy Pessé 244b0f8efc Add back readme 2015-01-24 21:34:18 +01:00
Samy Pessé b4dc31e82b Add command to generate all ebooks 2015-01-24 18:56:18 +01:00
Samy Pessé 1583ede3d7 Add Book.generateFile to output a pdf 2015-01-24 18:54:20 +01:00
Samy Pessé 572994a17e Add more tests for inclusion 2015-01-24 18:18:43 +01:00
Samy Pessé 796dd4c13a Add base loader to resolve git dependencies 2015-01-24 18:04:03 +01:00
Samy Pessé e02f29e763 Add test for local inclusion 2015-01-24 15:33:04 +01:00
Samy Pessé 6805b02c63 Improve german translation of "Introduction" 2015-01-24 12:53:06 +01:00
Samy Pessé 21949ccd04 Update gitbook-parsers@0.2.2 2015-01-24 12:38:36 +01:00
Samy Pessé 7b766f692b Return to version 0.5.1 of gaze 2015-01-24 12:34:01 +01:00
Samy Pessé c9aca86dcc Watch all fiels that can be parsed in serve 2015-01-24 12:29:04 +01:00
Samy Pessé 99d9d49610 Improve navigation with non existant entries
Use i18n for introduction title
2015-01-24 12:18:23 +01:00
Samy Pessé fd98746c2b Update gitbook-parsers and improve summary tests 2015-01-24 11:57:21 +01:00
Samy Pessé 2efd234824 Complete serve command 2015-01-24 11:23:53 +01:00
Samy Pessé 8be4304b5c Fix translation of White theme in german 2015-01-24 10:54:31 +01:00
Samy Pessé c984eba2bc Fix german translation 2015-01-23 23:21:56 +01:00
Samy Pessé 2bb2a2a330 Normalize language to support: "fr-FR" 2015-01-23 23:06:10 +01:00
Samy Pessé f12163534d Add german transalation
Based on PR by @winniehell
2015-01-23 22:51:23 +01:00
Samy Pessé e9d7c71911 Fix fr i18n texts 2015-01-23 22:46:07 +01:00
Samy Pessé 59b3b0fadd Translate ebook 2015-01-23 22:33:24 +01:00
Samy Pessé b426aba846 Add french translations for website 2015-01-23 22:29:40 +01:00
Samy Pessé 89885e17a5 Set translation to use in templates 2015-01-23 22:12:58 +01:00
Samy Pessé b66e9d2bed Load locales to translate layout 2015-01-23 22:05:11 +01:00
Samy Pessé d0f910d65f Fix language parsing test 2015-01-23 18:59:01 +01:00
Samy Pessé 8e41aca8a7 Fix #517: fix weibo sharing class in header 2015-01-23 18:53:48 +01:00
Samy Pessé 4fec7760a1 Fix error in book parsing 2015-01-23 18:50:10 +01:00
Samy Pessé e6cbea9c70 Change name config "lang" to "language" 2015-01-23 18:48:49 +01:00
Samy Pessé 7945f19285 Fix display of log type 2015-01-23 18:40:42 +01:00
Samy Pessé 5bd1269268 Accept argument "log" from commands 2015-01-23 18:39:07 +01:00
Samy Pessé 1fdd8a7611 Improve logs in book parser 2015-01-23 18:21:03 +01:00
Samy Pessé 112782d118 Add logs methods to Book object 2015-01-23 18:03:15 +01:00
Samy Pessé b88b4a0578 Add context configuration to Book object 2015-01-23 17:40:53 +01:00
Samy Pessé a964e19715 Fix paths indexed in search index 2015-01-23 16:17:04 +01:00
Samy Pessé c47166d700 Fix version in generator tag 2015-01-23 16:04:10 +01:00
Samy Pessé 77834f8166 Accept format argument to build command 2015-01-23 15:51:19 +01:00
Samy Pessé 0fc34e0a35 Use npm version of gitbook-parsers 2015-01-23 15:25:13 +01:00
Samy Pessé df16f3d90d Test ebook generation instead of pdf output 2015-01-23 14:10:45 +01:00
Samy Pessé ccd2f6ecdc Check that readme file is not ignored when parsing 2015-01-23 12:11:27 +01:00
Samy Pessé 19f1d31758 Add base for ebook generator 2015-01-23 00:16:46 +01:00
Samy Pessé ee2420cbcc Change name of generator "site" to "website" 2015-01-22 22:57:08 +01:00
Samy Pessé 322cf59a34 Use html link in search index 2015-01-22 22:46:35 +01:00
Samy Pessé 66841b1bb7 Remove glossary from theme js 2015-01-22 22:36:59 +01:00
Samy Pessé 74ea419c53 Write search index to output folder 2015-01-22 22:31:47 +01:00
Samy Pessé 0c8513801c Write glossary for website 2015-01-22 21:52:13 +01:00
Samy Pessé 90789aa5b9 Use container-based Travis CI stack
cc @winniehell
2015-01-22 21:21:27 +01:00
Samy Pessé 1d13624d3d Replace glossary terms by links to glossary 2015-01-22 19:45:14 +01:00
Samy Pessé 9e172549d1 Mark glossary terms in html parsed 2015-01-22 19:09:03 +01:00
Samy Pessé 0ba36c6f4e Use external gitbook parsers 2015-01-22 18:21:30 +01:00
Samy Pessé c47093e82a Add test for generation of multilingual book 2015-01-22 17:15:06 +01:00
Samy Pessé 57bdc102bc Fix tests of generations 2015-01-22 17:12:46 +01:00
Samy Pessé be651f0138 Separate test for geenration 2015-01-22 17:06:33 +01:00
Samy Pessé 974819bafb Remove parent extension for nunjucks 2015-01-22 17:03:51 +01:00
Samy Pessé f31a272b1f Add options "input" that equals root of the book 2015-01-21 23:04:55 +01:00
Samy Pessé a492c66321 Copy files from book to output
Add test for personalised styles
2015-01-21 23:03:09 +01:00
Samy Pessé 85930161f7 Normalize tests 2015-01-21 22:51:59 +01:00
Samy Pessé 4f9ece1ec1 Fix display of summary in website 2015-01-21 20:26:29 +01:00
Samy Pessé 748f004609 Add summary utils 2015-01-21 20:21:55 +01:00
Samy Pessé cb709930bb Normalize summary in gitbook 2015-01-21 20:21:47 +01:00
Samy Pessé 98027cb008 Fix path to assets in website 2015-01-21 20:04:03 +01:00
Samy Pessé 3d8db17bc3 Copy theme assets to output 2015-01-21 19:51:35 +01:00
Samy Pessé 3c605ea809 generate an index.html for entry points 2015-01-21 19:40:40 +01:00
Samy Pessé bd931c5cbd Adapt themes website templates 2015-01-21 19:15:57 +01:00
Samy Pessé c6e3a27284 Add normalization of plugins 2015-01-21 13:36:09 +01:00
Samy Pessé b5d7e7966f Fix site generator polluting basegenerator 2015-01-21 11:43:39 +01:00
Samy Pessé be2da0fab1 Fix json template preparation 2015-01-21 11:33:33 +01:00
Samy Pessé 0da50819d6 Fix asciidoc test generation 2015-01-21 11:28:59 +01:00
Samy Pessé 9f9265ad76 Add asciidoc support 2015-01-21 10:17:58 +01:00
Samy Pessé a5ebbc34c7 Add styles configuration in book.json 2015-01-20 13:30:22 +01:00
Samy Pessé 33e32b20d9 Use latest version fo theme 2015-01-20 11:32:11 +01:00
Samy Pessé 238cd9f7f0 Add back theme 2015-01-20 11:29:26 +01:00
Samy Pessé bac25cdf29 Improve tests for multilingual json output 2015-01-19 22:44:17 +01:00
Samy Pessé c15399613c Normalize paths in html content 2015-01-19 22:39:23 +01:00
Samy Pessé d6f7f1f4fb Add test for parsing of navigation 2015-01-19 21:19:41 +01:00
Samy Pessé a67dad8cbe Fix list of files and parsing of navigation files only 2015-01-19 21:16:06 +01:00
Samy Pessé b549158483 Parse navigation and progress in parsed page 2015-01-19 20:54:59 +01:00
Samy Pessé 9a2b03b183 Handle different structure in json multilingual output 2015-01-19 20:46:05 +01:00
Samy Pessé 2b2b4f46c2 Complete json generator for multilingual book
Add tests
2015-01-19 20:41:34 +01:00
Samy Pessé 6344928580 Write file in json generator 2015-01-19 18:49:02 +01:00
Samy Pessé 2dbf9b0d4a Start adapting json generator 2015-01-19 17:32:59 +01:00
Samy Pessé be4d2679a4 Add base test for generation 2015-01-19 17:25:16 +01:00
Samy Pessé 70189a5ee2 Fix parsing of plugins list 2015-01-19 16:59:42 +01:00
Samy Pessé ad233c0d8e Improve plugins installation to prevent installation of default plugins 2015-01-19 16:53:20 +01:00
Samy Pessé 41c3db4c04 Add command to install plugins 2015-01-19 16:30:52 +01:00
Samy Pessé 7f01f4b048 Add plugin object 2015-01-19 16:23:44 +01:00
Samy Pessé 5ed5844490 Parse list of files and add test for it 2015-01-19 16:00:12 +01:00
Samy Pessé 777963c97c Change name of Book.init to Book.parse 2015-01-19 15:52:35 +01:00
Samy Pessé 2f3d6f8dff Improve test for parsing multilanguages books 2015-01-19 15:48:22 +01:00
Samy Pessé 2ae3924365 Fix parsing of langs 2015-01-19 15:46:34 +01:00
Samy Pessé d172aaefee Add test for glossary parsing 2015-01-19 15:31:58 +01:00
Samy Pessé f0ea7bc53b Add test for readme parsing 2015-01-19 15:28:13 +01:00
Samy Pessé 793600ab2f Improve test to test a all book 2015-01-19 15:26:31 +01:00
Samy Pessé 354b4afba1 Parse readme 2015-01-19 15:12:08 +01:00
Samy Pessé 8569a74103 Use template before parsing glossary, summary and langs 2015-01-19 15:05:13 +01:00
Samy Pessé b314972195 Add configuration to send structures files 2015-01-19 14:51:35 +01:00
Samy Pessé 52179163dd Fix output for sub-books in multi languages 2015-01-19 13:27:17 +01:00
Samy Pessé 8ed9f645fc Fix parsing of langs
Add command to build
2015-01-19 13:22:34 +01:00
Samy Pessé e1594cde2c Remove doc about custom styles 2015-01-19 13:14:52 +01:00
Samy Pessé 60993d3123 Parse langs and glossary 2015-01-19 12:06:04 +01:00
Samy Pessé 39b2aaf489 Add parsing of summary and associated tests 2015-01-19 10:59:43 +01:00
Samy Pessé 58b04da60f Add test for loading configuration from book.js 2015-01-19 10:15:53 +01:00
Samy Pessé d9c020194d Add base object to load configuration 2015-01-19 10:08:49 +01:00
Samy Pessé ec586dd3cd Clear folder 2015-01-19 09:47:36 +01:00
Samy Pessé 8043216170 Merge pull request #555 from GitbookIO/feature/styles
Add custom styles options
2015-01-15 06:29:14 +01:00
Samy Pessé 3c7372dff5 Add doc about custom styles 2015-01-14 22:53:27 +01:00
Samy Pessé 2cab018791 Merge pull request #553 from GitbookIO/fix/summary-whitespace
Fix/summary whitespace
2015-01-14 22:41:34 +01:00
Samy Pessé 9467cda131 Apply styles to ebook output 2015-01-14 22:31:36 +01:00
Samy Pessé 3382f1b084 Include styles in website format 2015-01-14 22:19:40 +01:00
Aaron O'Mullan ae52ef5b78 Handle whitespace between lists in SUMMARY.md 2015-01-14 22:17:15 +01:00
Aaron O'Mullan f88951546d Add test/bin/summary.js
Another small useful tool
2015-01-14 22:16:37 +01:00
Samy Pessé 7d90cd2a9e Add default values for "styles" 2015-01-14 22:08:42 +01:00
Samy Pessé 94a9db3d54 Move theme folder "book" to "website" 2015-01-14 22:08:31 +01:00
Aaron O'Mullan 3f36d83bfb Add test/bin/lex.js utility
Helpful binary when developing / debugging
2015-01-14 21:56:57 +01:00
Aaron O'Mullan c7e3370ac0 Add summary whitespace tests 2015-01-14 21:45:32 +01:00
Samy Pessé bcb34dcd79 Merge pull request #547 from JozoVilcek/patch-2
Change link to mermaid plugin
2015-01-12 00:13:17 +01:00
Jozef Vilcek 71905aa0c8 Change link to mermaid plugin
Repository name for mermaid plugin has changed to better reflect naming conventions
2015-01-12 00:11:02 +01:00
Samy Pessé dcbf115f25 Adapt platform command to gitbook.com 2015-01-11 15:14:50 +01:00
Samy Pessé 668f2f9e2c Update urls in package.json 2015-01-11 15:09:25 +01:00
Samy Pessé 530975b4fb Improve names of plugins 2015-01-11 14:53:57 +01:00
Samy Pessé d35ce31af2 Merge pull request #545 from JozoVilcek/patch-1
Enlist gitbook-mermaid plugin
2015-01-11 14:52:49 +01:00
Jozef Vilcek e5f17cd694 Enlist gitbook-mermaid plugin 2015-01-11 13:52:11 +01:00
Aaron O'Mullan b50dea9c70 Update kramed to 0.4.4 2015-01-06 15:15:48 +01:00
Aaron O'Mullan 6b76032bb1 Bump version to 1.5.0 2014-12-17 17:08:09 +01:00
Aaron O'Mullan 85b09eda56 Add 1.4.2's changes 2014-12-17 17:07:10 +01:00
Aaron O'Mullan c4be78a81f Merge pull request #506 from Nijikokun/feature/clarity-midnight
Clarity: Midnight Theme (Night)
2014-12-16 18:53:09 +01:00
Aaron O'Mullan e7309f71d2 Merge pull request #530 from GitbookIO/fix/cli-error-handling
Robust CLI error handling
2014-12-16 17:46:01 +01:00
Aaron O'Mullan 6c979c9419 Robust CLI error handling
Fixes #528, fixes #524, fixes #518
2014-12-16 16:05:50 +01:00
Aaron O'Mullan 7fd7732345 Bump version to 1.4.2 2014-12-10 23:27:01 +01:00
Aaron O'Mullan 960ef4d945 Fix descriptor and me leak with bad write queues
Node.js for some reason queues up writes from fs.writeFile causing
memory and descriptor leaks, preventing building large books. This is
now fixed

Fixes #336, fixes #454
2014-12-10 16:32:49 +01:00
Aaron O'Mullan 92523fb4b5 Force process.exit at end of build 2014-12-09 17:31:23 +01:00
Samy Pessé 66bb46df22 Merge pull request #516 from jr0cket/patch-1
Add gitbook init command to "how to use" section
2014-12-08 00:46:04 +01:00
John Stevenson 0454710d3b Add gitbook init command to "how to use" section
Highlight that Gitbook can generate the files and directories for a book from its SUMMARY.md file.
2014-12-07 23:44:41 +00:00
Samy Pessé c9a31cc987 Add option isbn for books 2014-12-03 23:19:47 +01:00
Samy Pessé f03c316274 Bump version to 1.4.1 2014-11-30 17:01:47 +01:00
Samy Pessé 001a4f91be Fix command 'install' without arguments 2014-11-30 17:00:12 +01:00
Samy Pessé 44a3bfe296 Bump version to 1.4.0 2014-11-30 14:59:08 +01:00
Samy Pessé a9ce6f808d Add doc about gitbook install 2014-11-30 14:54:34 +01:00
Samy Pessé e5c667e0fa Add test for parsing of plugins list with version 2014-11-30 14:50:47 +01:00
Samy Pessé e36f274dbe Handle specific version for plugins 2014-11-30 14:46:21 +01:00
Samy Pessé 2a88427e74 Improve error message when plugins not found 2014-11-30 13:13:00 +01:00
Samy Pessé ed48f5f3ee Add command "install" to install plugins from book.json 2014-11-30 13:04:38 +01:00
Samy Pessé 7b4accb117 Bump version to 1.3.4 2014-11-25 18:55:33 +01:00
Samy Pessé d6437d31d4 Omit "generator" when building file with config file 2014-11-25 18:53:02 +01:00
Samy Pessé 3dd33f1f3c Merge pull request #502 from tonka3000/X-UA-Compatible-tag
add X-UA-Compatible meta tag for IE
2014-11-25 15:20:14 +01:00
Samy Pessé 3a0f00411b Fix #505: add glossary to ebook file 2014-11-24 22:08:19 +01:00
Samy Pessé 5b655bb29a Fx typo 2014-11-24 21:56:33 +01:00
Samy Pessé faf10605e8 Add hook finish:before 2014-11-24 21:41:50 +01:00
Nijiko Yonskai c52aecdbd5 Initial release 2014-11-22 20:37:06 -08:00
Samy Pessé 5c98b3350b Merge pull request #503 from jr0cket/ebook-command-fix
updated eBook generation commands - resolves #432
2014-11-22 16:41:19 +01:00
John Stevenson 6f4e1d10c1 made commands clearer by seperating output filename option 2014-11-22 14:47:12 +00:00
John Stevenson 870886599a updated eBook generation commands 2014-11-22 11:26:08 +00:00
Michael Aigner 1b9801b8a4 add X-UA-Compatible meta tag for IE (https://github.com/GitbookIO/gitbook/issues/501) 2014-11-20 10:16:40 +01:00
Samy Pessé 897463b22e Bump version to 1.3.3 2014-11-14 22:10:19 +01:00
Samy Pessé aa4894e94c Fix parsing of already lexed content 2014-11-14 22:09:30 +01:00
Samy Pessé 8a0180ae6b Bump version to 1.3.2 2014-11-14 18:53:57 +01:00
Samy Pessé d6776b98d3 Fix bundle task 2014-11-14 18:53:48 +01:00
Samy Pessé 6a04ba89a5 Update gitbook bundle 2014-11-14 18:53:39 +01:00
Samy Pessé 05eaeeb0d0 Fix #441: Improve validity of epub output 2014-11-13 08:34:35 +01:00
Samy Pessé 77043464b2 Fix #496: improve replacement of glossary terms in html 2014-11-12 22:10:38 +01:00
Samy Pessé b90f74a821 Merge pull request #494 from cnDelbert/master
Bug fix for issue#493: 2-level or more deep relative links go wrong when in win32
2014-11-12 19:06:33 +01:00
Aaron O'Mullan f804eb42ac Merge pull request #495 from romanlytkin/patch-1
Update README.md
2014-11-07 15:58:18 +01:00
romanlytkin 74fd8c159b Update README.md
Hi, i add 2 new plugins for gitbook, I hope it will be useful to someone.
2014-11-07 17:37:27 +03:00
Delbert a741ec1350 Bug fix for issue #493: 2-level or more deep relative links may go wrong when in win32 2014-11-06 15:50:59 +08:00
Samy Pessé 4b26c31b3b Remove unnecessary option "repo" from tests 2014-11-03 17:43:44 +01:00
Samy Pessé dfd487692a Adapt new page.parse api for tests 2014-11-03 17:40:38 +01:00
Samy Pessé 195374ecee Merge pull request #489 from codepiano/fork
bugfix,init page object's lexed attribute.
2014-11-03 17:29:54 +01:00
Samy Pessé 1659778a77 Bump version to 1.3.1 2014-11-03 16:58:37 +01:00
Samy Pessé 40c4035afb Fix #491: handle correctly url without path for links 2014-11-03 16:57:35 +01:00
codepiano 1502d65286 Merge branch 'master' of github.com:GitbookIO/gitbook into fork
Conflicts:
	lib/parse/page.js
2014-11-02 01:34:28 +08:00
Samy Pessé 0feb672d70 Bump version to 1.3.0 2014-11-01 15:36:10 +01:00
Samy Pessé 2a4ef5bbf4 Fix dependency in renderer 2014-11-01 15:21:16 +01:00
Samy Pessé c4540332a7 Bundle gitbook parser as a client side library 2014-11-01 15:09:58 +01:00
Samy Pessé 2104404cb3 Remove dependency for path in parser 2014-11-01 15:09:41 +01:00
Samy Pessé 002d685ebb Adapt include in page parsing to works on clientside 2014-11-01 15:00:13 +01:00
codepiano 9da10e2b25 bugfix,init page object's lexed attribute.
the page object's attribute 'lexed' is not correctly inited.so when
use page.lexed to generate the search index file,nothing will be
indexed.
2014-10-31 18:00:09 +08:00
182 changed files with 5411 additions and 4425 deletions
+10
View File
@@ -0,0 +1,10 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
+3
View File
@@ -1,4 +1,7 @@
sudo: false
language: node_js
node_js:
- "0.11"
- "0.10"
before_install:
- npm install svgexport -g
+28 -4
View File
@@ -7,12 +7,36 @@ Names below are ordered by first contribution.
Author
------
- Samy Pessé <samypesse@gmail.com>
- Aaron O'Mullan <aaron.omullan@gmail.com>
- Samy Pessé <samypesse@gmail.com> (@SamyPesse)
- Aaron O'Mullan <aaron.omullan@gmail.com> (@AaronO)
Contributors
------------
- Nijiko Yonskai
- Herman Starikov
- Nijiko Yonskai (@Nijikokun)
- Herman Starikov (@Hermanya)
Translators
------------
- German
- Winnie (@winniehell)
- Italian
- Giulio Bonanome (@gbonanome)
- Russian
- Andrey Akinshin (@AndreyAkinshin)
- Norwegian
- Knut Melvær (@kmelve)
- Persian (Farsi)
- Mohammad Hossein Mojtahedi (@mhm5000)
- Portuguese
- Ryan O'Mullan (@RyanOM)
- Spanish
- Ryan O'Mullan (@RyanOM)
- Simplifiled Chinese
- Hu Hao (@howiehu)
- Traditional Chinese
- Hu Hao (@howiehu)
- French
- Samy Pessé (@SamyPesse)
+109
View File
@@ -1,4 +1,113 @@
# Release notes
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 2.0.0-beta.4
- Fix default generator for use programmatically
- Add option "author" for html meta tags
- Fix links normalization (content and hash)
## 2.0.0-beta.3
- Fix odd cases with code blocks escaping
## 2.0.0-beta.2
- Fix definition of entry point title using SUMMARY.md
## 2.0.0-beta.1
- Fix windows incompatibility
- Add support for rtl (enabled by default for `ar` and `fa`)
- Escape code blocks in markdown parser
- Add Persian/Farsi translation (`fa`)
- Add Arabic translation (`ar`)
- Add Bengali translation (`bn`)
- Provide generator name in template context
## 2.0.0-alpha.9
- Fix links in sidebar
- Fix normalization of html link (README to index)
- Fix html snippets escaping
## 2.0.0-alpha.8
- Improve locale detection for i18n
- Fix chapter name for Glossary in pdf
- Don't escape html in glossary items
- Fix generation of multilingual book as ebook
- Add "post" block attribute to post-process
## 2.0.0-alpha.7
- Fix display of glossary in ebook formats
- Add default footer and header to pdf
- Fix generation of json format compatible with 1.x.x
- Add Simplifiled Chinese and Traditional Chinese translations
## 2.0.0-alpha.6
- Add es and pt translations
- Fix replacement of glossary terms
## 2.0.0-alpha.5
- Fix copy of files/covers
- Add back `finish:before` hook
## 2.0.0-alpha.4
- Fix copy of cover for multilingal books
## 2.0.0-alpha.3
- Norwegian translation
- Load plugins from book in priority
## 2.0.0-alpha.3
- Fix init command
- Update parsers to fix spaces in summary (`gitbook-parsers@0.3.1`)
## 2.0.0-alpha.1
- Externalize parsing into `gitbook-parsers` module
- Supports AsciiDoc and reStructuredText
- Hooks for page (`page:*`) are now deprecated, plugins should extend filters and blocks instead
- Hooks `summary` and `glossary` (after and before) have been removed
- Exercises and Quizzes are no longer parsed in the markdown parser
- Support for more markdown extensions: `.markdown`, `.mdown`
- Templates are rendered with nunjucks instead of swig, syntax is almost compatible, there is some changes with contexts and filters. `{{ super() }}` should be use instead of `{% parent %}`
- Clean output folder on build without removing `.git` and `.svn`
- MathJAX is no longer a default plugin
- SVG images are converted to PNG during generation of ebooks
- i18n in website and ebook (ru, it, de, fr)
- New templating syntax
- Content references (both internal and external)
- Glossary terms are handled during generation (also in ebook format)
## 1.5.0
- Fix `serve` command, broken by `1.4.2`
- Add nicer `dark` theme :)
## 1.4.2
- Force `process.exit` after builds, to prevent (possibly) lingering plugins
## 1.4.1
- Fix command 'install' without arguments
## 1.4.0
- Add command `gitbook install` to install plugins from book.json
- `package.json` is no longer necessary
## 1.3.4
- Add glossary to ebooks
- Fix autocover with new hook "finish:before"
- Add X-UA-Compatible meta tag for IE
## 1.3.3
- Fix parsing of lexed content using the client library
## 1.3.2
- ePub files are now passing validation from epubcheck
- Fix replacement of multiple glossary terms in a single sentence
- Fix on windows deep relative links
- Fix search indexer
## 1.3.1
- Fix error with links in markdown
## 1.3.0
- Bundle gitbook parsing library as a client side library in `gitbook.js` and `gitbook.min.js`
## 1.2.0
- Improvements on ebook generation
+1 -1
View File
@@ -93,4 +93,4 @@ module.exports = function (grunt) {
grunt.registerTask('default', [
'build'
]);
};
};
+116 -134
View File
@@ -1,10 +1,17 @@
GitBook
=======
[![Build Status](https://travis-ci.org/GitbookIO/gitbook.png?branch=master)](https://travis-ci.org/GitbookIO/gitbook)
[![NPM version](https://badge.fury.io/js/gitbook.svg)](http://badge.fury.io/js/gitbook)
[![Build Status](https://travis-ci.org/GitbookIO/gitbook.png?branch=master)](https://travis-ci.org/GitbookIO/gitbook)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/GitbookIO/gitbook?branch=master&svg=true)](https://ci.appveyor.com/project/SamyPesse/gitbook)
GitBook is a command line tool (and Node.js library) for building beautiful books using GitHub/Git and Markdown. Here is an example: [Learn Javascript](https://www.gitbook.io/book/GitBookIO/javascript). You can publish book easily online using [gitbook.io](https://www.gitbook.io) and an [editor](https://github.com/GitbookIO/editor) is available for Windows, Mac and Linux. You can follow [@GitBookIO](https://twitter.com/GitBookIO) on Twitter. Complete documentation is available at [help.gitbook.io](http://help.gitbook.io/).
GitBook is a command line tool (and Node.js library) for building beautiful books using GitHub/Git and Markdown (or AsciiDoc). Here is an example: [Learn Javascript](https://www.gitbook.com/book/GitBookIO/javascript).
You can publish and host book easily online using [gitbook.com](https://www.gitbook.com), a web-editor is [also available](https://www.gitbook.com/editor).
Stay updated by following [@GitBookIO](https://twitter.com/GitBookIO) on Twitter or [GitBook](https://www.facebook.com/gitbookcom) on Facebook.
Complete documentation is available at [help.gitbook.com](http://help.gitbook.com/).
![Image](https://raw.github.com/GitbookIO/gitbook/master/preview.png)
@@ -13,7 +20,13 @@ GitBook is a command line tool (and Node.js library) for building beautiful book
GitBook can be installed from **NPM** using:
```
$ npm install gitbook -g
$ npm install gitbook-cli -g
```
Create the directories and files for a book from its [SUMMARY.md](https://github.com/GitbookIO/gitbook#book-format) file (if existing) using
```
$ gitbook init
```
You can serve a repository as a book using:
@@ -25,115 +38,34 @@ $ gitbook serve ./repository
Or simply build the static website using:
```
$ gitbook build ./repository --output=./outputFolder
$ gitbook build ./repository ./outputFolder
```
Options for commands `build` and `serve` are:
## Features
```
-o, --output <directory> Path to output directory, defaults to ./_book
-f, --format <name> Change generation format, defaults to site, availables are: site, page, ebook, json
--config <config file> Configuration file to use, defaults to book.js or book.json
```
GitBook loads the default configuration from a `book.json` file in the repository if it exists.
Here are the options that can be stored in this file:
```js
{
// 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",
// Book title and description (defaults are extracted from the README)
"title": null,
"description": 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": "sepia", "night" or "white",
"family": "serif" or "sans",
"size": 1 to 4
}
},
// Variables for templating
"variables": {},
// 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
}
},
// Options for PDF generation
"pdf": {
// Add page numbers to the bottom of every page
"pageNumbers": false,
// Font size for the fiel content
"fontSize": 12,
// Paper size for the pdf
// Choices are [ua0, ua1, ua2, ua3, ua4, ua5, ua6, ub0, ub1, ub2, ub3, ub4, ub5, ub6, ulegal, uletter]
"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": null,
//Footer HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"footerTemplate": null
}
}
```
You can publish your books to our index by visiting [GitBook.io](http://www.gitbook.io)
* [Output as a website or ebook (pdf, epub, mobi)](http://help.gitbook.com/format/output.html)
* [Multi-Languages](http://help.gitbook.com/format/languages.html)
* [Glossary](http://help.gitbook.com/format/glossary.html)
* [Cover](http://help.gitbook.com/format/cover.html)
* [AsciiDoc Support](http://help.gitbook.com/format/asciidoc.html)
* [Variables and Templating](http://help.gitbook.com/format/templating.html)
* [Content References](http://help.gitbook.com/format/conrefs.html)
* [Plugins](http://help.gitbook.com/plugins/index.html)
* Interractive reader website:
* Search
* Font Settings (Serif, Sans Serif)
* Themes: white, sepia, night
## Output Formats
GitBook can generate your book in the following formats:
* **Static Website**: This is the default format. It generates a complete interactive static website that can be, for example, hosted on GitHub Pages.
* **eBook**: A complete eBook with exercise solutions at the end of the book. Generate this format using: ```gitbook ebook ./myrepo```. You need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed. The output format could be **PDF**, **ePub** or **MOBI**.
* **JSON**: This format is used for debugging or extracting metadata from a book. Generate this format using: ```gitbook build ./myrepo -f json```.
* **eBook**: You need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed. You can specify the eBook filename as the second argument, otherwise `book` will be used.
* Generate a **PDF** using: `gitbook pdf ./myrepo ./mybook.pdf`
* Generate a **ePub** using: `gitbook epub ./myrepo ./mybook.epub`
* Generate a **MOBI** using: `gitbook mobi ./myrepo ./mybook.mobi`
* **JSON**: This format is used for debugging or extracting metadata from a book. Generate this format using: ```gitbook build ./myrepo --format=json```.
## Book Format
@@ -187,7 +119,64 @@ Definition for this term
# Another term
With it's definition, this can contain bold text and all other kinds of inline markup ...
```
#### Variables and Templating
A set of variables can be defined in the `book.json`:
```js
{
"variables": {
"host": "mybook.com"
}
}
```
These variables can be used in the markdown files:
```
The host is {{ book.host }}
```
You can also use condition with these variables:
```
{% if book.host == "mybook.com" %}
{% else %}
{% endif %}
```
Variables of `book.json` are available in the `book` namespace. You can also access informations about the `file` itself and the `gitbook` version:
```
My file is {{ file.path }}
Modified at {{ file.mtime }}
Book built with GitBook {{ gitbook.version }}
```
#### Content References
You can use "content references," or conrefs, when writing books or documentation using GitBook.
Include a file from the same book:
```
{% include "./test.md" %}
```
or from a git repository (with a specific revision):
```
{% include "git+https://github.com/GitbookIO/documentation.git/README.md#1.0.1" %}
```
Includes can be used with variables (see [Variables and Templating](#variables-and-templating)):
```
{% include book.ref_doc_readme %}
```
#### Ignoring files & folders
@@ -203,45 +192,38 @@ The best resolution is **1800x2360**. The generation of the cover can be done au
A small version of the cover can also be set by creating a file: **/cover_small.jpg**.
#### Publish your book
#### AsciiDoc
The platform [GitBook.io](https://www.gitbook.io/) is like an "Heroku for books": you can create a book on it (public, paid, or private) and update it using **git push**.
Since version 2.0.0, AsciiDoc can be used instead of Markdown, simply replace the `.md` by the `.adoc` extension. Chapters in the summary are detected from an ordered list in the `SUMMARY.adoc`.
#### Plugins
## Publish your book
The platform [GitBook.com](https://www.gitbook.com/) is like an "Heroku for books": you can create a book on it (public, paid, or private) and update it using **git push**.
## Plugins
Plugins can be used to extend your book's functionality. Read [GitbookIO/plugin](https://github.com/GitbookIO/plugin) for more information about how to build a plugin for GitBook.
##### Official plugins:
Plugins needed to build a book can be installed using: `gitbook install ./`. You can find plugins at [plugins.gitbook.com](http://plugins.gitbook.com).
| Name | Description |
| ----- | ---- |
| [exercises](https://github.com/GitbookIO/plugin-exercises) | Add interactive exercises to your book. |
| [quizzes](https://github.com/GitbookIO/plugin-quizzes) | Add interactive quizzes to your book. |
| [mathjax](https://github.com/GitbookIO/plugin-mathjax) | Displays mathematical notation in the book. |
| [mixpanel](https://github.com/GitbookIO/plugin-mixpanel) | Mixpanel tracking for your book |
| [infinitescroll](https://github.com/GitbookIO/gitbook-plugin-infinitescroll) | Infinite Scrolling |
##### Other plugins:
## Debugging
| Name | Description |
| ----- | ---- |
| [Google Analytics](https://github.com/GitbookIO/plugin-ga) | Google Analytics tracking for your book |
| [Disqus](https://github.com/GitbookIO/plugin-disqus) | Disqus comments integration in your book |
| [Autocover](https://github.com/GitbookIO/plugin-autocover) | Generate a cover for your book |
| [Transform annoted quotes to notes](https://github.com/erixtekila/gitbook-plugin-richquotes) | Allow extra markdown markup to render blockquotes as nice notes |
| [Send code to console](https://github.com/erixtekila/gitbook-plugin-toconsole) | Evaluate javascript block in the browser inspector's console |
| [Revealable sections](https://github.com/mrpotes/gitbook-plugin-reveal) | Reveal sections of the page using buttons made from the first title in each section |
| [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 |
| [JSBin](https://github.com/jcouyang/gitbook-plugin-jsbin) | Embedded jsbin frame into your book |
#### Debugging
You can use the environment variable `DEBUG=true` to get better error messages (with stack trace). For example:
You can use the options `--log=debug` and `--debug` to get better error messages (with stack trace). For example:
```
$ export DEBUG=true
$ gitbook build ./
$ gitbook build ./ -- log=debug --debug
```
#### How to use the latest commit from GitBook in gitbook-cli
To use the latest commit from `GitBook/gitbook` with `gitbook-cli`:
```
$ git clone https://github.com/GitbookIO/gitbook.git ./gitbook
$ gitbook versions:link 2.0.0 ./gitbook
```
Now the version tag `2.0.0` wil be associated with the `./gitbook` folder.
You can uninstall it using: `gitbook versions:uninstall 2.0.0`.
+29
View File
@@ -0,0 +1,29 @@
# Fix line endings in Windows. (runs before repo cloning)
init:
- git config --global core.autocrlf input
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.10"
- nodejs_version: "0.11"
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# install svgexport
- npm install svgexport -g
# install modules
- npm install
# Post-install test scripts.
test_script:
# Output useful info for debugging.
- node --version
- npm --version
# run tests
- npm test
# Don't actually build.
build: off
-63
View File
@@ -1,63 +0,0 @@
var path = require('path');
var Q = require('q');
var _ = require('lodash');
var fs = require('fs');
var utils = require('./utils');
var generate = require("../lib/generate");
var parse = require("../lib/parse");
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');
};
var buildEbookCommand = function(command) {
return buildCommand(command)
.option('-c, --cover <path>', 'Cover image, default is cover.jpg if exists');
};
var makeBuildFunc = function(converter) {
return function(dir, options) {
dir = dir || process.cwd();
outputDir = options.output;
// Set debugging
if(options.verbose) {
process.env.DEBUG = "true";
}
console.log('Starting build ...');
return converter(
_.extend({}, options || {}, {
input: dir,
output: outputDir,
generator: options.format,
configFile: options.config
})
)
.then(function(output) {
console.log("Successfully built!");
return output;
})
.fail(function(err) {
// Log error
utils.logError(err);
// Exit process with failure code
process.exit(-1);
});
};
};
module.exports = {
folder: makeBuildFunc(generate.folder),
file: makeBuildFunc(generate.file),
command: buildCommand,
commandEbook: buildEbookCommand
};
+4 -134
View File
@@ -1,137 +1,7 @@
#! /usr/bin/env node
var Q = require('q');
var _ = require('lodash');
var path = require('path');
var prog = require('commander');
var tinylr = require('tiny-lr-fork');
var color = require('bash-color');
var pkg = require('../package.json');
var generators = require("../lib/generate").generators;
var initDir = require("../lib/generate/init");
var fs = require('../lib/generate/fs');
var utils = require('./utils');
var build = require('./build');
var Server = require('./server');
var platform = require("./platform");
// General options
prog
.version(pkg.version);
build.command(prog.command('build [source_dir]'))
.description('Build a gitbook from a directory')
.action(build.folder);
build.command(prog.command('serve [source_dir]'))
.description('Build then serve a gitbook from a directory')
.option('-p, --port <port>', 'Port for server to listen on', 4000)
.option('--no-watch', 'Disable restart with file watching')
.action(function(dir, options) {
var server = new Server();
// init livereload server
var lrOptions = {port: 35729};
var lrServer = tinylr(lrOptions);
var lrPath = undefined;
lrServer.listen(lrOptions.port, function(err) {
if (err) { return console.log(err); }
console.log('Live reload server started on port: ' + lrOptions.port);
});
var generate = function() {
if (server.isRunning()) console.log("Stopping server");
server.stop()
.then(function() {
return build.folder(dir, _.extend(options || {}, {
defaultsPlugins: ["livereload"]
}));
})
.then(function(_options) {
console.log();
console.log('Starting server ...');
return server.start(_options.output, options.port)
.then(function() {
console.log('Serving book on http://localhost:'+options.port);
if (lrPath) {
// trigger livereload
lrServer.changed({body:{files:[lrPath]}})
}
if (!options.watch) return;
return utils.watch(_options.input)
.then(function(filepath) {
// set livereload path
lrPath = filepath;
console.log("Restart after change in files");
console.log('');
return generate();
})
})
})
.fail(utils.logError);
};
console.log('Press CTRL+C to quit ...');
console.log('')
generate();
});
build.commandEbook(prog.command('pdf [source_dir]'))
.description('Build a gitbook as a PDF')
.action(function(dir, options) {
build.file(dir, _.extend(options, {
extension: "pdf",
format: "ebook"
}));
});
build.commandEbook(prog.command('epub [source_dir]'))
.description('Build a gitbook as a ePub book')
.action(function(dir, options) {
build.file(dir, _.extend(options, {
extension: "epub",
format: "ebook"
}));
});
build.commandEbook(prog.command('mobi [source_dir]'))
.description('Build a gitbook as a Mobi book')
.action(function(dir, options) {
build.file(dir, _.extend(options, {
extension: "mobi",
format: "ebook"
}));
});
prog
.command('init [source_dir]')
.description('Create files and folders based on contents of SUMMARY.md')
.action(function(dir) {
dir = dir || process.cwd();
return initDir(dir);
});
prog
.command('publish [source_dir]')
.description('Publish content to the associated gitbook.io book')
.action(function(dir) {
dir = dir || process.cwd();
return platform.publish(dir);
});
prog
.command('git:remote [source_dir] [book_id]')
.description('Adds a git remote to a book repository')
.action(function(dir, bookId) {
dir = dir || process.cwd();
return platform.remote(dir, bookId);
});
// Parse and fallback to help if no args
if(_.isEmpty(prog.parse(process.argv).args) && process.argv.length === 2) {
prog.help();
}
console.log(color.red("You need to install 'gitbook-cli' to have access to the gitbook command anywhere on your system."));
console.log(color.red("If you've installed this package globally, you need to uninstall it."));
console.log(color.red(">> Run 'npm uninstall -g gitbook' then 'npm install -g gitbook-cli'"));
-53
View File
@@ -1,53 +0,0 @@
var Q = require("q");
var utils = require("./utils");
var publish = function(folder) {
if (!folder) {
console.log("Need a repository folder");
return process.exit(-1);
}
utils.gitCmd("push", ["gitbook", "master"])
.then(function(out) {
console.log(out.stdout);
}, function(err) {
if (err.code == 128) {
console.log("No book on gitbook.io is configured with this git repository.");
console.log("Run 'gitbook git:remote username/book' to intialize this repository.");
} else {
console.log(err.message);
}
process.exit(-1);
});
};
var remote = function(folder, bookId) {
if (!folder || !bookId) {
console.log("Need a repository folder and a book id");
return process.exit(-1);
}
var url = "https://push.gitbook.io/"+bookId+".git";
var addRemote = function() {
return utils.gitCmd("remote", ["add", "gitbook", url]);
}
addRemote()
.fail(function(err) {
if (err.code == 128) {
return utils.gitCmd("remote", ["rm", "gitbook"]).then(addRemote);
}
return Q.reject(err);
})
.then(function(out) {
console.log("Book remote '"+url+"' added to the folder");
}, function(err) {
console.log(err.message);
process.exit(-1);
});
};
module.exports = {
publish: publish,
remote: remote
};
-66
View File
@@ -1,66 +0,0 @@
var Q = require('q');
var _ = require('lodash');
var exec = require('child_process').exec;
var http = require('http');
var send = require('send');
var path = require('path');
var Gaze = require('gaze').Gaze;
function watch(dir) {
var d = Q.defer();
dir = path.resolve(dir);
var gaze = new Gaze("**/*.md", {
cwd: dir
});
gaze.once("all", function(e, filepath) {
gaze.close();
d.resolve(filepath);
});
gaze.once("error", function(err) {
gaze.close();
d.reject(err);
});
return d.promise;
}
function logError(err) {
var message = err.message || err;
if (process.env.DEBUG != null) message = err.stack || message;
console.log(message);
return Q.reject(err);
};
function runGitCommand(command, args) {
var d = Q.defer(), child;
args = ["git", command].concat(args).join(" ");
child = exec(args, function (error, stdout, stderr) {
if (error !== null) {
error.stdout = stdout;
error.stderr = stderr;
d.reject(error);
} else {
d.resolve({
stdout: stdout,
stderr: stderr
})
}
});
return d.promise;
};
// Exports
module.exports = {
watch: watch,
logError: logError,
gitCmd: runGitCommand
};
+791
View File
@@ -0,0 +1,791 @@
var Q = require("q");
var _ = require("lodash");
var path = require("path");
var util = require("util");
var lunr = require('lunr');
var parsers = require("gitbook-parsers");
var color = require('bash-color');
var fs = require("./utils/fs");
var parseNavigation = require("./utils/navigation");
var parseProgress = require("./utils/progress");
var pageUtil = require("./utils/page");
var batch = require("./utils/batch");
var links = require("./utils/links");
var logger = require("./utils/logger");
var Configuration = require("./configuration");
var TemplateEngine = require("./template");
var Plugin = require("./plugin");
var PluginsList = require("./pluginslist");
var generators = require("./generators");
var Book = function(root, context, parent) {
var that = this;
this.context = _.defaults(context || {}, {
// Extend book configuration
config: {},
// Log function
log: function(msg) {
process.stdout.write(msg);
},
// Log level
logLevel: "info"
});
// Log
this.log = logger(this.context.log, this.context.logLevel);
// Root folder of the book
this.root = path.resolve(root);
// Parent book
this.parent = parent;
// Configuration
this.config = new Configuration(this, this.context.config);
Object.defineProperty(this, "options", {
get: function () {
return this.config.options;
}
});
// Template
this.template = new TemplateEngine(this);
// Summary
this.summary = {};
this.navigation = [];
// Glossary
this.glossary = [];
// Langs
this.langs = [];
// Sub-books
this.books = [];
// Files in the book
this.files = [];
// List of plugins
this.plugins = new PluginsList(this);
// Structure files
this.summaryFile = null;
this.glossaryFile = null;
this.readmeFile = null;
this.langsFile = null;
// Search Index
this.searchIndex = lunr(function () {
this.ref('url');
this.field('title', { boost: 10 });
this.field('body');
});
// Bind methods
_.bindAll(this);
};
// Initialize and parse the book: config, summary, glossary
Book.prototype.parse = function() {
var that = this;
var multilingual = false;
return this.parseConfig()
.then(function() {
return that.parsePlugins();
})
.then(function() {
return that.parseLangs()
.then(function() {
multilingual = that.langs.length > 0;
if (multilingual) that.log.info.ln("Parsing multilingual book, with", that.langs.length, "lanuages");
// Sub-books that inherit from the current book configuration
that.books = _.map(that.langs, function(lang) {
that.log.info.ln("Preparing language book", lang.lang);
return new Book(
path.join(that.root, lang.path),
_.merge({}, that.context, {
config: _.extend({}, that.options, {
'output': path.join(that.options.output, lang.lang),
'language': lang.lang
})
}),
that
)
});
});
})
.then(function() {
if (multilingual) return;
return that.listAllFiles();
})
.then(function() {
if (multilingual) return;
return that.parseReadme();
})
.then(function() {
if (multilingual) return;
return that.parseSummary();
})
.then(function() {
if (multilingual) return;
return that.parseGlossary();
})
.then(function() {
// Init sub-books
return _.reduce(that.books, function(prev, book) {
return prev.then(function() {
return book.parse();
});
}, Q());
})
.thenResolve(this);
};
// Generate the output
Book.prototype.generate = function(generator) {
var that = this;
that.options.generator = generator || that.options.generator;
that.log.info.ln("start generation with", that.options.generator, "generator");
return Q()
// Clean output folder
.then(function() {
that.log.info("clean", that.options.generator, "generator");
return fs.clean(that.options.output)
.progress(function(p) {
that.log.debug.ln("remove", p.file, "("+p.i+"/"+p.count+")");
})
.then(function() {
that.log.info.ok();
});
})
// Create generator
.then(function() {
var Generator = generators[generator];
if (!Generator) throw "Generator '"+that.options.generator+"' doesn't exist";
generator = new Generator(that);
return generator.prepare();
})
// Generate content
.then(function() {
if (that.isMultilingual()) {
return that.generateMultiLingual(generator);
} else {
// Separate list of files into the different operations needed
var ops = _.groupBy(that.files, function(file) {
if (file[file.length -1] == "/") {
return "directories";
} else if (_.contains(parsers.extensions, path.extname(file)) && that.navigation[file]) {
return "content";
} else {
return "files";
}
});
return Q()
// First, let's create folder
.then(function() {
return _.reduce(ops["directories"] || [], function(prev, folder) {
return prev.then(function() {
that.log.debug.ln("transferring folder", folder);
return Q(generator.transferFolder(folder));
});
}, Q());
})
// Then, let's copy other files
.then(function() {
return Q.all(_.map(ops["files"] || [], function(file) {
that.log.debug.ln("transferring file", file);
return Q(generator.transferFile(file));
}));
})
// Finally let's generate content
.then(function() {
var nFiles = (ops["content"] || []).length;
return _.reduce(ops["content"] || [], function(prev, file, i) {
return prev.then(function() {
var p = ((i*100)/nFiles).toFixed(0)+"%";
that.log.info.ln("processing", file, p);
return Q(generator.convertFile(file));
});
}, Q());
});
}
})
// Finish generation
.then(function() {
return generator.callHook("finish:before");
})
.then(function() {
return generator.finish();
})
.then(function() {
return generator.callHook("finish");
})
.then(function() {
that.log.info.ln("generation is finished");
});
};
// Generate the output for a multilingual book
Book.prototype.generateMultiLingual = function(generator) {
var that = this;
return Q()
.then(function() {
// Generate sub-books
return _.reduce(that.books, function(prev, book) {
return prev.then(function() {
return book.generate(that.options.generator);
});
}, Q());
});
};
// Extract files from ebook generated
Book.prototype.generateFile = function(output, options) {
var book = this;
options = _.defaults(options || {}, {
ebookFormat: path.extname(output).slice(1)
});
output = output || path.resolve(book.root, "book."+options.ebookFormat);
return fs.tmp.dir()
.then(function(tmpDir) {
book.setOutput(tmpDir);
return book.generate(options.ebookFormat)
.then(function(_options) {
var copyFile = function(lang) {
var _outputFile = output;
var _tmpDir = tmpDir;
if (lang) {
_outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+"_"+lang+path.extname(_outputFile);
_tmpDir = path.join(_tmpDir, lang);
}
book.log.debug.ln("copy ebook to", _outputFile);
return fs.copy(
path.join(_tmpDir, "index."+options.ebookFormat),
_outputFile
);
};
// Multi-langs book
return Q()
.then(function() {
if (book.isMultilingual()) {
return Q.all(
_.map(book.langs, function(lang) {
return copyFile(lang.lang);
})
)
.thenResolve(book.langs.length);
} else {
return copyFile().thenResolve(1);
}
})
.then(function(n) {
book.log.info.ok(n+" file(s) generated");
return fs.remove(tmpDir);
});
});
});
};
// Parse configuration
Book.prototype.parseConfig = function() {
var that = this;
that.log.info("loading book configuration....")
return that.config.load()
.then(function() {
that.log.info.ok();
});
};
// Parse list of plugins
Book.prototype.parsePlugins = function() {
var that = this;
var failed = [];
// Load plugins
return that.plugins.load(that.options.plugins)
.then(function() {
if (_.size(that.plugins.failed) > 0) return Q.reject(new Error("Error loading plugins: "+that.plugins.failed.join(",")+". Run 'gitbook install' to install plugins from NPM."));
that.log.info.ok(that.plugins.count()+" plugins loaded");
that.log.debug.ln("normalize plugins list");
});
};
// Parse readme to extract defaults title and description
Book.prototype.parseReadme = function() {
var that = this;
var structure = that.config.getStructure("readme");
that.log.debug.ln("start parsing readme:", structure);
return that.findFile(structure)
.then(function(readme) {
if (!readme) throw "No README file";
if (!_.contains(that.files, readme.path)) throw "README file is ignored";
that.readmeFile = readme.path;
that._defaultsStructure(that.readmeFile);
that.log.debug.ln("readme located at", that.readmeFile);
return that.template.renderFile(that.readmeFile)
.then(function(content) {
return readme.parser.readme(content);
});
})
.then(function(readme) {
that.options.title = that.options.title || readme.title;
that.options.description = that.options.description || readme.description;
});
};
// Parse langs to extract list of sub-books
Book.prototype.parseLangs = function() {
var that = this;
var structure = that.config.getStructure("langs");
that.log.debug.ln("start parsing languages index:", structure);
return that.findFile(structure)
.then(function(langs) {
if (!langs) return [];
that.langsFile = langs.path;
that._defaultsStructure(that.langsFile);
that.log.debug.ln("languages index located at", that.langsFile);
return that.template.renderFile(that.langsFile)
.then(function(content) {
return langs.parser.langs(content);
});
})
.then(function(langs) {
that.langs = langs;
});
};
// Parse summary to extract list of chapters
Book.prototype.parseSummary = function() {
var that = this;
var structure = that.config.getStructure("summary");
that.log.debug.ln("start parsing summary:", structure);
return that.findFile(structure)
.then(function(summary) {
if (!summary) throw "No SUMMARY file";
// Remove the summary from the list of files to parse
that.summaryFile = summary.path;
that._defaultsStructure(that.summaryFile);
that.files = _.without(that.files, that.summaryFile);
that.log.debug.ln("summary located at", that.summaryFile);
return that.template.renderFile(that.summaryFile)
.then(function(content) {
return summary.parser.summary(content, {
entryPoint: that.readmeFile,
entryPointTitle: "",
files: that.files
});
});
})
.then(function(summary) {
that.summary = summary;
that.navigation = parseNavigation(that.summary, that.files);
});
};
// Parse glossary to extract terms
Book.prototype.parseGlossary = function() {
var that = this;
var structure = that.config.getStructure("glossary");
that.log.debug.ln("start parsing glossary: ", structure);
return that.findFile(structure)
.then(function(glossary) {
if (!glossary) return [];
// Remove the glossary from the list of files to parse
that.glossaryFile = glossary.path;
that._defaultsStructure(that.glossaryFile);
that.files = _.without(that.files, that.glossaryFile);
that.log.debug.ln("glossary located at", that.glossaryFile);
return that.template.renderFile(that.glossaryFile)
.then(function(content) {
return glossary.parser.glossary(content);
});
})
.then(function(glossary) {
that.glossary = glossary;
});
};
// Parse a page
Book.prototype.parsePage = function(filename, options) {
var that = this, page = {};
options = _.defaults(options || {}, {
// Transform svg images
convertImages: false,
// Interpolate before templating
interpolateTemplate: _.identity,
// Interpolate after templating
interpolateContent: _.identity
});
var interpolate = function(fn) {
return Q(fn(page))
.then(function(_page) {
page = _page;
});
};
that.log.debug.ln("start parsing file", filename);
var extension = path.extname(filename);
var filetype = parsers.get(extension);
if (!filetype) return Q.reject(new Error("Can't parse file: "+filename));
// Type of parser used
page.type = filetype.name;
// Path relative to book
page.path = filename;
// Path absolute in the system
page.rawPath = path.resolve(that.root, filename);
// Progress in the book
page.progress = parseProgress(that.navigation, filename);
that.log.debug.ln("render template", filename);
// Read file content
return that.readFile(page.path)
.then(function(content) {
page.content = content;
return interpolate(options.interpolateTemplate);
})
// Prepare page markup
.then(function() {
return filetype.page.prepare(page.content)
.then(function(content) {
page.content = content;
});
})
// Generate template
.then(function() {
return that.template.renderPage(page);
})
// Prepare and Parse markup
.then(function(content) {
page.content = content;
that.log.debug.ln("use file parser", filetype.name, "for", filename);
return filetype.page(page.content);
})
// Post process sections
.then(function(_page) {
return _.reduce(_page.sections, function(prev, section) {
return prev.then(function(_sections) {
return that.template.postProcess(section.content || "")
.then(function(content) {
section.content = content;
return _sections.concat([section]);
})
});
}, Q([]));
})
// Prepare html
.then(function(_sections) {
return pageUtil.normalize(_sections, {
book: that,
convertImages: options.convertImages,
input: filename,
navigation: that.navigation,
base: path.dirname(filename) || './',
output: path.dirname(filename) || './',
glossary: that.glossary
});
})
// Interpolate output
.then(function(_sections) {
page.sections = _sections;
return interpolate(options.interpolateContent);
})
.then(function() {
that.indexPage(page);
return page;
});
};
// Find file that can be parsed with a specific filename
Book.prototype.findFile = function(filename) {
var that = this;
return _.reduce(parsers.extensions, function(prev, ext) {
return prev.then(function(output) {
// Stop if already find a parser
if (output) return output;
var filepath = filename+ext;
return that.fileExists(filepath)
.then(function(exists) {
if (!exists) return null;
return {
parser: parsers.get(ext),
path: filepath
};
})
});
}, Q(null));
};
// Check if a file exists in the book
Book.prototype.fileExists = function(filename) {
return fs.exists(
path.join(this.root, filename)
);
};
// Read a file
Book.prototype.readFile = function(filename) {
return fs.readFile(
path.join(this.root, filename),
{ encoding: "utf8" }
);
};
// Return stat for a file
Book.prototype.statFile = function(filename) {
return fs.stat(path.join(this.root, filename));
};
// List all files in the book
Book.prototype.listAllFiles = function() {
var that = this;
return fs.list(this.root, {
ignoreFiles: ['.ignore', '.gitignore', '.bookignore'],
ignoreRules: [
// Skip Git stuff
'.git/',
'.gitignore',
// Skip OS X meta data
'.DS_Store',
// Skip stuff installed by plugins
'node_modules',
// Skip book outputs
'_book',
'*.pdf',
'*.epub',
'*.mobi',
// Skip config files
'.ignore',
'.bookignore',
'book.json',
]
})
.then(function(_files) {
that.files = _files;
});
};
// Return true if the book is a multilingual book
Book.prototype.isMultilingual = function(filename) {
return this.books.length > 0;
};
// Return root of the parent
Book.prototype.parentRoot = function() {
if (this.parent) return this.parent.parentRoot();
return this.root;
};
// Return true if it's a sub-book
Book.prototype.isSubBook = function() {
return !!this.parent;
};
// Test if the file is the entry point
Book.prototype.isEntryPoint = function(fp) {
return fp == this.readmeFile;
};
// Resolve a path in book
Book.prototype.resolve = function(p) {
return path.resolve(this.root, p);
};
// Normalize a link to .html and convert README -> index
Book.prototype.contentLink = function(link) {
if (
path.basename(link, path.extname(link)) == "README"
|| link == this.readmeFile
) {
link = path.join(path.dirname(link), "index"+path.extname(link));
}
link = links.changeExtension(link, ".html");
return link;
}
// Index a page into the search index
Book.prototype.indexPage = function(page) {
var nav = this.navigation[page.path];
if (!nav) return;
this.log.debug.ln("index page", page.path);
this.searchIndex.add({
url: this.contentLink(page.path),
title: nav.title,
body: pageUtil.extractText(page.sections),
});
};
// Default structure paths to an extension
Book.prototype._defaultsStructure = function(filename) {
var that = this;
var extension = path.extname(filename);
that.readmeFile = that.readmeFile || that.config.getStructure("readme")+extension;
that.summaryFile = that.summaryFile || that.config.getStructure("summary")+extension;
that.glossaryFile = that.glossaryFile || that.config.getStructure("glossary")+extension;
that.langsFile = that.langsFile || that.config.getStructure("langs")+extension;
}
// Change output path
Book.prototype.setOutput = function(p) {
var that = this;
this.options.output = path.resolve(p);
_.each(this.books, function(book) {
book.setOutput(path.join(that.options.output, book.options.language));
});
};
// Init and return a book
Book.init = function(root) {
var book = new Book(root);
var extensionToUse = ".md";
var chaptersPaths = function(chapters) {
return _.reduce(chapters || [], function(accu, chapter) {
if (!chapter.path) return accu;
return accu.concat(
_.filter([
{
title: chapter.title,
path: chapter.path
}
].concat(chaptersPaths(chapter.articles)))
);
}, []);
};
book.log.info.ln("init book at", root);
return fs.mkdirp(root)
.then(function() {
book.log.info.ln("detect structure from SUMMARY (if it exists)");
return book.parseSummary();
})
.fail(function() {
return Q();
})
.then(function() {
var summary = book.summaryFile || "SUMMARY.md";
var chapters = book.summary.chapters || [];
extensionToUse = path.extname(summary);
if (chapters.length == 0) {
chapters = [
{
title: "Summary",
path: "SUMMARY"+extensionToUse
},
{
title: "Introduction",
path: "README"+extensionToUse
}
];
}
return Q(chaptersPaths(chapters));
})
.then(function(chapters) {
// Create files that don't exist
return Q.all(_.map(chapters, function(chapter) {
var absolutePath = path.resolve(book.root, chapter.path);
return fs.exists(absolutePath)
.then(function(exists) {
book.log.info.ln("create", chapter.path);
if(exists) return;
return fs.mkdirp(path.dirname(absolutePath))
.then(function() {
return fs.writeFile(absolutePath, '# '+chapter.title+'\n');
});
});
}));
})
.then(function() {
book.log.info.ln("initialization is finished");
});
};
module.exports= Book;
+243
View File
@@ -0,0 +1,243 @@
var _ = require("lodash");
var Q = require("q");
var path = require("path");
var semver = require('semver');
var pkg = require('../package.json');
var fs = require("./utils/fs");
var i18n = require("./utils/i18n");
// Default plugins added to each books
var defaultsPlugins = [];
// Normalize a list of plugins to use
function normalizePluginsList(plugins) {
// Normalize list to an array
plugins = _.isString(plugins) ? plugins.split(",") : (plugins || []);
// Remove empty parts
plugins = _.compact(plugins);
// Divide as {name, version} to handle format like "myplugin@1.0.0"
plugins = _.map(plugins, function(plugin) {
if (plugin.name) return 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(defaultsPlugins, 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
function normalizePluginsNames(plugins) {
return _.pluck(normalizePluginsList(plugins), "name");
};
var Configuration = function(book, options) {
var that = this;
this.book = book;
this.options = _.cloneDeep(Configuration.DEFAULT);
this.options = _.merge(this.options, options || {});
// options.input == book.root
Object.defineProperty(this.options, "input", {
get: function () {
return that.book.root;
}
});
};
// Read and parse the configuration
Configuration.prototype.load = function() {
var that = this;
return Q()
.then(function() {
try {
var _config = require(path.resolve(that.book.root, that.options.configFile));
that.options = _.merge(
that.options,
_.omit(_config, 'configFile', 'defaultsPlugins', 'generator', 'extension')
);
}
catch(err) {
if (err instanceof SyntaxError) return Q.reject(err);
return Q();
}
})
.then(function() {
if (!semver.satisfies(pkg.version, that.options.gitbook)) {
throw "GitBook version doesn't satisfy version required by the book: "+that.options.gitbook;
}
if (that.options.gitbook == "*") {
that.book.log.warn.ln("you should specify a gitbook version to use in your book.json, for example: "+(_.first(pkg.version.split("."))+".x.x"));
}
that.options.output = path.resolve(that.options.output || path.join(that.book.root, "_book"));
that.options.plugins = normalizePluginsList(that.options.plugins);
that.options.defaultsPlugins = normalizePluginsList(that.options.defaultsPlugins || "");
that.options.plugins = _.union(that.options.plugins, that.options.defaultsPlugins);
that.options.plugins = _.uniq(that.options.plugins, "name");
// Default value for text direction (from language)
if (!that.options.direction) {
var lang = i18n.getByLanguage(that.options.language);
if (lang) that.options.direction = lang.direction;
}
that.options.gitbook = pkg.version;
});
};
// Extend the configuration
Configuration.prototype.extend = function(options) {
_.extend(this.options, options);
};
// Get structure file
Configuration.prototype.getStructure = function(name) {
return this.options.structure[name].split(".").slice(0, -1).join(".");
};
// Return normalized language
Configuration.prototype.normalizeLanguage = function() {
return i18n.normalizeLanguage(this.options.language);
};
// Default configuration
Configuration.DEFAULT = {
// Options that can't be extend
"configFile": "book",
"generator": "website",
"extension": null,
// Book metadats (somes are extracted from the README by default)
"title": null,
"description": null,
"isbn": null,
"language": "en",
"direction": null,
"author": null,
// version of gitbook to use
"gitbook": "*",
// Structure
"structure": {
"langs": "LANGS.md",
"readme": "README.md",
"glossary": "GLOSSARY.md",
"summary": "SUMMARY.md"
},
// CSS Styles
"styles": {
"website": "styles/website.css",
"ebook": "styles/ebook.css",
"pdf": "styles/pdf.css",
"mobi": "styles/mobi.css",
"epub": "styles/epub.css"
},
// 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
}
},
// 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 [ua0, ua1, ua2, ua3, ua4, ua5, ua6, ub0, ub1, ub2, ub3, ub4, ub5, ub6, ulegal, uletter]
"paperSize": "a4",
// Margin (in pts)
// Note: 72 pts equals 1 inch
"margin": {
"right": 62,
"left": 62,
"top": 56,
"bottom": 56
},
//Header HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"headerTemplate": "",
//Footer HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"footerTemplate": ""
}
};
module.exports= Configuration;
-105
View File
@@ -1,105 +0,0 @@
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 title and description (defaults are extracted from the README)
"title": null,
"description": 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
}
},
// 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 [ua0, ua1, ua2, ua3, ua4, ua5, ua6, ub0, ub1, ub2, ub3, ub4, ub5, ub6, ulegal, uletter]
"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": ""
}
};
module.exports = {
CONFIG: CONFIG,
defaults: function(options) {
return _.merge(options || {}, CONFIG, _.defaults);
}
}
-77
View File
@@ -1,77 +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),
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;
},
};
-87
View File
@@ -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;
-378
View File
@@ -1,378 +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) {
// Set defaults to options
options = defaultConfig.defaults(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 config file
.then(function() {
try {
var _config = require(path.resolve(options.input, options.configFile));
options = _.merge(options, _.omit(_config, 'input', 'configFile', 'defaultsPlugins'));
}
catch(err) {
// No config file: not a big deal
return Q();
}
})
// 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);
});
};
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.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,
};
-69
View File
@@ -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;
-76
View File
@@ -1,76 +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(sections) {
json.sections = 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;
-73
View File
@@ -1,73 +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);
// 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')
);
};
// 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();
})
// 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;
-284
View File
@@ -1,284 +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 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));
};
// Normalize a list of plugin name to use
Plugin.normalizeNames = function(names) {
// Normalize list to an array
names = _.isString(names) ? names.split(",") : (names || []);
// List plugins to remove
var toremove = _.chain(names)
.filter(function(name) {
return name.length > 0 && name[0] == "-";
})
.map(function(name) {
return name.slice(1);
})
.value();
// Merge with defaults
names = _.chain(names)
.concat(Plugin.defaults)
.uniq()
.value();
// Remove plugins starting with
names = _.filter(names, function(name) {
return !_.contains(toremove, name) && !(name.length > 0 && name[0] == "-");
});
return names;
};
// 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(",")));
// 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
Plugin.defaults = [
"mathjax"
];
module.exports = Plugin;
-101
View File
@@ -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;
-284
View File
@@ -1,284 +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.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.loadTemplates();
});
};
// Load all templates
Generator.prototype.loadTemplates = function() {
this.template = swig.compileFile(
this.plugins.template("site:page") || path.resolve(this.options.theme, 'templates/book/page.html')
);
this.langsTemplate = swig.compileFile(
this.plugins.template("site:langs") || path.resolve(this.options.theme, 'templates/book/langs.html')
);
this.glossaryTemplate = swig.compileFile(
this.plugins.template("site:glossary") || path.resolve(this.options.theme, 'templates/book/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({
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) || '/',
// Project's include folder
includes_dir: path.join(that.options.input, '_includes'),
// Output directory
outdir: path.dirname(_input) || '/',
// Templating variables
variables: that.options.variables,
});
})
.then(function(sections) {
page.sections = 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) || ".";
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;
-71
View File
@@ -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;
-52
View File
@@ -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;
+78
View File
@@ -0,0 +1,78 @@
var _ = require("lodash");
var path = require("path");
var Q = require("q");
var fs = require("./utils/fs");
var Plugin = require("./plugin");
var BaseGenerator = function(book) {
this.book = book;
Object.defineProperty(this, "options", {
get: function () {
return this.book.options;
}
});
_.bindAll(this);
};
BaseGenerator.prototype.callHook = function(name, data) {
return this.book.plugins.hook(name, data);
};
// Prepare the genertor
BaseGenerator.prototype.prepare = function() {
var that = this;
return that.callHook("init");
};
// Write a parsed file to the output
BaseGenerator.prototype.convertFile = function(input) {
return Q.reject(new Error("Could not convert "+input));
};
// Copy file to the output (non parsable)
BaseGenerator.prototype.transferFile = function(input) {
return fs.copy(
path.join(this.book.root, input),
path.join(this.options.output, input)
);
};
// Copy a folder to the output
BaseGenerator.prototype.transferFolder = function(input) {
return fs.mkdirp(
path.join(this.book.options.output, input)
);
};
// Copy the cover picture
BaseGenerator.prototype.copyCover = function() {
var that = this;
return Q.all([
fs.copy(path.join(that.book.root, "cover.jpg"), path.join(that.options.output, "cover.jpg")),
fs.copy(path.join(that.book.root, "cover_small.jpg"), path.join(that.options.output, "cover_small.jpg"))
])
.fail(function() {
// If orignaly from multi-lang, try copy from parent
if (!that.book.isSubBook()) return;
return Q.all([
fs.copy(path.join(that.book.parentRoot(), "cover.jpg"), path.join(that.options.output, "cover.jpg")),
fs.copy(path.join(that.book.parentRoot(), "cover_small.jpg"), path.join(that.options.output, "cover_small.jpg"))
]);
})
.fail(function(err) {
return Q();
});
};
// At teh end of the generation
BaseGenerator.prototype.finish = function() {
return Q.reject(new Error("Could not finish generation"));
};
module.exports = BaseGenerator;
@@ -4,23 +4,55 @@ 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 fs = require("../utils/fs");
var stringUtils = require("../utils/string");
var BaseGenerator = require("./website");
var Generator = function() {
var Generator = function(book, format) {
BaseGenerator.apply(this, arguments);
// eBook format
this.ebookFormat = format;
// Resources namespace
this.namespace = "ebook";
// Styles to use
this.styles = _.compact(["ebook", this.ebookFormat]);
// Convert images (svg -> png)
this.convertImages = true;
};
util.inherits(Generator, BaseGenerator);
Generator.prototype.prepareTemplates = function() {
this.templates["page"] = this.book.plugins.template("ebook:page") || path.resolve(this.options.theme, 'templates/ebook/page.html');
this.templates["summary"] = this.book.plugins.template("ebook:summary") || path.resolve(this.options.theme, 'templates/ebook/summary.html');
this.templates["glossary"] = this.book.plugins.template("ebook:glossary") || path.resolve(this.options.theme, 'templates/ebook/glossary.html');
return Q();
};
// Generate table of contents
Generator.prototype.writeSummary = function() {
var that = this;
that.book.log.info.ln("write SUMMARY.html");
return this._writeTemplate(this.templates["summary"], {}, path.join(this.options.output, "SUMMARY.html"));
};
Generator.prototype.finish = function() {
var that = this;
return BaseGenerator.prototype.finish.apply(this)
return Q()
.then(this.copyAssets)
.then(this.copyCover)
.then(this.writeGlossary)
.then(this.writeSummary)
.then(function() {
if (!that.ebookFormat) return Q();
var d = Q.defer();
var format = that.options.extension || path.extname(that.options.output).replace("\.", "") || "pdf";
if (!that.options.cover && fs.existsSync(path.join(that.options.output, "cover.jpg"))) {
that.options.cover = path.join(that.options.output, "cover.jpg");
@@ -30,7 +62,10 @@ Generator.prototype.finish = function() {
"--cover": that.options.cover,
"--title": that.options.title,
"--comments": that.options.description,
"--isbn": that.options.isbn,
"--authors": that.options.author,
"--language": that.options.language,
"--book-producer": "GitBook",
"--publisher": "GitBook",
"--chapter": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter ')]",
"--chapter-mark": "pagebreak",
@@ -43,7 +78,7 @@ Generator.prototype.finish = function() {
"--breadth-first": true
};
if (format == "pdf") {
if (that.ebookFormat == "pdf") {
var pdfOptions = that.options.pdf;
_.extend(_options, {
@@ -55,20 +90,28 @@ Generator.prototype.finish = function() {
"--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)
"--pdf-header-template": String(pdfOptions.headerTemplate) || "<p class='header'><span>"+that.options.title+"</span></p>",
"--pdf-footer-template": String(pdfOptions.footerTemplate) || "<p class='footer'><span>_SECTION_</span> <span style='float:right;'>_PAGENUM_</span></p>"
});
} else if (that.ebookFormat == "epub") {
_.extend(_options, {
"--dont-split-on-page-breaks": true
});
}
var command = [
"ebook-convert",
path.join(that.options.output, "SUMMARY.html"),
path.join(that.options.output, "index."+format),
path.join(that.options.output, "index."+that.ebookFormat),
stringUtils.optionsToShellArgs(_options)
].join(" ");
exec(command, function (error, stdout, stderr) {
that.book.log.info("start conversion to", that.ebookFormat, "....");
var child = exec(command, function (error, stdout, stderr) {
if (error) {
that.book.log.info.fail();
if (error.code == 127) {
error.message = "Need to install ebook-convert from Calibre";
} else {
@@ -76,9 +119,19 @@ Generator.prototype.finish = function() {
}
return d.reject(error);
}
that.book.log.info.ok();
d.resolve();
});
child.stdout.on('data', function (data) {
that.book.log.debug(data);
});
child.stderr.on('data', function (data) {
that.book.log.debug(data);
});
return d.promise;
});
};
+11
View File
@@ -0,0 +1,11 @@
var _ = require("lodash");
var EbookGenerator = require("./ebook");
module.exports = {
json: require("./json"),
website: require("./website"),
ebook: EbookGenerator,
pdf: _.partialRight(EbookGenerator, "pdf"),
mobi: _.partialRight(EbookGenerator, "mobi"),
epub: _.partialRight(EbookGenerator, "epub")
};
+76
View File
@@ -0,0 +1,76 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var fs = require("../utils/fs");
var BaseGenerator = require("../generator");
var links = require("../utils/links");
var Generator = function() {
BaseGenerator.apply(this, arguments);
};
util.inherits(Generator, BaseGenerator);
// Ignore some methods
Generator.prototype.transferFile = function(input) { };
// Convert an input file
Generator.prototype.convertFile = function(input) {
var that = this;
return that.book.parsePage(input)
.then(function(page) {
var json = {
progress: page.progress,
sections: page.sections
};
var output = links.changeExtension(page.path, ".json");
output = path.join(that.options.output, output);
return fs.writeFile(
output,
JSON.stringify(json, null, 4)
);
});
};
// Finish generation
Generator.prototype.finish = function() {
return this.writeReadme();
};
// Write README.json
Generator.prototype.writeReadme = function() {
var that = this;
var mainlang, langs;
return Q()
.then(function() {
langs = that.book.langs;
mainLang = langs.length > 0? _.first(langs).lang : null;
readme = links.changeExtension(that.book.readmeFile, ".json");
// Read readme from main language
return fs.readFile(
mainLang? path.join(that.options.output, mainLang, readme) : path.join(that.options.output, readme)
);
})
.then(function(content) {
// Extend it with infos about the languages
var json = JSON.parse(content);
_.extend(json, {
langs: langs
});
// Write it as README.json
return fs.writeFile(
path.join(that.options.output, "README.json"),
JSON.stringify(json, null, 4)
);
});
};
module.exports = Generator;
+279
View File
@@ -0,0 +1,279 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var nunjucks = require("nunjucks");
var AutoEscapeExtension = require("nunjucks-autoescape");
var FilterExtension = require("nunjucks-filter");
var I18nExtension = require("nunjucks-i18n");
var fs = require("../utils/fs");
var BaseGenerator = require("../generator");
var links = require("../utils/links");
var pageUtil = require("../utils/page");
var i18n = require("../utils/i18n");
var pkg = require("../../package.json");
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Revision
this.revision = new Date();
// Resources namespace
this.namespace = "website";
// Style to integrates in the output
this.styles = ["website"];
// Convert images (svg -> png)
this.convertImages = false;
// Templates
this.templates = {};
};
util.inherits(Generator, BaseGenerator);
// Prepare the genertor
Generator.prototype.prepare = function() {
return BaseGenerator.prototype.prepare.apply(this)
.then(this.prepareStyles)
.then(this.prepareTemplates)
.then(this.prepareTemplateEngine);
};
// Prepare all styles
Generator.prototype.prepareStyles = function() {
var that = this;
this.styles = _.chain(this.styles)
.map(function(style) {
var stylePath = that.options.styles[style];
if (fs.existsSync(path.resolve(that.book.root, stylePath))) {
return stylePath;
}
return null;
})
.compact()
.value();
return Q();
};
// Prepare templates
Generator.prototype.prepareTemplates = function() {
this.templates["page"] = this.book.plugins.template("site:page") || path.resolve(this.options.theme, 'templates/website/page.html');
this.templates["langs"] = this.book.plugins.template("site:langs") || path.resolve(this.options.theme, 'templates/website/langs.html');
this.templates["glossary"] = this.book.plugins.template("site:glossary") || path.resolve(this.options.theme, 'templates/website/glossary.html');
return Q();
};
// Prepare template engine
Generator.prototype.prepareTemplateEngine = function() {
var that = this;
return Q()
.then(function() {
var language = that.book.config.normalizeLanguage();
if (!i18n.getByLanguage(language) && language != "en") {
that.book.log.warn.ln("Language '"+language+"' is not available as a layout locales (en, "+i18n.getLanguages().join(", ")+")");
}
var folders = _.chain(that.templates)
.values()
.map(path.dirname)
.uniq()
.value();
that.env = new nunjucks.Environment(
new nunjucks.FileSystemLoader(folders),
{
autoescape: true
}
);
// Add filter
that.env.addFilter("contentLink", that.book.contentLink.bind(that.book));
that.env.addFilter('lvl', function(lvl) {
return lvl.split(".").length;
});
// Add extension
that.env.addExtension('AutoEscapeExtension', new AutoEscapeExtension(that.env));
that.env.addExtension('FilterExtension', new FilterExtension(that.env));
that.env.addExtension('I18nExtension', new I18nExtension({
env: that.env,
translations: i18n.getLocales(),
locale: "language"
}));
});
};
// Finis generation
Generator.prototype.finish = function() {
return this.copyAssets()
.then(this.copyCover)
.then(this.writeGlossary)
.then(this.writeSearchIndex)
.then(this.writeLangsIndex)
};
// Convert an input file
Generator.prototype.convertFile = function(input) {
var that = this;
return that.book.parsePage(input, {
convertImages: that.convertImages,
interpolateTemplate: function(page) {
return that.callHook("page:before", page);
},
interpolateContent: function(page) {
return that.callHook("page", page);
}
})
.then(function(page) {
var relativeOutput = that.book.contentLink(page.path);
var output = path.join(that.options.output, relativeOutput);
var basePath = path.relative(path.dirname(output), that.options.output) || ".";
if (process.platform === 'win32') basePath = basePath.replace(/\\/g, '/');
that.book.log.debug.ln("write parsed file", page.path, "to", relativeOutput);
return that._writeTemplate(that.templates["page"], {
progress: page.progress,
_input: page.path,
content: page.sections,
basePath: basePath,
staticBase: links.join(basePath, "gitbook")
}, output);
});
};
// Write the index for langs
Generator.prototype.writeLangsIndex = function() {
var that = this;
if (!this.book.langs.length) return Q();
return this._writeTemplate(this.templates["langs"], {
langs: this.book.langs
}, path.join(this.options.output, "index.html"));
};
// Write glossary
Generator.prototype.writeGlossary = function() {
var that = this;
// No glossary
if (this.book.glossary.length == 0) return Q();
return this._writeTemplate(this.templates["glossary"], {}, path.join(this.options.output, "GLOSSARY.html"));
};
// Write the search index
Generator.prototype.writeSearchIndex = function() {
var that = this;
return fs.writeFile(
path.join(this.options.output, "search_index.json"),
JSON.stringify(this.book.searchIndex)
);
};
// Convert a page into a normalized data set
Generator.prototype.normalizePage = function(page) {
var that = this;
var _callHook = function(name) {
return that.callHook(name, page)
.then(function(_page) {
page = _page;
return page;
});
};
return Q()
.then(function() {
return _callHook("page");
})
.then(function() {
return page;
});
};
// Generate a template
Generator.prototype._writeTemplate = function(tpl, options, output, interpolate) {
var that = this;
interpolate = interpolate || _.identity;
return Q()
.then(function(sections) {
return that.env.render(
tpl,
_.extend({
gitbook: {
version: pkg.version
},
styles: that.styles,
revision: that.revision,
title: that.options.title,
description: that.options.description,
language: that.book.config.normalizeLanguage(),
glossary: that.book.glossary,
summary: that.book.summary,
allNavigation: that.book.navigation,
plugins: {
resources: that.book.plugins.resources(that.namespace)
},
pluginsConfig: JSON.stringify(that.options.pluginsConfig),
htmlSnippet: _.partial(_.partialRight(that.book.plugins.html, that, options), that.namespace),
options: that.options,
basePath: ".",
staticBase: path.join(".", "gitbook"),
}, options)
);
})
.then(interpolate)
.then(function(html) {
return fs.writeFile(
output,
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.book.plugins.list, function(plugin) {
var pluginAssets = path.join(that.options.output, "gitbook/plugins/", plugin.name);
return plugin.copyAssets(pluginAssets, that.namespace);
})
);
});
};
module.exports = Generator;
+208 -3
View File
@@ -1,5 +1,210 @@
module.exports = {
parse: require('./parse/'),
generate: require('./generate/')
var Q = require("q");
var _ = require("lodash");
var path = require("path");
var tinylr = require('tiny-lr');
var color = require('bash-color');
var Book = require("./book");
var Plugin = require("./plugin");
var Server = require("./utils/server");
var stringUtils = require("./utils/string");
var watch = require("./utils/watch");
var logger = require("./utils/logger");
var LOG_OPTION = {
name: "log",
description: "Minimum log level to display",
values: _.chain(logger.LEVELS).keys().map(stringUtils.toLowerCase).value(),
defaults: "info"
};
var FORMAT_OPTION = {
name: "format",
description: "Format to build to",
values: ["website", "json", "ebook"],
defaults: "website"
};
module.exports = {
Book: Book,
LOG_LEVELS: logger.LEVELS,
commands: _.flatten([
{
name: "build [book] [output]",
description: "build a book",
options: [
FORMAT_OPTION,
LOG_OPTION
],
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var output = args[1] || path.join(input, "_book");
var book = new Book(input, _.extend({}, {
'config': {
'output': output
},
'logLevel': kwargs.log
}));
return book.parse()
.then(function() {
return book.generate(kwargs.format);
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
},
_.map(["pdf", "epub", "mobi"], function(ebookType) {
return {
name: ebookType+" [book] [output]",
description: "build a book to "+ebookType,
options: [
LOG_OPTION
],
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var output = args[1];
var book = new Book(input, _.extend({}, {
'logLevel': kwargs.log
}));
return book.parse()
.then(function() {
return book.generateFile(output, {
ebookFormat: ebookType
});
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
};
}),
{
name: "serve [book]",
description: "Build then serve a gitbook from a directory",
options: [
{
name: "port",
description: "Port for server to listen on",
defaults: 4000
},
{
name: "lrport",
description: "Port for livereload server to listen on",
defaults: 35729
},
{
name: "watch",
description: "Enable/disable file watcher",
defaults: true
},
FORMAT_OPTION,
LOG_OPTION
],
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var server = new Server();
// Init livereload server
var lrServer = tinylr({});
var lrPath = undefined;
var generate = function() {
if (server.isRunning()) console.log("Stopping server");
return server.stop()
.then(function() {
var book = new Book(input, _.extend({}, {
'config': {
'defaultsPlugins': ["livereload"]
},
'logLevel': kwargs.log
}));
return book.parse()
.then(function() {
return book.generate(kwargs.format);
})
.thenResolve(book);
})
.then(function(book) {
console.log();
console.log('Starting server ...');
return server.start(book.options.output, kwargs.port)
.then(function() {
console.log('Serving book on http://localhost:'+kwargs.port);
if (lrPath) {
// trigger livereload
lrServer.changed({
body: {
files: [lrPath]
}
});
}
if (!kwargs.watch) return;
return watch(book.root)
.then(function(filepath) {
// set livereload path
lrPath = filepath;
console.log("Restart after change in files");
console.log('');
return generate();
})
})
});
};
return Q.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport)
.then(function() {
console.log('Live reload server started on port:', kwargs.lrport);
console.log('Press CTRL+C to quit ...');
console.log('')
return generate();
});
}
},
{
name: "install [book]",
description: "install plugins dependencies",
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var book = new Book(input);
return book.config.load()
.then(function() {
return book.plugins.install();
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
},
{
name: "init [directory]",
description: "create files and folders based on contents of SUMMARY.md",
exec: function(args, kwargs) {
return Book.init(args[0] || process.cwd())
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
}
])
};
-48
View File
@@ -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;
-48
View File
@@ -1,48 +0,0 @@
var _ = require('lodash');
var fs = require('graceful-fs');
var path = require('path');
// Include helper
function importInclude(name, paths) {
return paths
.map(function(folder) {
// Try including snippet from FS
try {
var fname = path.join(folder, name);
// Trim trailing newlines/space of imported snippets
return fs.readFileSync(fname, 'utf8').trimRight();
} catch(err) {}
})
.filter(Boolean)[0];
}
function includer(ctx, folders) {
return function(key) {
key = key.trim();
return ctx[key] || importInclude(key, folders);
};
}
module.exports = function(markdown, folder, ctx) {
// List of folders to search for includes
var folders = [];
// Handle folder arg (string or array)
if(_.isString(folder)) {
folders = [folder];
} else if(_.isArray(folder)) {
folders = folder;
}
// variable context
ctx = ctx || {};
// Memoized include function (to cache lookups)
var _include = _.memoize(includer(ctx, folders));
return markdown.replace(/{{([\s\S]+?)}}/g, function(match, key) {
// If fails leave content as is
return _include(key) || match;
});
};
-10
View File
@@ -1,10 +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')
};
-17
View File
@@ -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;
-87
View File
@@ -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;
-25
View File
@@ -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;
-79
View File
@@ -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;
-156
View File
@@ -1,156 +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
return (_.isArray(src) ? src : lex(include(src, [options.dir, options.includes_dir], options.variables)))
.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)
};
});
}
// Exports
module.exports = parsePage;
-45
View File
@@ -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;
-143
View File
@@ -1,143 +0,0 @@
var url = require('url');
var inherits = require('util').inherits;
var links = require('../utils').links;
var path = require('path');
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;
// Relative link, rewrite it to point to github repo
if(links.isRelative(_href) && path.extname(parsed.path) == ".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;
-148
View File
@@ -1,148 +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);
}
// 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(nodes),
'list_item_start', 'list_item_end'
);
}
function parseSummary(src) {
// Split out chapter sections
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;
+183
View File
@@ -0,0 +1,183 @@
var _ = require("lodash");
var Q = require("q");
var semver = require("semver");
var path = require("path");
var url = require("url");
var fs = require("./utils/fs");
var resolve = require('resolve');
var pkg = require("../package.json");
var Plugin = function(book, name) {
this.book = book;
this.name = name;
this.packageInfos = {};
this.infos = {};
// Bind methods
_.bindAll(this);
_.each([
"gitbook-plugin-"+name,
"gitbook-"+name,
name,
], function(_name) {
// Load from the book
if (this.load(_name, book.root)) return false;
// Load from default plugins
if (this.load(_name, __dirname)) return false;
}, this);
};
// Type of plugins resources
Plugin.RESOURCES = ["js", "css"];
Plugin.HOOKS = [
"init", "finish", "finish:before"
]
// 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) {
this.packageInfos = {};
this.infos = {};
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;
var book = this.infos[base];
// Compatibility with version 1.x.x
if (base == "website") book = book || this.infos["book"];
// Nothing specified, fallback to default
if (!book) {
return Q({});
}
// Dynamic function
if(typeof book === "function") {
// Call giving it the context of our book
return Q().then(book.bind(this.book));
}
// 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(Plugin.RESOURCES, function(resourceType) {
resources[resourceType] = (resources[resourceType] || []).map(that.normalizeResource);
});
return resources;
});
};
// Normalize filters and return them
Plugin.prototype.getFilters = function() {
return this.infos.filters || {};
};
// Normalize blocks and return them
Plugin.prototype.getBlocks = function() {
return this.infos.blocks || {};
};
// Test if it's a valid plugin
Plugin.prototype.isValid = function() {
var that = this;
var isValid = (
this.packageInfos &&
this.packageInfos.name &&
this.packageInfos.engines &&
this.packageInfos.engines.gitbook &&
semver.satisfies(pkg.version, this.packageInfos.engines.gitbook)
);
// Valid hooks
_.each(this.infos.hooks, function(hook, hookName) {
if (_.contains(Plugin.HOOKS, hookName)) return;
that.book.log.warn.ln("Hook '"+hookName+" 'used by plugin '"+that.packageInfos.name+"' has been removed or is deprecated");
});
return isValid;
};
// Resolve file path
Plugin.prototype.resolveFile = function(filename) {
return path.resolve(this.baseDir, filename);
};
// Resolve file path
Plugin.prototype.callHook = function(name, data) {
// Our book will be the context to apply
var context = this.book;
var hookFunc = this.infos.hooks? this.infos.hooks[name] : null;
data = data || {};
if (!hookFunc) return Q(data);
this.book.log.debug.ln("call hook", name);
if (!_.contains(Plugin.HOOKS, name)) this.book.log.warn.ln("hook '"+name+"' used by plugin '"+this.name+"' is deprecated, and will be remove in the coming versions");
return Q()
.then(function() {
return hookFunc.apply(context, [data]);
});
};
// Copy plugin assets fodler
Plugin.prototype.copyAssets = function(out, base) {
var that = this;
return this.getResources(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));
};
module.exports = Plugin;
+225
View File
@@ -0,0 +1,225 @@
var _ = require("lodash");
var Q = require("q");
var npmi = require('npmi');
var npm = require('npm');
var semver = require('semver');
var Plugin = require("./plugin");
var pkg = require("../package.json");
var initNPM = _.memoize(function() {
return Q.nfcall(npm.load, { silent: true, loglevel: 'silent' });
});
var PluginsList = function(book, plugins) {
this.book = book;
this.log = this.book.log;
// List of Plugin objects
this.list = [];
// List of names of failed plugins
this.failed = [];
// Namespaces
this.namespaces = _.chain(["website", "ebook"])
.map(function(namespace) {
return [
namespace,
{
html: {},
resources: _.chain(Plugin.RESOURCES)
.map(function(type) {
return [type, []];
})
.object()
.value()
}
];
})
.object()
.value();
// Bind methods
_.bindAll(this);
if (plugins) this.load(plugins);
};
// return count of plugins
PluginsList.prototype.count = function() {
return this.list.length;
};
// Add and load a plugin
PluginsList.prototype.load = function(plugin, options) {
var that = this;
if (_.isArray(plugin)) {
return _.reduce(plugin, function(prev, p) {
return prev.then(function() {
return that.load(p);
});
}, Q());
}
if (_.isObject(plugin) && !(plugin instanceof Plugin)) plugin = plugin.name;
if (_.isString(plugin)) plugin = new Plugin(this.book, plugin);
that.log.info("load plugin", plugin.name, "....");
if (!plugin.isValid()) {
that.log.info.fail();
that.failed.push(plugin.name);
return Q();
} else {
that.log.info.ok();
// Push in the list
that.list.push(plugin);
}
// Extract filters
_.each(plugin.getFilters(), function(filterFunc, filterName) {
that.book.template.addFilter(filterName, filterFunc);
});
// Extract blocks
_.each(plugin.getBlocks(), function(block, blockName) {
that.book.template.addBlock(blockName, block);
});
return _.reduce(_.keys(that.namespaces), function(prev, namespaceName) {
return prev.then(function() {
return plugin.getResources(namespaceName)
.then(function(plResources) {
var namespace = that.namespaces[namespaceName];
// Extract js and css
_.each(Plugin.RESOURCES, function(resourceType) {
namespace.resources[resourceType] = (namespace.resources[resourceType] || []).concat(plResources[resourceType] || []);
});
// Map of html resources by name added by each plugin
_.each(plResources.html || {}, function(value, tag) {
// Turn into function if not one already
if (!_.isFunction(value)) value = _.constant(value);
namespace.html[tag] = namespace.html[tag] || [];
namespace.html[tag].push(value);
});
})
});
}, Q());
};
// Call a hook
PluginsList.prototype.hook = function(name, data) {
return _.reduce(this.list, function(prev, plugin) {
return prev.then(function(ret) {
return plugin.callHook(name, ret);
});
}, Q(data));
};
// Return a template from a plugin
PluginsList.prototype.template = function(name) {
var withTpl = _.find(this.list, function(plugin) {
return (
plugin.infos.templates &&
plugin.infos.templates[name]
);
});
if (!withTpl) return null;
return withTpl.resolveFile(withTpl.infos.templates[name]);
};
// Return an html snippet
PluginsList.prototype.html = function(namespace, tag, context, options) {
var htmlSnippets = this.namespaces[namespace].html[tag];
return _.map(htmlSnippets || [], function(code) {
return code.call(context, options);
}).join("\n");
};
// Return a resources map for a namespace
PluginsList.prototype.resources = function(namespace) {
return this.namespaces[namespace].resources;
};
// Install plugins from a book
PluginsList.prototype.install = function() {
var that = this;
var defaultsPlugins = _.pluck(that.book.options.defaultsPlugins)
// Remove defaults (no need to install)
var plugins = _.filter(that.book.options.plugins, function(plugin) {
return !_.contains(defaultsPlugins, plugin.name);
});
// Install plugins one by one
that.book.log.info.ln(plugins.length+" plugins to install");
return _.reduce(plugins, function(prev, plugin) {
return prev.then(function() {
var fullname = "gitbook-plugin-"+plugin.name;
return Q()
// Resolve version if needed
.then(function() {
if (plugin.version) return plugin.version;
that.book.log.info.ln("No version specified, resolve plugin", plugin.name);
return initNPM()
.then(function() {
return Q.nfcall(npm.commands.view, [fullname+"@*", "engines"], true);
})
.then(function(versions) {
return _.chain(versions)
.pairs()
.map(function(v) {
return {
version: v[0],
gitbook: (v[1].engines || {})["gitbook"]
}
})
.filter(function(v) {
return v.gitbook && semver.satisfies(pkg.version, v.gitbook);
})
.sort(function(v1, v2) {
return semver.lt(v1.version, v2.version)? 1 : -1;
})
.pluck("version")
.first()
.value();
});
})
// Install the plugin with the resolved version
.then(function(version) {
if (!version) {
throw "Found no satisfactory version for plugin "+plugin.name;
}
that.book.log.info.ln("install plugin", plugin.name, "from npm ("+fullname+") with version", version);
return Q.nfcall(npmi, {
'name': fullname,
'version': version,
'path': that.book.root,
'npmLoad': {
'loglevel': 'silent',
'loaded': true,
'prefix': that.book.root
}
});
})
.then(function() {
that.book.log.info.ok("plugin", plugin.name, "installed with success");
});
});
}, Q());
};
module.exports = PluginsList;
+394
View File
@@ -0,0 +1,394 @@
var _ = require("lodash");
var Q = require("q");
var path = require("path");
var nunjucks = require("nunjucks");
var git = require("./utils/git");
var stringUtils = require("./utils/string");
var fs = require("./utils/fs");
var batch = require("./utils/batch");
var pkg = require("../package.json");
// The loader should handle relative and git url
var BookLoader = nunjucks.Loader.extend({
async: true,
init: function(book) {
this.book = book;
},
getSource: function(fileurl, callback) {
var that = this;
git.resolveFile(fileurl)
.then(function(filepath) {
// Is local file
if (!filepath) filepath = path.resolve(that.book.root, fileurl);
else that.book.log.debug.ln("resolve from git", fileurl, "to", filepath)
// Read file from absolute path
return fs.readFile(filepath)
.then(function(source) {
return {
src: source.toString(),
path: filepath
}
});
})
.nodeify(callback);
},
resolve: function(from, to) {
return path.resolve(path.dirname(from), to);
}
});
var TemplateEngine = function(book) {
this.book = book;
this.log = this.book.log;
// Nunjucks env
this.env = new nunjucks.Environment(
new BookLoader(book),
{
// Escaping is done after by the markdown parser
autoescape: false,
// Tags
tags: {
blockStart: '{%',
blockEnd: '%}',
variableStart: '{{',
variableEnd: '}}',
commentStart: '{###',
commentEnd: '###}'
}
}
);
// List of tags shortcuts
this.shortcuts = [];
// Map of blocks
this.blocks = {};
// Bind methods
_.bindAll(this);
// Default block "html" that return html not parsed
this.addBlock("html", {
process: _.identity
});
};
// Process a block in a context
TemplateEngine.prototype.processBlock = function(blk) {
if (_.isString(blk)) blk = { body: blk };
blk = _.defaults(blk, {
parse: false,
post: undefined
});
blk.id = _.uniqueId("blk");
var toAdd = (!blk.parse) || (blk.post != undefined);
// Add to global map
if (toAdd) this.blocks[blk.id] = blk;
//Parsable block, just return it
if (blk.parse) {
return blk.body;
}
// Return it as a macro
return "%+%"+blk.id+"%+%";
};
// Replace blocks by body after processing
// This is done to avoid that markdown processer parse the block content
TemplateEngine.prototype.replaceBlocks = function(content) {
var that = this;
return content.replace(/\%\+\%([\s\S]+?)\%\+\%/g, function(match, key) {
var blk = that.blocks[key];
if (!blk) return match;
var body = blk.body;
return body;
});
};
// Bind a function to a context
TemplateEngine.prototype.bindContext = function(func) {
var that = this;
return function() {
var ctx = {
ctx: this.ctx,
book: that.book,
generator: that.book.options.generator
};
return func.apply(ctx, arguments);
};
};
// Add filter
TemplateEngine.prototype.addFilter = function(filterName, func) {
try {
this.env.getFilter(filterName);
this.log.warn.ln("conflict in filters, '"+filterName+"' is already set");
return false;
} catch(e) {}
this.log.debug.ln("add filter '"+filterName+"'");
this.env.addFilter(filterName, this.bindContext(function() {
var ctx = this;
var args = Array.prototype.slice.apply(arguments);
var callback = _.last(args);
Q()
.then(function() {
return func.apply(ctx, args.slice(0, -1));
})
.nodeify(callback);
}), true);
return true;
};
// Add a block
TemplateEngine.prototype.addBlock = function(name, block) {
var that = this;
block = _.defaults(block || {}, {
shortcuts: [],
end: "end"+name,
process: _.identity,
blocks: []
});
var extName = 'Block'+name+'Extension';
if (this.env.getExtension(extName)) {
this.log.warn.ln("conflict in blocks, '"+name+"' is already defined");
return false;
}
this.log.debug.ln("add block '"+name+"'");
var Ext = function () {
this.tags = [name];
this.parse = function(parser, nodes, lexer) {
var body = null;
var lastBlockName = null;
var lastBlockArgs = null;
var allBlocks = block.blocks.concat([block.end]);
var subbodies = {};
var tok = parser.nextToken();
var args = parser.parseSignature(null, true);
parser.advanceAfterBlockEnd(tok.value);
while (1) {
// Read body
var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks);
// Handle body with previous block name and args
if (lastBlockName) {
subbodies[lastBlockName] = subbodies[lastBlockName] || [];
subbodies[lastBlockName].push({
body: currentBody,
args: lastBlockArgs
});
} else {
body = currentBody;
}
// Read new block
lastBlockName = parser.peekToken().value;
if (lastBlockName == block.end) {
break;
}
// Parse signature and move to the end of the block
lastBlockArgs = parser.parseSignature(null, true);
parser.advanceAfterBlockEnd(lastBlockName);
}
parser.advanceAfterBlockEnd();
var bodies = [body];
_.each(block.blocks, function(blockName) {
subbodies[blockName] = subbodies[blockName] || [];
if (subbodies[blockName].length == 0) {
subbodies[blockName].push({
args: new nodes.NodeList(),
body: new nodes.NodeList()
});
}
bodies.push(subbodies[blockName][0].body);
});
return new nodes.CallExtensionAsync(this, 'run', args, bodies);
};
this.run = function(context) {
var args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
// Extract blocks
var blocks = args
.concat([])
.slice(-block.blocks.length);
// Eliminate blocks from list
if (block.blocks.length > 0) args = args.slice(0, -block.blocks.length);
// Extract main body and kwargs
var body = args.pop();
var kwargs = args.pop() || {};
// Extract blocks body
var _blocks = _.map(block.blocks, function(blockName, i){
return {
name: blockName,
body: blocks[i]()
};
});
var func = that.bindContext(block.process);
Q()
.then(function() {
return func.call(context, {
body: body(),
args: args,
kwargs: kwargs,
blocks: _blocks
});
})
// process the block returned
.then(that.processBlock)
.nodeify(callback)
};
};
// Add the Extension
this.env.addExtension(extName, new Ext());
// Add shortcuts
if (!_.isArray(block.shortcuts)) block.shortcuts = [block.shortcuts];
_.each(block.shortcuts, function(shortcut) {
this.log.debug.ln("add template shortcut from '"+shortcut.start+"' to block '"+name+"' for parsers ", shortcut.parsers);
this.shortcuts.push({
parsers: shortcut.parsers,
start: shortcut.start,
end: shortcut.end,
tag: {
start: name,
end: block.end
}
});
}, this);
};
// Apply a shortcut to a string
TemplateEngine.prototype._applyShortcut = function(parser, content, shortcut) {
if (!_.contains(shortcut.parsers, parser)) return content;
var regex = new RegExp(
stringUtils.escapeRegex(shortcut.start) + "([\\s\\S]*?[^\\$])" + stringUtils.escapeRegex(shortcut.end),
'g'
);
return content.replace(regex, function(all, match) {
return "{% "+shortcut.tag.start+" %}"+ match + "{% "+shortcut.tag.end+" %}";
});
};
// Render a string from the book
TemplateEngine.prototype.renderString = function(content, context, options) {
var context = _.extend({}, context, {
// Variables from book.json
book: this.book.options.variables,
// infos about gitbook
gitbook: {
version: pkg.version,
generator: this.book.options.generator
}
});
options = _.defaults(options || {}, {
path: null,
type: null
});
if (options.path) options.path = this.book.resolve(options.path);
// Replace shortcuts
content = _.reduce(this.shortcuts, _.partial(this._applyShortcut.bind(this), options.type), content);
return Q.nfcall(this.env.renderString.bind(this.env), content, context, options);
};
// Render a file from the book
TemplateEngine.prototype.renderFile = function(filename, options) {
var that = this, context;
return that.book.readFile(filename)
.then(function(content) {
return that.renderString(content, {}, {
path: filename
});
});
};
// Render a page from the book
TemplateEngine.prototype.renderPage = function(page) {
var that = this, context;
return that.book.statFile(page.path)
.then(function(stat) {
context = {
// infos about the file
file: {
path: page.path,
mtime: stat.mtime
}
};
return that.renderString(page.content, context, {
path: page.path,
type: page.type
});
});
};
// Post process content
TemplateEngine.prototype.postProcess = function(content) {
var that = this;
return Q(content)
.then(that.replaceBlocks)
.then(function(_content) {
return batch.execEach(that.blocks, {
max: 20,
fn: function(blk, blkId) {
return Q()
.then(function() {
if (!blk.post) return Q();
return blk.post();
})
.then(function() {
delete that.blocks[blkId];
});
}
})
.thenResolve(_content);
});
};
module.exports = TemplateEngine;
+52
View File
@@ -0,0 +1,52 @@
var Q = require("q");
var _ = require("lodash");
// Execute a method for all element
function execEach(items, options) {
if (_.size(items) == 0) return Q();
var concurrents = 0, d = Q.defer(), pending = [];
options = _.defaults(options || {}, {
max: 100,
fn: function(item) {}
});
function startItem(item, i) {
if (concurrents >= options.max) {
pending.push([item, i]);
return;
}
concurrents++;
Q()
.then(function() {
return options.fn(item, i);
})
.then(function() {
concurrents--;
// Next pending
var next = pending.shift();
if (concurrents == 0 && !next) {
d.resolve();
} else if (next) {
startItem.apply(null, next);
}
})
.fail(function(err) {
pending = [];
d.reject(err);
})
}
_.each(items, startItem);
return d.promise;
}
module.exports = {
execEach: execEach
};
+178
View File
@@ -0,0 +1,178 @@
var _ = require("lodash");
var Q = require("q");
var tmp = require("tmp");
var path = require("path");
var fs = require("graceful-fs");
var fsExtra = require("fs-extra");
var Ignore = require("fstream-ignore");
var fsUtils = {
tmp: {
file: function(opt) {
return Q.nfcall(tmp.file.bind(tmp), opt).get(0)
},
dir: function() {
return Q.nfcall(tmp.dir.bind(tmp)).get(0)
}
},
list: listFiles,
stat: Q.denodeify(fs.stat),
readdir: Q.denodeify(fs.readdir),
readFile: Q.denodeify(fs.readFile),
writeFile: writeFile,
writeStream: writeStream,
mkdirp: Q.denodeify(fsExtra.mkdirp),
copy: Q.denodeify(fsExtra.copy),
remove: Q.denodeify(fsExtra.remove),
symlink: Q.denodeify(fsExtra.symlink),
exists: function(path) {
var d = Q.defer();
fs.exists(path, d.resolve);
return d.promise;
},
existsSync: fs.existsSync.bind(fs),
readFileSync: fs.readFileSync.bind(fs),
clean: cleanFolder,
getUniqueFilename: getUniqueFilename,
}
// Write a file
function writeFile(filename, data, options) {
var d = Q.defer();
try {
fs.writeFileSync(filename, data, options)
} catch(err) {
d.reject(err);
}
d.resolve();
return d.promise;
}
// Write a stream to a file
function writeStream(filename, st) {
var d = Q.defer();
var wstream = fs.createWriteStream(filename);
wstream.on('finish', function () {
d.resolve();
});
wstream.on('error', function (err) {
d.reject(err);
});
st.pipe(wstream);
return d.promise;
}
// Find a filename available
function getUniqueFilename(base, filename) {
if (!filename) {
filename = base;
base = "/";
}
filename = path.resolve(base, filename);
var ext = path.extname(filename);
filename = path.join(path.dirname(filename), path.basename(filename, ext));
var _filename = filename+ext;
var i = 0;
while (1) {
if (!fs.existsSync(filename)) break;
_filename = filename+"_"+i+ext;
i = i + 1;
}
return path.relative(base, _filename);
}
// List files in a directory
function listFiles(root, options) {
options = _.defaults(options || {}, {
ignoreFiles: [],
ignoreRules: []
});
var d = Q.defer();
// Our list of files
var files = [];
var ig = Ignore({
path: root,
ignoreFiles: options.ignoreFiles
});
// Add extra rules to ignore common folders
ig.addIgnoreRules(options.ignoreRules, '__custom_stuff');
// Push each file to our list
ig.on('child', function (c) {
files.push(
c.path.substr(c.root.path.length + 1) + (c.props.Directory === true ? '/' : '')
);
});
ig.on('end', function() {
// Normalize paths on Windows
if(process.platform === 'win32') {
return d.resolve(files.map(function(file) {
return file.replace(/\\/g, '/');
}));
}
// Simply return paths otherwise
return d.resolve(files);
});
ig.on('error', d.reject);
return d.promise;
}
// Clean a folder without removing .git and .svn
// Creates it if non existant
function cleanFolder(root) {
if (!fs.existsSync(root)) return fsUtils.mkdirp(root);
return listFiles(root, {
ignoreFiles: [],
ignoreRules: [
// Skip Git and SVN stuff
'.git/',
'.svn/'
]
})
.then(function(files) {
var d = Q.defer();
_.reduce(files, function(prev, file, i) {
return prev.then(function() {
var _file = path.join(root, file);
d.notify({
i: i+1,
count: files.length,
file: _file
});
return fsUtils.remove(_file);
});
}, Q())
.then(function() {
d.resolve();
}, function(err) {
d.reject(err);
});
return d.promise;
});
}
module.exports = fsUtils;
+112
View File
@@ -0,0 +1,112 @@
var Q = require("q");
var _ = require("lodash");
var url = require("url");
var tmp = require("tmp");
var path = require("path");
var crc = require("crc");
var exec = Q.denodeify(require("child_process").exec);
var URI = require("URIjs");
var fs = require("./fs");
var GIT_PREFIX = "git+";
var GIT_TMP = null;
// Check if an url is a git dependency url
function checkGitUrl(giturl) {
return (giturl.indexOf(GIT_PREFIX) === 0);
}
// Validates a SHA in hexadecimal
function validateSha(str) {
return (/[0-9a-f]{40}/).test(str);
}
// Parse and extract infos
function parseGitUrl(giturl) {
var ref, uri, fileParts, filepath;
if (!checkGitUrl(giturl)) return null;
giturl = giturl.slice(GIT_PREFIX.length);
uri = new URI(giturl);
ref = uri.fragment() || "master";
uri.fragment(null);
// Extract file inside the repo (after the .git)
fileParts =uri.path().split(".git");
filepath = fileParts.length > 1? fileParts.slice(1).join(".git") : "";
if (filepath[0] == "/") filepath = filepath.slice(1);
// Recreate pathname without the real filename
uri.path(_.first(fileParts)+".git");
return {
host: uri.toString(),
ref: ref || "master",
filepath: filepath
};
}
// Clone a git repo from a specific ref
function cloneGitRepo(host, ref) {
var isBranch = false;
ref = ref || "master";
if (!validateSha(ref)) isBranch = true;
return Q()
// Create temporary folder to store git repos
.then(function() {
if (GIT_TMP) return;
return fs.tmp.dir()
.then(function(_tmp) {
GIT_TMP = _tmp;
});
})
// Return or clone the git repo
.then(function() {
// Unique ID for repo/ref combinaison
var repoId = crc.crc32(host+"#"+ref).toString(16);
// Absolute path to the folder
var repoPath = path.resolve(GIT_TMP, repoId);
return fs.exists(repoPath)
.then(function(doExists) {
if (doExists) return;
// Clone repo
return exec("git clone "+host+" "+repoPath)
.then(function() {
return exec("git checkout "+ref, { cwd: repoPath });
})
})
.thenResolve(repoPath);
});
}
// Get file from a git repo
function resolveFileFromGit(giturl) {
if (_.isString(giturl)) giturl = parseGitUrl(giturl);
if (!giturl) return Q(null);
// Clone or get from cache
return cloneGitRepo(giturl.host, giturl.ref)
.then(function(repo) {
// Resolve relative path
return path.resolve(repo, giturl.filepath);
});
};
module.exports = {
checkUrl: checkGitUrl,
parseUrl: parseGitUrl,
resolveFile: resolveFileFromGit
};
+72
View File
@@ -0,0 +1,72 @@
var _ = require("lodash");
var path = require("path");
var fs = require("fs");
var I18N_PATH = path.resolve(__dirname, "../../theme/i18n/")
var getLocales = _.memoize(function() {
var locales = fs.readdirSync(I18N_PATH);
return _.chain(locales)
.map(function(local) {
local = path.basename(local, ".json");
return [local, _.extend({
direction: "ltr"
}, require(path.join(I18N_PATH, local)), {
id: local
})];
})
.object()
.value();
});
var getLanguages = function() {
return _.keys(getLocales());
};
var getByLanguage = function(lang) {
lang = normalizeLanguage(lang);
var locales = getLocales();
return locales[lang];
};
var compareLocales = function(lang, locale) {
var langMain = _.first(lang.split("-"));
var langSecond = _.last(lang.split("-"));
var localeMain = _.first(locale.split("-"));
var localeSecond = _.last(locale.split("-"));
if (locale == lang) return 100;
if (localeMain == langMain) return 50;
if (localeSecond == langSecond) return 20;
return 0;
};
var normalizeLanguage = _.memoize(function(lang) {
var locales = getLocales();
var language = _.chain(locales)
.values()
.map(function(locale) {
locale = locale.id;
return {
locale: locale,
score: compareLocales(lang, locale)
}
})
.filter(function(lang) {
return lang.score > 0;
})
.sortBy("score")
.pluck("locale")
.last()
.value();
return language || lang;
});
module.exports = {
getLocales: getLocales,
getLanguages: getLanguages,
getByLanguage: getByLanguage,
normalizeLanguage: normalizeLanguage
};
+39
View File
@@ -0,0 +1,39 @@
var _ = require("lodash");
var Q = require("q");
var fs = require("./fs");
var spawn = require('spawn-cmd').spawn;
var links = require("./links");
// Convert a svg file
var convertSVG = function(source, dest, options) {
if (!fs.existsSync(source)) return Q.reject(new Error("File doesn't exist: "+source));
var d = Q.defer();
options = _.defaults(options || {}, {
});
//var command = shellescape(['svgexport', source, dest]);
var child = spawn('svgexport', [source, dest]);
child.on("error", function(error) {
if (error.code == "ENOENT") error = new Error("Need to install 'svgexport' using 'npm install svgexport -g'");
return d.reject(error);
});
child.on("close", function(code) {
if (code == 0 && fs.existsSync(dest)) {
d.resolve();
} else {
d.reject(new Error("Error converting "+source+" into "+dest));
}
});
return d.promise;
};
module.exports = {
convertSVG: convertSVG,
INVALID: [".svg"]
};
-4
View File
@@ -1,4 +0,0 @@
module.exports = {
lang: require('./lang'),
links: require('./links')
};
-19
View File
@@ -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
};
+37 -15
View File
@@ -15,46 +15,68 @@ var isRelative = function(href) {
try {
var parsed = url.parse(href);
return !parsed.protocol && parsed.path && parsed.path[0] != '/';
return !!(!parsed.protocol && parsed.path);
} catch(err) {}
return true;
};
// Return true if the link is an achor
var isAnchor = function(href) {
try {
var parsed = url.parse(href);
return !!(!parsed.protocol && !parsed.path && parsed.hash);
} catch(err) {}
return false;
};
// Normalize a path to be a link
var normalizeLink = function(s) {
return s.replace(/\\/g, '/');
};
// 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);
if (isExternal(_href)) return _href;
// make it relative to output
_href = path.relative(outdir, _href);
// Path '_href' inside the base folder
var hrefInRoot = path.normalize(path.join(dir, _href));
if (_href[0] == "/") hrefInRoot = path.normalize(_href.slice(1));
if (process.platform === 'win32') {
_href = _href.replace(/\\/g, '/');
}
// Make it relative to output
_href = path.relative(outdir, hrefInRoot);
// Normalize windows paths
_href = normalizeLink(_href);
return _href;
};
// Join links
var join = function() {
var _href = path.join.apply(path, arguments);
if (process.platform === 'win32') {
_href = _href.replace(/\\/g, '/');
}
return _href;
return normalizeLink(_href);
};
// Change extension
var changeExtension = function(filename, newext) {
return path.join(
path.dirname(filename),
path.basename(filename, path.extname(filename))+newext
);
};
module.exports = {
isAnchor: isAnchor,
isRelative: isRelative,
isExternal: isExternal,
toAbsolute: toAbsolute,
join: join
join: join,
changeExtension: changeExtension,
normalize: normalizeLink
};
+102
View File
@@ -0,0 +1,102 @@
var _ = require('lodash');
var util = require('util');
var color = require('bash-color');
var LEVELS = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
DISABLED: 10
};
var COLORS = {
DEBUG: color.purple,
INFO: color.cyan,
WARN: color.yellow,
ERROR: color.red
};
module.exports = function(_write, logLevel) {
var logger = {};
var lastChar = '\n';
if (_.isString(logLevel)) logLevel = LEVELS[logLevel.toUpperCase()];
// Write a simple message
logger.write = function(msg) {
msg = msg.toString();
lastChar = _.last(msg);
return _write(msg);
};
// Format a message
logger.format = function() {
return util.format.apply(util, arguments);
};
// Write a line
logger.writeLn = function(msg) {
return this.write((msg || "")+"\n");
};
// Write a message with a certain level
logger.log = function(level) {
if (level < logLevel) return;
var levelKey = _.findKey(LEVELS, function(v) { return v == level; });
var args = Array.prototype.slice.apply(arguments, [1]);
var msg = logger.format.apply(logger, args);
if (lastChar == '\n') {
msg = COLORS[levelKey](levelKey.toLowerCase()+":")+" "+msg;
}
return logger.write(msg);
};
logger.logLn = function() {
if (lastChar != '\n') logger.write("\n");
var args = Array.prototype.slice.apply(arguments);
args.push("\n");
logger.log.apply(logger, args);
};
// Write a OK
logger.ok = function(level) {
var args = Array.prototype.slice.apply(arguments, [1]);
var msg = logger.format.apply(logger, args);
if (arguments.length > 1) {
logger.logLn(level, color.green('>> ') + msg.trim().replace(/\n/g, color.green('\n>> ')));
} else {
logger.log(level, color.green("OK"), "\n");
}
};
// Write an "FAIL"
logger.fail = function(level) {
return logger.log(level, color.red("ERROR")+"\n");
};
_.each(_.omit(LEVELS, 'DISABLED'), function(level, levelKey) {
levelKey = levelKey.toLowerCase();
logger[levelKey] = _.partial(logger.log, level);
logger[levelKey].ln = _.partial(logger.logLn, level);
logger[levelKey].ok = _.partial(logger.ok, level);
logger[levelKey].fail = _.partial(logger.fail, level);
logger[levelKey].promise = function(p) {
return p.
then(function(st) {
logger[levelKey].ok();
return st;
}, function(err) {
logger[levelKey].fail();
throw err;
});
}
});
return logger;
};
module.exports.LEVELS = LEVELS;
module.exports.COLORS = COLORS;
@@ -27,19 +27,35 @@ function navigation(summary, files) {
files = _.isArray(files) ? files : (_.isString(files) ? [files] : null);
// List of all navNodes
// Flatten chapters, then add in default README node if ndeeded etc ...
// Flatten chapters, then add in default README node if needed 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];
var mapping = _.chain(navNodes)
.map(function(current, i) {
var prev = null, next = null;
// Skip if no path
if(!current.path) return null;
// Find prev
prev = _.chain(navNodes)
.slice(0, i)
.reverse()
.find(function(node) {
return node.exists && !node.external;
})
.value();
// Find next
next = _.chain(navNodes)
.slice(i+1)
.find(function(node) {
return node.exists && !node.external;
})
.value();
return [current.path, {
title: current.title,
prev: prev,
@@ -47,7 +63,7 @@ function navigation(summary, files) {
level: current.level,
}];
})
.filter()
.compact()
.object()
.value();
+343
View File
@@ -0,0 +1,343 @@
var Q = require('q');
var _ = require('lodash');
var url = require('url');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
var request = require('request');
var crc = require("crc");
var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');
var batch = require('./batch');
// Render a cheerio dom as html
var renderDom = function($, dom, options) {
if (!dom && $._root && $._root.children) {
dom = $._root.children;
}
options = options|| dom.options || $._options;
return domSerializer(dom, options);
};
// Map of images that have been converted
var imgConversionCache = {};
function replaceText($, el, search, replace, text_only ) {
return $(el).each(function(){
var node = this.firstChild,
val,
new_val,
// Elements to be removed at the end.
remove = [];
// Only continue if firstChild exists.
if ( node ) {
// Loop over all childNodes.
do {
// Only process text nodes.
if ( node.nodeType === 3 ) {
// The original node value.
val = node.nodeValue;
// The new value.
new_val = val.replace( search, replace );
// Only replace text if the new value is actually different!
if ( new_val !== val ) {
if ( !text_only && /</.test( new_val ) ) {
// The new value contains HTML, set it in a slower but far more
// robust way.
$(node).before( new_val );
// Don't remove the node yet, or the loop will lose its place.
remove.push( node );
} else {
// The new value contains no HTML, so it can be set in this
// very fast, simple way.
node.nodeValue = new_val;
}
}
}
} while ( node = node.nextSibling );
}
// Time to remove those elements!
remove.length && $(remove).remove();
});
};
function pregQuote( str ) {
return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
};
// Adapt an html snippet to be relative to a base folder
function normalizeHtml(src, options) {
var $ = cheerio.load(src, {
// We should parse html without trying to normalize too much
xmlMode: false,
// SVG need some attributes to use uppercases
lowerCaseAttributeNames: false,
lowerCaseTags: false
});
var toConvert = [];
var svgContent = {};
var outputRoot = options.book.options.output;
imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};
// Find svg images to extract and process
if (options.convertImages) {
$("svg").each(function() {
var content = renderDom($, $(this));
var svgId = _.uniqueId("svg");
var dest = svgId+".svg";
// Generate filename
dest = "/"+fs.getUniqueFilename(outputRoot, dest);
svgContent[dest] = '<?xml version="1.0" encoding="UTF-8"?>'+content;
$(this).replaceWith($("<img>").attr("src", dest));
});
}
// Find images to normalize
$("img").each(function() {
var origin = undefined;
var src = $(this).attr("src");
if (!src) return;
var isExternal = links.isExternal(src);
// Transform as relative to the bases
if (links.isRelative(src)) {
src = links.toAbsolute(src, options.base, options.output);
}
// Convert if needed
if (options.convertImages) {
// If image is external and ebook, then downlaod the images
if (isExternal) {
origin = src;
src = "/"+crc.crc32(origin).toString(16)+path.extname(origin);
src = links.toAbsolute(src, options.base, options.output);
isExternal = false;
}
var ext = path.extname(src);
var srcAbs = links.join("/", options.base, src);
// Test image extension
if (_.contains(imgUtils.INVALID, ext)) {
if (imgConversionCache[outputRoot][srcAbs]) {
// Already converted
src = imgConversionCache[outputRoot][srcAbs];
} else {
// Not converted yet
var dest = "";
// Replace extension
dest = links.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
dest = dest[0] == "/"? dest.slice(1) : dest;
// Get a name that doesn't exists
dest = fs.getUniqueFilename(outputRoot, dest);
options.book.log.debug.ln("detect invalid image (will be converted to png):", srcAbs);
// Add to cache
imgConversionCache[outputRoot][srcAbs] = "/"+dest;
// Push to convert
toConvert.push({
origin: origin,
content: svgContent[srcAbs],
source: isExternal? srcAbs : path.join("./", srcAbs),
dest: path.join("./", dest)
});
src = links.join("/", dest);
}
// Reset as relative to output
src = links.toAbsolute(src, options.base, options.output);
}
else if (origin) {
// Need to downlaod image
toConvert.push({
origin: origin,
source: path.join("./", srcAbs)
});
}
}
$(this).attr("src", src);
});
$("a").each(function() {
var href = $(this).attr("href");
if (!href) return;
if (links.isAnchor(href)) {
// Keep it as it is
} else if (links.isRelative(href)) {
var parts = url.parse(href);
var absolutePath = path.join(options.base, parts.pathname);
var anchor = parts.hash || "";
// If is in navigation relative: transform as content
if (options.navigation[absolutePath]) {
absolutePath = options.book.contentLink(absolutePath);
}
// Transform as absolute
href = links.toAbsolute("/"+absolutePath, options.base, options.output)+anchor;
} else {
// External links
$(this).attr("target", "_blank");
}
// Transform extension
$(this).attr("href", href);
});
// Replace glossayr terms
_.each(options.glossary, function(term) {
var r = new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
var includedInFiles = false;
$("*").each(function() {
replaceText($, this, r, function(match) {
// Add to files index in glossary
if (!includedInFiles) {
includedInFiles = true;
term.files = term.files || [];
term.files.push(options.navigation[options.input]);
}
return "<a href='"+links.toAbsolute("/GLOSSARY.html", options.base, options.output)+"#"+term.id+"' class='glossary-term' title='"+_.escape(term.description)+"'>"+match+"</a>";
});
});
});
return {
html: renderDom($),
images: toConvert
};
};
// Convert svg images to png
function convertImages(images, options) {
if (!options.convertImages) return Q();
var downloaded = [];
options.book.log.debug.ln("convert ", images.length, "images to png");
return batch.execEach(images, {
max: 100,
fn: function(image) {
var imgin = path.resolve(options.book.options.output, image.source);
return Q()
// Write image if need to be download
.then(function() {
if (!image.origin && !_.contains(downloaded, image.origin)) return;
options.book.log.debug("download image", image.origin, "...");
downloaded.push(image.origin);
return options.book.log.debug.promise(fs.writeStream(imgin, request(image.origin)));
})
// Write svg if content
.then(function() {
if (!image.content) return;
return fs.writeFile(imgin, image.content);
})
// Convert
.then(function() {
if (!image.dest) return;
var imgout = path.resolve(options.book.options.output, image.dest);
options.book.log.debug("convert image", image.source, "to", image.dest, "...");
return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
});
}
})
.then(function() {
options.book.log.debug.ok(images.length+" images converted with success");
});
};
// Adapt page content to be relative to a base folder
function normalizePage(sections, options) {
options = _.defaults(options || {}, {
// Current book
book: null,
// Do we need to convert svg?
convertImages: false,
// Current file path
input: ".",
// Navigation to use to transform path
navigation: {},
// Directory parent of the file currently in rendering process
base: "./",
// Directory parent from the html output
output: "./",
// Glossary terms
glossary: []
});
// List of images to convert
var toConvert = [];
sections = _.map(sections, function(section) {
if (section.type != "normal") return section;
var out = normalizeHtml(section.content, options);;
toConvert = toConvert.concat(out.images);
section.content = out.html;
return section;
});
return Q()
.then(function() {
toConvert = _.uniq(toConvert, 'source');
return convertImages(toConvert, options);
})
.thenResolve(sections);
};
// Extract text from sections
function extractText(sections) {
return _.reduce(sections, function(prev, section) {
if (section.type != "normal") return prev;
var $ = cheerio.load(section.content);
$("*").each(function() {
prev = prev+" "+$(this).text();
});
return prev;
}, "");
};
module.exports = {
normalize: normalizePage,
extractText: extractText
};
@@ -44,4 +44,4 @@ var calculProgress = function(navigation, current) {
};
}
module.exports = calculProgress;
module.exports = calculProgress;
+7 -1
View File
@@ -20,7 +20,13 @@ function optionsToShellArgs(options) {
.join(" ");
}
function escapeRegex(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
module.exports = {
escapeRegex: escapeRegex,
escapeShellArg: escapeShellArg,
optionsToShellArgs: optionsToShellArgs
optionsToShellArgs: optionsToShellArgs,
toLowerCase: String.prototype.toLowerCase.call.bind(String.prototype.toLowerCase)
};
+38
View File
@@ -0,0 +1,38 @@
var Q = require('q');
var _ = require('lodash');
var path = require('path');
var Gaze = require('gaze').Gaze;
var parsers = require('gitbook-parsers')
function watch(dir) {
var d = Q.defer();
dir = path.resolve(dir);
var toWatch = [
"book.json", "book.js"
];
_.each(parsers.extensions, function(ext) {
toWatch.push("**/*"+ext);
});
var gaze = new Gaze(toWatch, {
cwd: dir
});
gaze.once("all", function(e, filepath) {
gaze.close();
d.resolve(filepath);
});
gaze.once("error", function(err) {
gaze.close();
d.reject(err);
});
return d.promise;
}
module.exports = watch;
+34 -29
View File
@@ -1,35 +1,40 @@
{
"name": "gitbook",
"version": "1.2.0",
"homepage": "http://www.gitbook.io/",
"version": "2.0.0-beta.4",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
"dependencies": {
"q": "1.0.1",
"lodash": "2.4.1",
"kramed": "0.4.3",
"kramed-text-renderer": "0.2.1",
"lunr": "0.5.2",
"swig": "1.3.2",
"send": "0.2.0",
"fstream-ignore": "0.0.7",
"commander": "2.2.0",
"graceful-fs": "3.0.2",
"fs-extra": "0.10.0",
"highlight.js": "8.3.0",
"tmp": "0.0.23",
"semver": "2.2.1",
"gaze": "~0.5.1",
"lunr": "0.5.7",
"lodash": "3.5.0",
"graceful-fs": "3.0.5",
"resolve": "0.6.3",
"tiny-lr-fork": "0.0.5",
"gitbook-plugin": "0.0.2",
"gitbook-plugin-mathjax": "0.0.6",
"fs-extra": "0.16.5",
"fstream-ignore": "1.0.2",
"gitbook-parsers": "0.5.4",
"nunjucks": "git+https://github.com/mozilla/nunjucks.git#0f8b21b8df7e8e852b2e1889388653b7075f0d09",
"nunjucks-autoescape": "0.1.1",
"nunjucks-filter": "0.1.0",
"nunjucks-i18n": "1.0.0",
"semver": "2.2.1",
"npmi": "0.1.1",
"cheerio": "0.18.0",
"gitbook-plugin-livereload": "0.0.1",
"gitbook-plugin-exercises": "1.0.0",
"gitbook-plugin-quizzes": "1.0.0"
"gaze": "~0.5.1",
"send": "0.2.0",
"tiny-lr": "0.1.5",
"tmp": "0.0.24",
"crc": "3.2.1",
"bash-color": "0.0.3",
"URIjs": "1.14.1",
"request": "2.51.0",
"npm": "2.4.1",
"dom-serializer": "0.1.0",
"spawn-cmd": "0.0.2"
},
"devDependencies": {
"mocha": "1.18.2",
"mocha": "2.2.1",
"grunt": "~0.4.2",
"grunt-cli": "0.1.11",
"grunt-contrib-copy": "0.5.0",
@@ -38,21 +43,21 @@
"grunt-bower-install-simple": "0.9.2"
},
"scripts": {
"test": "export TESTING=true; mocha --reporter list"
},
"bin": {
"gitbook": "./bin/gitbook.js"
"test": "node_modules/.bin/mocha --reporter list --timeout 15000"
},
"repository": {
"type": "git",
"url": "https://github.com/GitbookIO/gitbook.git"
},
"bin": {
"gitbook": "./bin/gitbook.js"
},
"keywords": [
"git",
"book",
"gitbook"
],
"author": "FriendCode <contact@friendco.de>",
"author": "FriendCode Inc. <contact@gitbook.com>",
"license": "Apache 2",
"bugs": {
"url": "https://github.com/GitbookIO/gitbook/issues"
@@ -60,11 +65,11 @@
"contributors": [
{
"name": "Aaron O'Mullan",
"email": "aaron.omullan@gmail.com"
"email": "aaron@gitbook.com"
},
{
"name": "Samy Pessé",
"email": "samypesse@gmail.com"
"email": "samy@gitbook.com"
}
]
}
+14
View File
@@ -0,0 +1,14 @@
var path = require('path');
var assert = require('assert');
var Book = require('../').Book;
describe('Configuration parsing', function () {
it('should correctly load from json', function() {
assert(books[0].options.title == "Test");
});
it('should correctly load from javascript', function() {
assert(books[4].options.title == "Test 2");
});
});
+36
View File
@@ -0,0 +1,36 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var cheerio = require('cheerio');
var fs = require("fs");
var fsUtil = require("../lib/utils/fs");
describe('eBook Generator', function () {
it('should correctly generate ebook pages', function(done) {
testGeneration(books[1], "ebook", function(output) {
assert(fs.existsSync(path.join(output, "SUMMARY.html")));
}, done);
});
it('should correctly convert svg images to png', function(done) {
testGeneration(books[4], "ebook", function(output) {
// Check that all images exists
_.each([
"index.html",
"sub/PAGE.html"
], function(pageName) {
var pageFile = path.join(output, pageName);
var pageFolder = path.dirname(pageFile);
var pageContent = fs.readFileSync(pageFile, {encoding: "utf8"});
var $ = cheerio.load(pageContent);
$("img").each(function() {
var src = $(this).attr("src");
assert(fs.existsSync(path.resolve(pageFolder, src)), src+" not found for page "+pageName);
})
});
}, done);
});
});
-13
View File
@@ -1,13 +0,0 @@
# Summary
* [Custom name for Introduction](README.md)
* [Chapter 1](chapter-1/README.md)
* [Article 1](chapter-1/ARTICLE1.md)
* [Article 2](chapter-1/ARTICLE2.md)
* [article 1.2.1](chapter-1/ARTICLE-1-2-1.md)
* [article 1.2.2](chapter-1/ARTICLE-1-2-2.md)
* [Chapter 2](chapter-2/README.md)
* [Chapter 3](chapter-3/README.md)
* [Chapter 4](chapter-4/README.md)
* Unfinished article
* Unfinished Chapter
-120
View File
@@ -1,120 +0,0 @@
# Learn Node.js by Example
## Requirements
- [x] A computer with internet access
- [ ] Time: 30h (e.g. 2 months 30 mins per day or **1 week intensive**)
## What is Node.js ?
Node.js lets you *easily* build networked software (websites, applications "apps",
using JavaScript).
Its not "*point-and-click*" like WordPress, SquareSpace or Salesforce;
you will need to write some "code". But as I will demonstrate, that's
a *lot* easier than it sounds and gives you more power/flexibility
and puts you in full control.
## Node.js is not "Version 1.0" yet can I used it in Production?
Yes! Some of the biggest organisations/companies in the world
are using Node.js in Production systems:
[Alibaba](https://github.com/alibaba/node-hbase-client),
[Ajax.org](Ajax.org),
[Box.com](http://tech.blog.box.com/2014/06/node-js-high-availability-at-box/), British Sky Broadcasting (Sky/Now TV),
CNN,
[Cloudup](https://cloudup.com/),
Conde Nast,
[DirectTV](http://strongloop.com/strongblog/node-summit-media-companies-embrace-node-js-for-rapidly-developing-responsive-apps/),
[Dow Jones](http://nodejs.org/industry),
eBay,
[FeedHenry](http://www.feedhenry.com/),
[GitHub](https://twitter.com/github/status/16979699217465344),
[Google](http://venturebeat.com/2012/01/24/node-at-google-mozilla-yahoo/),
[Groupon](http://nodeup.com/fiftyeight),
HBO,
Help.com,
[HP](https://github.com/joyent/node/wiki/Projects,-Applications,-and-Companies-Using-Node),
iTV,
[Joyent](https://www.joyent.com/) (duh!),
[Klout](https://klout.com),
LinkedIn,
McDonalds,
[Medium](https://medium.com/the-story),
Mozilla,
NetFlix,
[OpenTable](http://hapijs.com/community),
PayPal,
Pearson,
~~Q~~,
[Revolt](http://revolt.tv/),
[Square](https://modulus.io/companies-using-node),
Tesco,
ThomasCook,
Trello,
Uber,
Voxer,
Walmart,
Wikimedia (in progress of moving to SOA with node!)
Yahoo,
Yammer,
[Yandex](https://www.youtube.com/watch?v=zdCxgdH4wZo),
[Zendesk](http://radar.zendesk.com/)
Want more? See: http://nodejs.org/industry/ and <br />
https://github.com/joyent/node/wiki/Projects,-Applications,-and-Companies-Using-Node
# Try it!
## Download & Install
> http://nodejs.org/download/
## Node.js (Core) API
The node.js ("core") has many useful modules.
Bookmark: [http://nodejs.org/api](http://nodejs.org/api/) (you will come back to it)
## Stability (Can we use it?)
> *Which node.js* ***core*** *package(s) can/should I use?*
Every core module has a
["***Stability Index***"](http://nodejs.org/api/documentation.html#documentation_stability_index)
rating on the node.js API.
**General rule**: If you are being *paid* to write code
that runs in node.js, <br /> pick core modules/methods
with stability **Stable**, **API Frozen** and **Locked**.
![Node.js Stability Index](http://i.imgur.com/xIroFrS.png)
### Examples
- [**cluster**](http://nodejs.org/api/cluster.html) is ***Experimental*** - don't use
- [**domain**](http://nodejs.org/api/domain.html) is ***Unstable*** - don't use
- [**path**](http://nodejs.org/api/path.html) is ***Stable*** - use
- [**events**](http://nodejs.org/api/events.html) is ***Frozen*** - use
- [**assert**](http://nodejs.org/api/assert.html) is ***Locked*** - use
Core Modules to Learn
- path
- os
Community Modules to Learn:
- [jscs](https://www.npmjs.org/package/jscs) - code style checker
- [q](https://www.npmjs.org/package/q) - promises library
- [nd](https://www.npmjs.org/package/nd) - view documentation for a module
-3
View File
@@ -1,3 +0,0 @@
# Nice course
Check out this source file [in C++](../src/something.cpp)
-30
View File
@@ -1,30 +0,0 @@
# Magic
Sufficiently advanced technology, beyond the understanding of the observer producing a sense of wonder.
Hello, I am random noise in the middle of this beautiful Glossary. (Really astonishing !)
# PHP
An atrocious language, invented for the sole purpose of inflicting pain and suffering amongst the proframming wizards of this world.
# Clojure
Lisp re-invented for hipsters.
# Go
Go Go Google [Wow](https://www.google.com)
Fantastic, I love code too ! :
```py
def f(x):
return x * 4
# Wow this is some really awesome code
# totally mind blowing
# but we don't care, it shouldn't be in our glossary !
print(f(9))
```
# Gitbook
Awesome project. Really amazing, I'm really at a loss for words ...
-11
View File
@@ -1,11 +0,0 @@
## Wow such book
Some nice content here
---
A beautiful separator, but non an exercise or a quiz !
---
Some more beautiful text, because `this` book is awesome ...
-12
View File
@@ -1,12 +0,0 @@
# Images Test
This is a test for images path calculation. It supposed this fiel is in a syntax/ folder
### #
[![Screen](./preview.png)](./preview.png)
### 2
[![Screen](../preview2.png)](./preview2.png)
-29
View File
@@ -1,29 +0,0 @@
# 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 ?
```
----
-5
View File
@@ -1,5 +0,0 @@
# Nice course
Check out this other chapter [Test](test.md)
Check out this other chapter [Test](../before.md)
-68
View File
@@ -1,68 +0,0 @@
# Python basics
Python is a nice language, you can add stuff. Bla bla bla.
Lets jump into an exercise :
---
It's dead simple, `c` must be the sum of `a` and `b`
```py
a = 1
b = 2
```
```py
a = 1
b = 2
c = a + b
```
```py
assert(c, 3)
```
---
Some more nice content ....
[Cool stuff](http://gitbook.io)
[Link to another Markdown file](./xyz/file.md)
And look at this pretty picture:
![Pretty](../assets/my-pretty-picture.png "Pretty")
Lets go for another exercise but this time with some context :
---
Exercise with some context code :
Using the `double` function provided, build a `quadruple` function
```py
```
```py
def quadruple(x):
return double(double(x))
```
```py
assert(quadruple(8), 32)
```
```py
def double(x):
return x * 2
```
---
-48
View File
@@ -1,48 +0,0 @@
# Gitbook quiz
Gitbook lets you write a quiz using GFM tables:
---
Here's a quiz about Gitbook
| | Good | Bad |
| ---------------- | ---- | --- |
| What is Gitbook? | (x) | ( ) |
> Gitbook is good
What does Gitbook support?
- [x] Table-based questions with radio buttons
- [x] Table-based questions with checkboxes
- [ ] Telepathy
- [x] List-based questions with checkboxes
- [x] List-based questions with radio buttons
- [ ] Moon-on-a-stick
> Gitbook supports table and list based quiz questions using either radio buttons or checkboxes.
>
> Gitbook is not telepathic and does not give you the moon on a stick.
---
Some more nice content ....
[Cool stuff](http://gitbook.io)
[Link to another Markdown file](./xyz/file.md)
---
Quiz test 2: What does Gitbook support?
- [x] Table-based questions with radio buttons
- [x] Table-based questions with checkboxes
- [ ] Telepathy
- [x] List-based questions with checkboxes
- [x] List-based questions with radio buttons
- [ ] Moon-on-a-stick
> Gitbook supports table and list based quiz questions using either radio buttons or checkboxes.
---
-7
View File
@@ -1,7 +0,0 @@
# This is the title
This is the book description.
other content
...
-68
View File
@@ -1,68 +0,0 @@
# Title
Some text
---
## NOT Exercise
Simple subsection NOT exercise
```
x = 1
```
What is this
```
y = [1, 2, 3]
```
```
z = {a: 1, b: 2}
```
---
## Exercise
Define a variable `x` equal to 10.
```js
var x =
```
```js
var x = 10;
```
```js
assert(x == 10);
```
```js
// This is context code available everywhere
// The user will be able to call magicFunc in his code
function magicFunc() {
return 3;
}
```
---
## Another exercise
Bla bla bla ... This time with no `context` code.
```js
var x =
```
```js
var x = 10;
```
```js
assert(x == 10);
```
-12
View File
@@ -1,12 +0,0 @@
# Summary
* [Chapter 1](chapter-1/README.md)
* [Article 1](chapter-1/ARTICLE1.md)
* [Article 2](chapter-1/ARTICLE2.md)
* [article 1.2.1](\chapter-1\ARTICLE-1-2-1.md)
* [article 1.2.2](/chapter-1/ARTICLE-1-2-2.md)
* [Chapter 2](chapter-2/README.md)
* [Chapter 3](chapter-3/README.md)
* [Chapter 4](chapter-4/README.md)
* Unfinished article
* Unfinished Chapter
-2
View File
@@ -1,2 +0,0 @@
# This is a test
-3
View File
@@ -1,3 +0,0 @@
# Summary
* [test](test.md)
-1
View File
@@ -1 +0,0 @@
# Test
-1
View File
@@ -1 +0,0 @@
# This should fail
-7
View File
@@ -1,7 +0,0 @@
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("All is well\n");
return 0;
}
+6
View File
@@ -0,0 +1,6 @@
# Glossary
## Description
This is the dexcription of a description.
+3
View File
@@ -0,0 +1,3 @@
# Other Title
This is a description, it's simple, no ?
+1
View File
@@ -0,0 +1 @@
# Summary
+3
View File
@@ -0,0 +1,3 @@
{
"title": "Test"
}
+3
View File
@@ -0,0 +1,3 @@
body {
background: red;
}
+9
View File
@@ -0,0 +1,9 @@
# Test
a test text
# Test 2
a second test
+4
View File
@@ -0,0 +1,4 @@
# Test
This file is not parsed by gitbook because the structure is defined in book.json.
+6
View File
@@ -0,0 +1,6 @@
# Summary
* [Chapter 1](test.md)
* [Article 1](sub/test1.md)
* [Chapter 2](test2.md)
* [Google](https://www.google.com)
+5
View File
@@ -0,0 +1,5 @@
{
"structure": {
"readme": "intro.md"
}
}
+4
View File
@@ -0,0 +1,4 @@
# My Book
Test description
+8
View File
@@ -0,0 +1,8 @@
# This file is used to etst links transformations:
This is a relative link [test](../intro.md).
![image](../image.png)
This is a glossary link.
+4
View File
@@ -0,0 +1,4 @@
# Languages
* [English](en/)
* [French](fr/)
+2
View File
@@ -0,0 +1,2 @@
# Multi-Languages test
+1
View File
@@ -0,0 +1 @@
# English Book
+1
View File
@@ -0,0 +1 @@
# Summary
+1
View File
@@ -0,0 +1 @@
# French Book
+1
View File
@@ -0,0 +1 @@
# Summary
+4
View File
@@ -0,0 +1,4 @@
= My Book
Test description
+5
View File
@@ -0,0 +1,5 @@
= Summary
. link:test.adoc[Chapter 1]
.. link:test1.adoc[Article 1]
. link:test2.adoc[Chapter 2]

Some files were not shown because too many files have changed in this diff Show More