Compare commits

..

329 Commits

Author SHA1 Message Date
Samy Pessé ce50a1d50f Bump version to 3.0.0-pre.14 2016-05-10 14:37:22 +02:00
Samy Pessé a76e018d36 Update theme-default@1.0.0-pre.10 2016-05-10 14:37:05 +02:00
Samy Pessé b06f1fd677 Fix article.depth starting at 2 2016-05-10 14:36:03 +02:00
Samy Pessé 3d8b1880dc Fix page's title in pdf header/footer 2016-05-10 14:25:50 +02:00
Samy Pessé 1a13c8c9bb Add option "--no-live" for command serve 2016-05-10 13:14:32 +02:00
Samy Pessé 852fae3a09 Fix #1232: add option "--no-watch" to disable watcher and live reloading 2016-05-10 13:08:19 +02:00
Samy Pessé d342d31c8e Fix error when summary contains entry without ref 2016-05-10 12:12:19 +02:00
Samy Pesse 5b3f54fbc8 Fix "gitbook init" when article has no ref 2016-05-10 09:55:22 +02:00
Samy Pessé 308ece784c Merge pull request #1288 from todvora/patch-1
Fixed summary encoding
2016-05-10 08:55:40 +02:00
Tomas Dvorak 0758ddf04e Fixed summary encoding 2016-05-10 08:17:52 +02:00
Samy Pesse f24d9aef73 Add test for block replaced with anchors 2016-05-09 22:17:46 +02:00
Samy Pesse f8bb55f258 Adapt unit tests for blocks 2016-05-09 22:01:25 +02:00
Samy Pesse 76daebeb84 Fix unit test for templateBlock using a removed method "getParse" 2016-05-09 18:41:21 +02:00
Samy Pesse b0d366b7e8 Fix linting warning in models/templateBlock.js 2016-05-08 22:45:26 +02:00
Samy Pesse 3d0054dd64 Fix #1286: by default blocks should not be parsable 2016-05-08 22:33:41 +02:00
Samy Pessé 13bc89ff44 Merge pull request #1285 from GitbookIO/api/summary
[WIP] Api for plugins to access summary
2016-05-08 22:28:15 +02:00
Samy Pesse bb910f984e Improve comment for API summary.walk 2016-05-08 22:26:46 +02:00
Samy Pessé e576fdea14 Merge pull request #1278 from dylang/patch-1
bump bash-color to version that has a license
2016-05-08 18:44:53 +02:00
Samy Pesse c7864927ec Add method summary.getArticleByPath to summary api 2016-05-08 11:08:27 +02:00
Samy Pesse 54956de59f Start API for plugins to access the summary 2016-05-08 10:33:45 +02:00
Samy Pesse 2cbd30d1f2 Fix command "gitbook init" 2016-05-07 00:24:12 +02:00
Dylan Greene 55614f1675 bump bash-color to version that has a license
Version currently being used does not have a license making it difficult for companies to use.
2016-05-06 09:56:43 -04:00
Samy Pesse 4f7421993f Bump version to 3.0.0-pre.13 2016-05-06 10:59:52 +02:00
Samy Pesse 32ad7b530e Fix #1266: don't fail on "a" tags without "href" 2016-05-06 10:57:41 +02:00
Samy Pesse ee5d5010f6 Correctly read assets using input fs when copying 2016-05-06 10:54:13 +02:00
Samy Pesse cf7e5884cd Add method readAsStream on fs model 2016-05-06 10:53:55 +02:00
Samy Pesse 11a0a64d9e Add failing test for #1272 2016-05-06 10:36:55 +02:00
Samy Pesse b6051babac Adapt unit tests for new templating post process 2016-05-05 11:09:35 +02:00
Samy Pesse 34947b5e20 Add mode TemplateOutput to represent {content + blocks} from template 2016-05-05 11:00:05 +02:00
Samy Pesse c621380b66 Add tests for replaceShortcuts 2016-05-04 20:18:48 +02:00
Samy Pesse c4b54033ce Fix #1268: fix shortcuts of template block not being applied 2016-05-04 19:01:31 +02:00
Samy Pesse 4d85d6eb6e Fix #1267: don't fail when "node_modules" folder doesn't exist 2016-05-04 17:41:15 +02:00
Samy Pessé cc9274c9af Bump version to 3.0.0-pre.12 2016-05-04 13:50:52 +02:00
Johan Preynat bbdf7efc34 Fix location.relative(dir, file) trailing slash if file is a directory 2016-05-04 11:51:39 +02:00
Samy Pessé 38a986995a Merge pull request #1264 from GitbookIO/fix/plugin-resources
Fix Loading plugin resources
2016-05-04 10:33:29 +02:00
Johan Preynat ee8d35df7a Update Plugins.listResources() to use the prepared resources list 2016-05-04 10:30:11 +02:00
Johan Preynat be411cd5ff Update copyPluginAssets to use prepared resources 2016-05-04 10:27:21 +02:00
Johan Preynat 2104019171 List resources per plugin in website generator onInit() 2016-05-04 10:26:57 +02:00
Johan Preynat 4e564707df Add resources to website generator state 2016-05-04 10:26:06 +02:00
Johan Preynat da8611e0c3 Add global method resolve(fileName) 2016-05-04 10:25:37 +02:00
Samy Pessé 5d11641a5f Merge pull request #1260 from GitbookIO/move-article
Adds move and remove article modifier.
2016-05-04 09:47:50 +02:00
Samy Pessé 24d38e4fcf Fix #1263: Fix page generation for AsciiDoc 2016-05-04 09:30:09 +02:00
Samy Pessé efb65a8413 Remove filter for pdf/epub/mobi files when outputting 2016-05-03 17:18:13 +02:00
Soreine 3fc90554f5 Add tests, and fixes. 2016-05-03 16:02:58 +02:00
Soreine 4ebe28faae Update insert specification 2016-05-03 13:11:32 +02:00
Soreine 693171cb05 Adds move and remove article modifier. 2016-05-03 12:37:26 +02:00
Samy Pessé 42a2d090aa Bump version to 3.0.0-pre.11 2016-05-03 12:15:30 +02:00
Samy Pessé c84eaa83dd Merge pull request #1259 from GitbookIO/png-data-uri
Adding data-uri support for PNG Images in Books
2016-05-03 12:10:09 +02:00
Johan Preynat 74c3ff80cb lib/models/config.js: Fix wrong call to PluginDependency.listToArray() to PluginDependency.listFromArray() 2016-05-03 12:04:52 +02:00
Johan Preynat 5893d3ee64 lib/parse/parseConfig.js: Remove unused require 2016-05-03 12:04:04 +02:00
Johan Preynat 7f435c56a0 lib/output/modifiers/inlineAssets.js: Add inlinePng() modifier 2016-05-03 11:30:02 +02:00
Johan Preynat 59b7100cf6 lib/output/modifiers/__tests__/inlinePng.js: Add test file for inlinePng modifier 2016-05-03 11:29:40 +02:00
Johan Preynat 0923d1aa24 lib/output/modifiers/inlinePng.js: Add modifier to save inline PNG images 2016-05-03 11:29:17 +02:00
Johan Preynat ffe6c36f2f lib/output/modifiers/resolveImages.js: Don't resolve images when src is a data URI 2016-05-03 11:28:39 +02:00
Johan Preynat 8c4752c5d8 lib/utils/__tests__/location.js: Add location.isDataURI() test file 2016-05-03 11:27:36 +02:00
Johan Preynat d6202e522b lib/utils/images.js: Add convertInlinePNG() method 2016-05-03 11:27:05 +02:00
Johan Preynat 50a24c62c5 lib/utils/location.js: Add isDataURI() method 2016-05-03 11:26:38 +02:00
Soreine 6b17d08892 Adds default file naming utilities in Book model 2016-05-03 11:17:27 +02:00
Samy Pesse 79c91fd8c5 Log npm version being used when running gitbook install 2016-05-02 22:18:19 +02:00
Samy Pesse 95764258ab Update dependencies 2016-05-02 22:16:51 +02:00
Samy Pesse 41e6872557 Merge branch 'fix/plugins_deps' 2016-05-02 22:02:45 +02:00
Samy Pesse 30c96c37c2 Adapt Plugin.listAll to use a list of PluginDependency 2016-05-02 22:02:24 +02:00
Samy Pesse 20f1cf3dff Enable promise long stack trace when testing or debugging 2016-05-02 22:01:05 +02:00
Samy Pesse 8de0d6186e Fix mock fs for windows testing 2016-05-02 19:16:06 +02:00
Samy Pesse edd7d8aefe Update gitbook-asciidoc@1.1.4 2016-05-02 18:45:17 +02:00
Samy Pesse a8da4903f4 Update gitbook-asciidoc@1.1.3 to fix error on linux 2016-05-02 17:27:18 +02:00
Samy Pesse c8df5033f7 Update gitbook-asciidoc@1.1.2 2016-05-02 17:25:41 +02:00
Samy Pesse 412f418da3 Update gitbook-markdown@1.2.3 2016-05-02 17:15:54 +02:00
Johan Preynat 3782d21069 lib/output/ebook/onPage.js: Fix missing page's filepath at Modifiers.inlineAssets() call 2016-05-02 16:54:14 +02:00
Johan Preynat 82a29f4470 lib/models/languages.js: Use lang.ref 2016-05-02 16:37:47 +02:00
Johan Preynat 4a65326b24 Update jest@12.0.2 2016-05-02 16:37:24 +02:00
Johan Preynat d75d8729be lib/output/ebook/onFinish.js: Use lowercase summary.html filename from themes 2016-05-02 16:26:00 +02:00
Samy Pessé c87060454e Add model for plugin dependencies in the book.json 2016-05-02 16:24:20 +02:00
Samy Pessé bbba52c7ff Update gitbook-markdown and gitbook-asciidoc 2016-05-02 16:21:44 +02:00
Samy Pessé 33418bdb33 Document method in utils/command.js 2016-05-02 15:21:08 +02:00
Samy Pessé de47db2b66 Fix escaping of ebook-convert options 2016-05-02 15:09:10 +02:00
Johan Preynat fa6fcffbe1 Improve lib/plugins/__tests__/findInstalled.js 2016-05-02 15:06:02 +02:00
Samy Pessé 93d3f11891 Fix js error when using node < 4.x 2016-05-02 15:03:29 +02:00
Johan Preynat 59479f10fc Fix lib/plugins/__tests__/findInstalled.js 2016-05-02 14:46:54 +02:00
Samy Pessé 11e72253d4 Bump version to 3.0.0-pre.10 2016-05-02 14:40:54 +02:00
Samy Pessé 37fca9c88a Update theme-default@1.0.0-pre.9 2016-05-02 14:39:39 +02:00
Johan Preynat 6d67048e98 Use gitbook-plugin-search@2.2.1 2016-05-02 14:36:23 +02:00
Johan Preynat c516a5c1bd lib/fs/node.js: Use fresh-require@1.0.3 to read uncached configuration files 2016-05-02 14:34:34 +02:00
Johan Preynat 429431c13c lib/api/deprecate.js: Fix deprecateRenameMethod() 2016-05-02 14:05:11 +02:00
Samy Pessé b80dfd7b9a Add method contentLink as deprecated for plugin API 2016-05-02 13:50:51 +02:00
Samy Pessé 4c144b6ceb Fix Plugins.listAll to handle strings 2016-05-02 13:42:18 +02:00
Samy Pessé 718e32773a Add method removePlugin to ConfigModifier 2016-05-02 13:41:07 +02:00
Samy Pessé fb75d5d3ee Add this.gitbook.version 2016-05-02 13:41:07 +02:00
Samy Pessé 99bbcbebfc Fix modifiers for ebook format 2016-05-02 13:41:07 +02:00
Soreine 289e4b5dc1 Tweak Summary.toText doc 2016-05-02 13:38:51 +02:00
Johan Preynat a974dcdab6 lib/templating/postRender.js: Fix missing argument to engine.getBlock() 2016-05-02 13:32:27 +02:00
Samy Pessé 9a48620152 Document encodeGlobal api 2016-05-02 10:02:46 +02:00
Samy Pessé e444b3a402 Fix launch of livereload server 2016-05-02 09:59:44 +02:00
Samy Pessé fabf4f0c0a Enable livereload plugin when serving content 2016-05-02 09:56:32 +02:00
Samy Pessé d467888d6a Improve timing debug 2016-05-02 09:47:11 +02:00
Samy Pessé eb46311561 getArticleByPath is now a global function for template instead of a filter 2016-05-02 09:38:24 +02:00
Samy Pessé 63839bb0a7 Fix apply of async html modifiers 2016-05-02 09:29:49 +02:00
Samy Pessé 793fada0fb Add test for async template block 2016-05-02 09:19:57 +02:00
Samy Pessé 494ba78156 Complete api method template.applyBlock 2016-05-02 09:14:01 +02:00
Samy Pessé 9816b27ebf Update nunjucks@2.4.2 2016-05-02 09:06:59 +02:00
Samy Pessé 1c43b7891b Log resolved version when installing plugins 2016-05-02 09:02:36 +02:00
Samy Pessé 9d7739213e Remove should from git.js test 2016-05-02 08:57:18 +02:00
Samy Pessé 2ab5f6a305 Fix case of helper/resolveFileToUrl.js 2016-05-02 08:53:32 +02:00
Samy Pesse c73103969f Add signature for api template.applyBlock 2016-05-01 23:27:38 +02:00
Samy Pesse 6cbbdf5d5b Improve spacing when installing plugins 2016-05-01 22:58:25 +02:00
Samy Pesse 3a48435497 Cleanup folder before generating output 2016-05-01 19:35:50 +02:00
Samy Pesse 084925de8b Improve plugin loading to not fake module errors 2016-05-01 19:07:57 +02:00
Samy Pesse 5bac2fe695 Fix #1253: add instructions for generating ebook in docs 2016-05-01 16:15:19 +02:00
Samy Pesse 2c29fb241f Always log timing infos 2016-05-01 15:55:27 +02:00
Johan Preynat 77dc11bcc2 lib/cli/options.js: Return pure JS Array for CLI log options 2016-05-01 12:09:58 +02:00
Samy Pesse 5154622566 Add @soreine and @jpreynat to authors list 2016-04-30 23:50:47 +02:00
Samy Pesse c681cd286a Remove lodash dependency 2016-04-30 23:07:43 +02:00
Samy Pesse c1d53ec11f Switch parsers to a model 2016-04-30 22:06:16 +02:00
Samy Pesse 36b49c66c6 Merge branch 'fixes' 2016-04-30 20:15:08 +02:00
Samy Pesse 80b8e340da Fix add global env to templateEngine 2016-04-30 20:08:04 +02:00
Samy Pesse b51470db39 Add tests for conrefs loader 2016-04-30 20:06:43 +02:00
Samy Pesse a5f4596f8d Add tests for templateEngine 2016-04-30 19:00:35 +02:00
Johan Preynat 5f3c3760c0 lib/models/templateBlock.js: Fix convert blocks to pure JS array before passing to parser.parseUntilBlocks() 2016-04-30 17:05:30 +02:00
Johan Preynat 8b05885d57 lib/models/templateBlock.js: Use function for Ext() instead of var 2016-04-30 17:05:30 +02:00
Johan Preynat d9d7ab4ee5 lib/models/templateBlock.js: Add test file lib/models/__tests__/templateBlock.js 2016-04-30 17:05:30 +02:00
Samy Pesse 30bce5f9bb Add option "--timing" to mesure gitbook performances 2016-04-30 14:37:44 +02:00
Samy Pesse fbe0282738 Add tests for git utility 2016-04-30 12:30:56 +02:00
Samy Pesse 5bad08b9a0 Add this.generator as deprecated 2016-04-30 12:14:43 +02:00
Samy Pesse 310691bca2 Use areIdenticalPaths for Summary.getByPath 2016-04-29 23:43:01 +02:00
Samy Pesse 8f7967acd0 Log page causing error when a bug occurs during generation 2016-04-29 23:32:41 +02:00
Samy Pesse faf18476b1 Don't fail if page doesn't exist 2016-04-29 23:23:54 +02:00
Samy Pesse 9acccad23a Fix validation of configuration for plugins 2016-04-29 23:19:33 +02:00
Samy Pesse db1a0aedf8 Fix listing of plugins when contains "-" 2016-04-29 23:13:31 +02:00
Samy Pesse 01a26fda25 Fix linting error 2016-04-29 20:18:28 +02:00
Samy Pessé 0229272b0a Add api this.readFile and this.readFileAsString 2016-04-29 17:46:59 +02:00
Samy Pessé c817acff09 Add context for page templates 2016-04-29 17:40:35 +02:00
Samy Pessé a5eec81819 Fix context for page template 2016-04-29 17:34:33 +02:00
Samy Pessé 98f70879bd Complete ebook generation using ebook-convert 2016-04-29 16:50:50 +02:00
Samy Pessé ceb4627f4e Use API to create context for filters/blocks 2016-04-29 15:30:56 +02:00
Samy Pessé 8d9d4fe23f Merge remote-tracking branch 'origin/fixes' into fixes 2016-04-29 15:28:01 +02:00
Samy Pessé 730298110c Add ebook commands pdf/mobi/epub 2016-04-29 15:27:53 +02:00
Johan Preynat 8578f7d0d1 lib/api/encodePage.js: Fix deprecated page.progress assignment 2016-04-29 15:16:56 +02:00
Samy Pessé 07a5daefb0 Generate SUMMARY.html for ebook output 2016-04-29 15:09:11 +02:00
Samy Pessé 0ed524bce5 Correctly setup configuration for plugins in multilingual books 2016-04-29 15:08:54 +02:00
Johan Preynat 94199af7b2 Add location.areIdenticalPaths(p1,p2) to effectively compare two paths 2016-04-29 14:38:12 +02:00
Johan Preynat b8d7a00f58 Find installed gitbook-plugins in node_modules folder 2016-04-29 14:38:12 +02:00
Johan Preynat aca963befc Fix typo 2016-04-29 14:38:12 +02:00
Samy Pessé 3ea4e334bf It should generate index for languages 2016-04-29 13:04:54 +02:00
Samy Pessé 9955fa8c29 Use plugin assets from parent when language book 2016-04-29 12:46:31 +02:00
Samy Pessé fb37a6bcab Inherit plugins list from parent output when generating multilingual book 2016-04-29 11:31:54 +02:00
Samy Pessé 4f8bdbb518 Add tests for parsing a book with option "root" 2016-04-29 11:11:42 +02:00
Samy Pessé 73c190891d Fix parsing of multilingual books 2016-04-29 10:58:25 +02:00
Samy Pessé 93e701f471 Complete deprecated page.progress and this.navigation 2016-04-29 10:17:16 +02:00
Samy Pesse 68b9bf7e38 Encode navigation for compatibility with gitbook v2 2016-04-28 22:45:50 +02:00
Samy Pesse 826f90505c Add command "init" 2016-04-28 21:25:58 +02:00
Samy Pessé 7ae36f16c8 Add base for deprecated page.naviation 2016-04-28 15:45:25 +02:00
Samy Pessé 281f6d5535 Extend timeout for tests 2016-04-28 15:36:54 +02:00
Samy Pessé b4072a06c4 Add basic tests for website generator 2016-04-28 15:28:46 +02:00
Samy Pessé cb3ee423c9 Add basic tests for json generator 2016-04-28 15:20:25 +02:00
Samy Pessé 1360a744a7 Export more models 2016-04-28 15:02:24 +02:00
Samy Pessé f3058598dc Always generate a README.json as introduction of json output 2016-04-28 14:42:07 +02:00
Samy Pessé 94db0f6ec9 Fix typo in JSONUtils.encodeBook 2016-04-28 14:39:11 +02:00
Samy Pessé f08732095d Copy readme.json from main language for json output of multilingual 2016-04-28 14:37:19 +02:00
Samy Pessé f946650ddb Add "next" and "previous" to page JSON repr 2016-04-28 14:28:41 +02:00
Samy Pessé 14fd6bbdce Add tests for PathUtils 2016-04-28 14:16:15 +02:00
Samy Pessé 5059e8df34 Add modifiers to edit article and insert in summary 2016-04-28 14:12:21 +02:00
Samy Pessé 3d405a1ac5 Fix ref in summary when parsed without ref 2016-04-28 14:03:58 +02:00
Samy Pessé 78e00db018 Add tests for SummaryModifier.editPartTitle 2016-04-28 11:35:30 +02:00
Samy Pessé e62bc31e63 Add method setSummary to book 2016-04-28 11:17:33 +02:00
Samy Pessé 4763f1342c Add base modifiers for summary as SummaryModifier 2016-04-28 11:15:51 +02:00
Samy Pessé 9413e53b00 Add tests for parseIgnore 2016-04-28 10:37:52 +02:00
Samy Pessé 35d352bbeb Handle correctly ignored file in multilanguages book 2016-04-28 09:59:34 +02:00
Samy Pesse 8dd5e0c250 Add comments for todos 2016-04-27 22:42:09 +02:00
Samy Pesse 139e487998 Correctly resolve images starting with / 2016-04-27 22:36:59 +02:00
Samy Pesse 9bb0a3fbd3 Add tests for svg inliner 2016-04-27 22:18:33 +02:00
Samy Pessé 9a1a6f2625 Document cli/server.js 2016-04-27 18:02:49 +02:00
Samy Pessé e781d6d2b1 Add test for assets inliner 2016-04-27 17:58:35 +02:00
Samy Pessé 5b342b50ce Add language property to book for multilingual 2016-04-27 16:41:17 +02:00
Samy Pessé 869822d180 Use orderedMap for language books 2016-04-27 16:26:16 +02:00
Samy Pessé e50422bce5 Use code TemplateBlock to highlight code 2016-04-27 16:22:17 +02:00
Samy Pessé 999882e723 Add test for highlightCode html modifier 2016-04-27 15:42:55 +02:00
Samy Pessé d68dc82159 Merge remote-tracking branch 'origin/fixes' into fixes 2016-04-27 15:12:25 +02:00
Samy Pessé cc9fdd0032 Generate multilingual books 2016-04-27 15:12:18 +02:00
Soreine 503cc4a729 Check for stdout, for browser compatibility 2016-04-27 14:52:26 +02:00
Samy Pessé d585269228 Correctly ignore summary and templates from assets 2016-04-27 14:37:02 +02:00
Samy Pessé 50f535b910 Fix plugin's config validation 2016-04-27 14:31:45 +02:00
Samy Pessé 978bdd149e Add browser specific index.js 2016-04-27 14:18:23 +02:00
Samy Pessé a9d558e891 Add method ".toText" on glossary and summary 2016-04-27 14:11:45 +02:00
Samy Pessé 8291eb58df Complete api method output.toURL 2016-04-27 13:53:32 +02:00
Samy Pessé 7298dec353 Add plugins.resources for template context 2016-04-27 13:47:12 +02:00
Samy Pessé 7574b01863 Merge remote-tracking branch 'origin/fixes' into fixes 2016-04-27 13:13:38 +02:00
Samy Pessé 98e739a12b Add JS context for template 2016-04-27 13:13:27 +02:00
Soreine 262ce03e46 Exposes FS in public interface 2016-04-27 13:12:51 +02:00
Samy Pessé 1906691a35 Fix order of plugins assets copy 2016-04-27 12:48:40 +02:00
Samy Pessé 4ca23b84a4 Fix filters for theme templates 2016-04-27 12:08:00 +02:00
Samy Pessé b05eb8440b Don;t log deprecation warning for own logic 2016-04-27 10:38:54 +02:00
Samy Pessé f4de7748f5 Complete generator name in plugins API 2016-04-27 09:59:09 +02:00
Samy Pessé 93c26a839b Add this.book and this.options as deprecated 2016-04-27 09:42:40 +02:00
Samy Pesse 878e7441f2 Stop running test on node 0.12 for windows 2016-04-26 23:19:19 +02:00
Samy Pesse 27d387e3e1 Load i18n translations 2016-04-26 21:48:56 +02:00
Samy Pesse 7b7e4f60bc Correctly set log level 2016-04-26 20:49:20 +02:00
Samy Pesse 8bac111acf Add base for plugin JS api 2016-04-26 17:02:59 +02:00
Samy Pesse 7a46d3b497 Add command "serve" 2016-04-26 15:51:52 +02:00
Samy Pesse 357aa92e31 Add default filters to theme templating engine 2016-04-26 15:50:11 +02:00
Samy Pesse fa17f16716 Add base for commands
Improve plugins installation
2016-04-26 15:23:28 +02:00
Samy Pesse ce99c8c706 Add more tests for summary 2016-04-26 14:25:54 +02:00
Samy Pesse be828cf004 Add duration to final log for generation 2016-04-26 14:12:26 +02:00
Samy Pesse 1057bb15d3 Complete method getByPath of Summary 2016-04-26 13:03:53 +02:00
Samy Pesse ea5e8891a8 Add test for readme parsing 2016-04-26 11:17:47 +02:00
Samy Pesse 57d83f082a Add basic tests for summary parsing 2016-04-26 11:15:50 +02:00
Samy Pesse 8a3f80ba82 Add test for parsing glossary 2016-04-26 11:00:36 +02:00
Samy Pesse 374ebd6f7a Add mock fs and tests for it 2016-04-26 10:23:37 +02:00
Samy Pesse ab2ebefe2b Copy resources from plugins to output folder 2016-04-25 22:30:41 +02:00
Samy Pesse c34a9f6213 Copy assets from themes to output folder 2016-04-25 21:13:17 +02:00
Samy Pessé f5df41c75f Enable extension "do" 2016-04-25 15:44:20 +02:00
Samy Pessé 0c2dc06cb7 Fix resolution of theme's templates 2016-04-25 15:36:18 +02:00
Samy Pessé 4552ccd7d7 Add loader for themes 2016-04-25 14:44:49 +02:00
Samy Pessé 525bcd8295 Disable travis tests on 0.12 2016-04-25 14:37:49 +02:00
Samy Pessé ac2ad8d8b5 Add conrefs loader for templating 2016-04-25 14:29:51 +02:00
Samy Pessé 14ce495e9e Add more tests for LocationUtils 2016-04-25 14:15:28 +02:00
Samy Pessé 4aed2cf652 Add post processing for templates 2016-04-25 13:52:56 +02:00
Samy Pessé 244fb0ca28 Replace shortcuts in templating 2016-04-25 12:24:59 +02:00
Samy Pessé f6e123f1ed Add base rendering for template 2016-04-25 11:58:55 +02:00
Samy Pessé e1e4e7f011 Add tests for link resolution 2016-04-25 11:28:21 +02:00
Samy Pessé 814b6ff489 Complete json format 2016-04-25 11:05:06 +02:00
Samy Pessé 3aa17ab1f4 Convert markdown to html for pages 2016-04-25 10:58:10 +02:00
Samy Pessé 4646ed8f4a Fix plugins loading 2016-04-25 10:32:44 +02:00
Samy Pesse 3f5c8acb3c Add fiel for testing resolveLinks 2016-04-24 23:45:47 +02:00
Samy Pesse 93b257314a Add default filters and blocks 2016-04-24 19:12:03 +02:00
Samy Pesse 19a1db8cb4 Add base structure for templating 2016-04-24 18:52:37 +02:00
Samy Pesse c3275a4aa9 Parse multilingual books 2016-04-24 00:15:38 +02:00
Samy Pesse 927185d95c add test for modifier "annotateText" 2016-04-23 23:58:08 +02:00
Samy Pesse a3df6c6f0c Add assets inliner modifier for HTML 2016-04-23 21:10:53 +02:00
Samy Pesse e1fa977b5b Add json encoding utils 2016-04-23 17:10:16 +02:00
Samy Pesse ce95f316b9 Add method to be article by level 2016-04-23 11:22:20 +02:00
Samy Pessé a162af5a75 Add base for calling hook on page/global 2016-04-22 22:58:37 +02:00
Samy Pessé fb0ea4610d Add base utils for generation 2016-04-22 22:21:34 +02:00
Samy Pessé 4f86a9978b Add test for config 2016-04-22 11:56:34 +02:00
Samy Pessé cc78b880ce Add utils to install plugins 2016-04-22 11:33:25 +02:00
Samy Pessé 4336fdb241 Base 2016-04-22 11:00:21 +02:00
Samy Pessé 87db7cf1d4 Merge remote-tracking branch 'origin/master' 2016-04-20 17:26:49 +02:00
Samy Pessé 09ff802c7e When using structure.readme, fix link to readme to resolve to index.html 2016-04-20 17:26:13 +02:00
Johan Preynat 71b59b1950 Correctly detect promises from block.process in TemplateEngine.applyBlock() 2016-04-20 15:59:53 +02:00
Samy Pessé a3300ad9c6 Comment template code 2016-04-20 15:02:39 +02:00
Samy Pessé 8f40d4affa Fix reliability of template.self in macro import 2016-04-20 10:03:04 +02:00
Samy Pessé 560f3fcccb Update plugin-search@2.2.0 2016-04-20 09:42:11 +02:00
Samy Pesse ecaadf63c5 Correctly ignore structure file (only in root folder) 2016-04-20 00:06:45 +02:00
Samy Pesse d490a6e0b4 Correctly load plugins when using "root" option 2016-04-19 17:40:01 +02:00
Samy Pesse 53ced205b8 Ensure "template" is always set when rendering a template 2016-04-19 15:46:37 +02:00
Samy Pesse 61c4983949 Fix template context should be global in nunjucks for theming
Fix pdf_footer and pdf_header template not rendering
2016-04-19 15:33:35 +02:00
Samy Pesse fb862f466f Bump version to 3.0.0-pre.9 2016-04-19 14:18:01 +02:00
Samy Pesse 8d51300386 Bump version to 2.1.0 2016-04-19 14:16:51 +02:00
Samy Pesse c4b413563d Update theme-default@1.0.0-pre.8 2016-04-19 14:14:59 +02:00
Samy Pesse 8f852971f9 Cleanup book.js 2016-04-19 14:12:01 +02:00
Johan Preynat 3c54c89464 Use page.content if modified or fallback to page.sections in plugins hooks 2016-04-19 10:02:41 +02:00
Samy Pessé d07aa13822 Add tests for page hook 2016-04-18 17:14:55 +02:00
Samy Pessé c218f7d0e3 Correctly set template.self in all cases 2016-04-18 16:56:50 +02:00
Samy Pessé 72ad872e90 Fix js error when using output without root 2016-04-18 14:26:24 +02:00
Samy Pessé 854f244d22 Fix infinite loop on this.options 2016-04-18 14:17:51 +02:00
Samy Pessé 9acbb223db Add generator and output property as deprecated in config 2016-04-18 14:16:22 +02:00
Johan Preynat c59fc687db Ensure plugins compatibility with page.sections 2016-04-18 14:10:19 +02:00
Samy Pessé 0791b917f1 Add context for readme 2016-04-18 12:49:31 +02:00
Samy Pessé ba182ce8e4 Fix tests for copy of website theme 2016-04-15 11:33:38 +02:00
Samy Pessé 663291c515 Output context as global for themes 2016-04-15 11:33:24 +02:00
Samy Pessé 99253eecea Bump version to 3.0.0-pre.8 2016-04-14 15:02:49 +02:00
Samy Pessé 916a02cb36 Update plugins search@2.0.0 and theme-default@1.0.0-pre.7 2016-04-14 15:00:25 +02:00
Samy Pessé 114c4abcd1 Add method for theme: template.getJSContext 2016-04-14 14:51:42 +02:00
Samy Pessé e1e3d11932 Add lunr as default plugin 2016-04-14 14:51:22 +02:00
Samy Pessé df46217c78 Improve description extraction and adapt unit tests 2016-04-14 10:07:51 +02:00
Samy Pessé d18cc92396 Pass template to block's context 2016-04-14 09:51:56 +02:00
Samy Pessé 8f63b4b790 Remove unused variable 2016-04-13 17:32:01 +02:00
Samy Pessé ce7de93936 Improve block parsing to handle args/kwargs on subblocks 2016-04-13 16:22:53 +02:00
Johan Preynat ef2a24fe6f Add trailing dots to page.description when longer than 300 characters 2016-04-13 16:00:16 +02:00
Samy Pessé 038cd0155b Add filter "getArticleByPath" for theme templates 2016-04-13 11:48:29 +02:00
Samy Pessé fb430ac1ed Increase size of extracted description 2016-04-13 10:55:32 +02:00
Johan Preynat 53cfa3703b Update page context for plugins hooks 2016-04-13 09:33:26 +02:00
Johan Preynat 7ed9e8d145 Update plugin-fontsettings@1.0.3 / plugin-sharing@1.0.2 2016-04-12 17:05:32 +02:00
Samy Pessé 93e0b0cbea Watch _layouts when serving 2016-04-12 13:53:58 +02:00
Samy Pessé 0118bd3168 Cleanup code to list plugins 2016-04-11 12:29:53 +02:00
Samy Pessé 07d5245a5f Bump version to 3.0.0-pre.7 2016-04-11 09:59:58 +02:00
Samy Pessé 6b844213ec Update theme-default@1.0.0-pre.6 2016-04-11 09:59:18 +02:00
Samy Pessé dabdeb8a80 Fix #1195: fix external images and fix test for it 2016-04-11 09:56:13 +02:00
Samy Pessé ecd56315e6 Merge pull request #1220 from seanlane/patch-1
Fix minor spelling error.
2016-04-11 09:34:15 +02:00
Sean Lane 15b9e839e9 Fix minor spelling error. 2016-04-10 14:31:49 -06:00
Samy Pesse d7f7dcd47b Switch to ignore@3.1.1 2016-04-10 22:28:29 +02:00
Samy Pessé b1981e09b8 Fix generator deprecation warning being shown for each call 2016-04-10 18:20:51 +02:00
Samy Pessé c9edd0a795 Load plugins from book parent in multilingual book 2016-04-09 12:16:17 +02:00
Samy Pesse d83e0716bb Add test for main language in json output 2016-04-08 23:58:42 +02:00
Samy Pessé aed38afff3 Bump version to 3.0.0-pre.6 2016-04-08 16:35:35 +02:00
Samy Pessé 1df522e6ec Update node modules 2016-04-08 16:34:53 +02:00
Samy Pessé bd4ef2354e Merge pull request #1216 from GitbookIO/fix/ignore
Fix #1215: fix ignore rules not working
2016-04-08 15:48:33 +02:00
Samy Pessé 32a49a6969 Inherit ignore rules of output from book 2016-04-08 15:46:46 +02:00
Samy Pessé 21806687e6 Add base tests for fiailng ignore 2016-04-08 15:23:46 +02:00
Samy Pessé 7ade91a091 Disable ssh test when ci 2016-04-08 15:11:54 +02:00
Samy Pessé 4e41f96d89 Add "languages" property to all json files for multilingual book 2016-04-08 14:42:37 +02:00
Johan Preynat 325c32b4c9 Update gitbook-plugin-highlight@2.0.2 2016-04-06 10:50:03 +02:00
Samy Pessé 7d6606a8e5 Merge pull request #1202 from qjcg/patch-1
Proofreading
2016-04-06 09:19:37 +02:00
Samy Pessé c887d520eb Update github-slugid@1.0.1 2016-04-04 11:05:42 +02:00
John Gosset e6050fc558 Proofreading
A few proofreading changes with clarity in mind.
2016-03-31 13:23:15 -04:00
Samy Pessé cae4853334 Merge pull request #1194 from jashaj/master
Fix for incorrect splitting the plugin name/version
2016-03-24 21:50:26 +01:00
Jasha Joachimsthal 9ef2bebfe9 Misinterpreted second argument of String.prototype.split. Remainder of the array must be joined instead. 2016-03-24 21:21:23 +01:00
Samy Pessé 53294af810 Merge pull request #1192 from jashaj/master
Limit splitting plugin name for git with authentication
2016-03-24 16:54:29 +01:00
Jasha Joachimsthal 7d21c815a5 Limit splitting plugin name for git+ssh 2016-03-24 15:23:28 +01:00
Samy Pessé e07719a86c Fix template search paths to use plugins listed first 2016-03-22 10:59:25 +01:00
Soreine fa5bee6ca3 Import paragraph about Escaping nunchuck from old doc. 2016-03-21 15:07:22 +01:00
Samy Pessé 3a43ef2c3f Add doc for inline rendering 2016-03-18 11:55:24 +01:00
Samy Pessé 55deab03cd Add test for blocks markdown/asciidoc/markup 2016-03-18 11:26:31 +01:00
Samy Pessé edda84fefd Add block "markdown", "asciidoc" and "markup" 2016-03-18 11:20:57 +01:00
Samy Pessé 10b191f631 Use stabler block anchor in template output 2016-03-18 11:17:26 +01:00
Samy Pessé d5a575c5e0 Fix context for template block/filters 2016-03-18 11:14:04 +01:00
Samy Pessé 82d8c3e2d9 Add method renderInline and renderBlock to book 2016-03-18 11:10:58 +01:00
Samy Pessé bc0c8dbcf3 Update nunjucks@2.4.1 2016-03-18 11:07:21 +01:00
Samy Pessé de0245ff40 Update nunjucks@2.4.1 2016-03-18 11:03:22 +01:00
Samy Pessé 7f56ff0281 Fix doc for paragraph 2016-03-17 11:53:28 +01:00
Samy Pessé 2cfbf5411c Add question about text direction in the faq 2016-03-17 10:41:34 +01:00
Samy Pessé f70da1c82a Fix links in readme 2016-03-15 12:40:40 +01:00
Samy Pessé 0b4db49f57 Merge pull request #1181 from GitbookIO/feature/allplugins_tpl
All plugins can extend templates/theme
2016-03-15 12:37:52 +01:00
Samy Pessé fbffd54aa2 Load all plugins, even dependencies of plugins 2016-03-15 12:37:25 +01:00
Samy Pessé 4d19a33af2 Add method registry.list to list all plugins for a book 2016-03-15 11:43:37 +01:00
Samy Pessé 9d67a0e656 Add theme for extending theme from book 2016-03-11 11:21:47 +01:00
Samy Pessé 093005bfd9 Fix FSLoader for relative paths 2016-03-11 10:51:36 +01:00
Samy Pessé 514883f0bd Create new loader for nunjucks to avoid infinite loop 2016-03-11 10:43:37 +01:00
Samy Pessé 27681aad98 Update nunjucks@2.4.0 2016-03-11 10:20:26 +01:00
Samy Pessé 0d367da776 Use complete list of plugins for templates resolution / i18n / assets 2016-03-09 16:45:27 +01:00
Samy Pessé c499a8a13a Add back gitbook.generator property as deprecated 2016-03-09 15:50:38 +01:00
Samy Pessé 64a6de751d Change documentation's title 2016-03-09 15:22:04 +01:00
Samy Pessé d39f0d6ff0 Add "output" variables for templating 2016-03-09 15:18:57 +01:00
Samy Pessé bdbfbf7300 Merge doc for ebook and cover 2016-03-09 11:10:52 +01:00
Samy Pessé ccac3d2c3c Publish compleet reference of markdown syntax for gitbook 2016-03-08 17:26:36 +01:00
Samy Pessé f07278490f Add question about rtl to faq 2016-03-08 09:26:46 +01:00
Samy Pessé f1880fc53c Fix incorrect link in themes doc 2016-03-07 15:20:27 +01:00
Samy Pessé e8fe4a2dea Improve docs for plugins 2016-03-07 15:15:41 +01:00
Samy Pessé 1d79507665 Add book to templating context 2016-03-07 14:52:30 +01:00
Samy Pessé f05dc67887 Improve version suggestion 2016-03-07 14:06:58 +01:00
318 changed files with 11890 additions and 7065 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
docs/**/*
test/node_modules/**/*
_book/**/*
node_modules/**/*
test/**/*
+3 -2
View File
@@ -12,8 +12,9 @@
},
"env": {
"node": true,
"mocha": true,
"browser": true
"browser": true,
"jest": true,
"jasmine": true
},
"extends": "eslint:recommended"
}
-1
View File
@@ -3,7 +3,6 @@ language: node_js
node_js:
- "stable"
- "4.1"
- "0.12"
before_install:
- npm install svgexport -g
after_success:
+4 -3
View File
@@ -7,9 +7,10 @@ Names below are ordered by first contribution.
Author
------
- Samy Pessé <samypesse@gmail.com> (@SamyPesse)
- Aaron O'Mullan <aaron.omullan@gmail.com> (@AaronO)
- Samy Pessé <samy@gitbook.com> (@SamyPesse)
- Aaron O'Mullan <aaron@gitbook.com> (@AaronO)
- Johan Preynat <johan@gitbook.com> (@jpreynat)
- Nicolas Gaborit <nicolas@gitbook.com> (@soreine)
Contributors
------------
+7
View File
@@ -15,6 +15,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Multi-lingual books share assets folder
- YAML front matter is parsed and can extend page's properties
- Fix `uk` translation
- Fix heading ID including dashes
- Fix error in code highlighting for unknown languages
- Fix data-uri images being handled as external images
- Accept SSH url as plugin version
- Add templating blocks `markdown`, `asciidoc` and `markup`
- Better search experience
- Better default theme, more responsive and cleaner
## 2.6.7
- Fix bug with filenames including spaces
+9 -9
View File
@@ -12,7 +12,7 @@ You can publish and host books easily online using [gitbook.com](https://www.git
Check out the [GitBook Community Slack Channel](https://slack.gitbook.com), 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/).
Complete documentation is available at [toolchain.gitbook.com](http://toolchain.gitbook.com/).
![Image](https://raw.github.com/GitbookIO/gitbook/master/preview.png)
@@ -32,14 +32,14 @@ We're always happy to help out with your books or any other questions you might
## Features
* [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/format/plugins.html)
* Write using [Markdown](http://toolchain.gitbook.com/syntax/markdown.html) or [AsciiDoc](http://toolchain.gitbook.com/syntax/asciidoc.html)
* Output as a website or [ebook (pdf, epub, mobi)](http://toolchain.gitbook.com/ebook.html)
* [Multi-Languages](http://toolchain.gitbook.com/languages.html)
* [Lexicon / Glossary](http://toolchain.gitbook.com/lexicon.html)
* [Cover](http://toolchain.gitbook.com/ebook.html)
* [Variables and Templating](http://toolchain.gitbook.com/templating/)
* [Content References](http://toolchain.gitbook.com/templating/conrefs.html)
* [Plugins](http://toolchain.gitbook.com/plugins/)
* [Beautiful default theme](https://github.com/GitbookIO/theme-default)
## Publish your book
+2 -2
View File
@@ -5,8 +5,8 @@ init:
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.12"
- nodejs_version: "4.1"
- nodejs_version: "5"
- nodejs_version: "4"
# Install scripts. (runs after repo cloning)
install:
+3 -4
View File
@@ -3,14 +3,13 @@ var pkg = require('./package.json');
module.exports = {
// Documentation for GitBook is stored under "docs"
root: './docs',
title: 'GitBook Documentation',
title: 'GitBook Toolchain Documentation',
// Enforce use of GitBook v3
gitbook: pkg.version,
gitbook: '>=3.0.0-pre.0',
// Use the "official" theme
plugins: ['theme-official', 'sitemap'],
theme: 'official',
variables: {
version: pkg.version
@@ -18,7 +17,7 @@ module.exports = {
pluginsConfig: {
sitemap: {
hostname: 'https://docs.gitbook.com'
hostname: 'https://toolchain.gitbook.com'
}
}
};
+1 -1
View File
@@ -1,4 +1,4 @@
# GitBook Format Documentation
# GitBook Toolchain Documentation
This document aims to be a comprehensive guide to GitBook. It contains the full documentation for version **{{ book.version }}**. Help for GitBook.com specific questions can be found at [help.gitbook.com](https://help.gitbook.com).
+18 -2
View File
@@ -12,16 +12,32 @@
* [Configuration](config.md)
* [Glossary](lexicon.md)
* [Multi-Lingual](languages.md)
* [AsciiDoc](asciidoc.md)
* [Markdown](syntax/markdown.md)
* [Headings](syntax/markdown.md#headings)
* [Paragraphs](syntax/markdown.md#paragraphs)
* [Lists](syntax/markdown.md#lists)
* [Links](syntax/markdown.md#links)
* [Images](syntax/markdown.md#images)
* [Blockquotes](syntax/markdown.md#blockquotes)
* [Tables](syntax/markdown.md#tables)
* [Code](syntax/markdown.md#code)
* [HTML](syntax/markdown.md#html)
* [Footnotes](syntax/markdown.md#footnotes)
* [AsciiDoc](syntax/asciidoc.md)
* [eBook and PDF](ebook.md)
### Customization
* [Cover](cover.md)
* [Templating](templating/README.md)
* [Content References](templating/conrefs.md)
* [Variables](templating/variables.md)
* [Builtin](templating/builtin.md)
* [Plugins](plugins/README.md)
* [Create a plugin](plugins/create.md)
* [Hooks](plugins/hooks.md)
* [Blocks](plugins/blocks.md)
* [Filters](plugins/filters.md)
* [API & Context](plugins/api.md)
* [Test your plugin](plugins/testing.md)
* [Theming](themes/README.md)
+1 -1
View File
@@ -1,4 +1,4 @@
{% extends template.theme %}
{% extends template.self %}
{% block header_nav %}
<a href="https://github.com/GitbookIO/gitbook/blob/master/docs/{{ file.path }}" target="_blank" class="btn btn-link pull-right hidden-xs">
-21
View File
@@ -1,21 +0,0 @@
# Cover
Covers are used for all the ebook formats, and to make books more elegant on GitBook.com.
To provide a cover, place a **cover.jpg** file at the root directory of your book. Adding a **cover_small.jpg** will specify a smaller version of the cover. The cover should be a **JPEG** file.
### Best Sizes
| | Big | Small |
|:-------------------:|:-----------:|:-----------------:|
| **File** | `cover.jpg` | `cover_small.jpg` |
| **Size(in pixels)** | 1800x2360 | 200x262 |
### Guidelines
A good cover respects the following guidelines:
* No border
* Clearly visible book title
* Any important text should be visible in the small version
+40
View File
@@ -0,0 +1,40 @@
# Generating eBooks and PDFs
GitBook can generates a website, but can also output content as ebook (ePub, Mobi, PDF).
```
# Generate a PDF file
$ gitbook pdf ./ ./mybook.pdf
# Generate an ePub file
$ gitbook epub ./ ./mybook.epub
# Generate a Mobi file
$ gitbook mobi ./ ./mybook.mobi
```
### Installing ebook-convert
`ebook-convert` is required to generate ebooks (epub, mobi, pdf).
##### OS X
Download the [Calibre application](https://calibre-ebook.com/download). After moving the `calibre.app` to your Applications folder create a symbolic link to the ebook-convert tool:
```
$ sudo ln -s ~/Applications/calibre.app/Contents/MacOS/ebook-convert /usr/bin
```
You can replace `/usr/bin` with any directory that is in your $PATH.
### Cover
Covers are used for all the ebook formats. It's an important part of an ebook brandline.
A good cover should respect the following guidelines:
* Size of 1800x2360 (pixels)
* No border
* Clearly visible book title
* Any important text should be visible in the small version
+22
View File
@@ -1,5 +1,9 @@
# GitBook FAQ
This page gathers common questions and answers concerning the GitBook format and toolchain.
Questions about GitBook.com and the Editor are gather into the [help.gitbook.com's FAQ](http://help.gitbook.com/faq.html).
#### How can I host/publish my book?
Books can easily be published and hosted on [GitBook.com](https://www.gitbook.com). But GitBook output can be hosted on any static file hosting solution.
@@ -10,6 +14,20 @@ Any text editor should work! But we advise using the [GitBook Editor](https://ww
---
#### Does GitBook supports RTL/bi-directional text ?
The GitBook format supports right to left, and bi-directional writing. To enable it, you either need to specify a language (ex: `ar`), or force GitBook to use RTL in your `book.json`:
``` json
{
"language": "ar",
"direction": "rtl"
}
```
With version 3.0 of GitBook, it's automatically detected according to the content.
_Note that, while the output book will indeed respect RTL, the Editor doesn't support RTL writing yet_.
#### Should I use an `.html` or `.md` extensions in my links?
You should always use paths and the `.md` extensions when linking to your files, GitBook will automatically replace these paths by the appropriate link when the pointing file is referenced in the Table of Contents.
@@ -18,6 +36,10 @@ You should always use paths and the `.md` extensions when linking to your files,
Yes, GitBooks can be created in [sub-directories](structure.md#subdirectory). GitBook.com and the CLI also looks by default in a serie of [folders](structure.md).
#### Does GitBook supports RTL languages?
Yes, GitBook automatically detect the direction in your pages (`rtl` or `ltr`) and adjust the layout accordingly. The direction can also be specified globally in the [book.json](config.md).
---
#### Does GitBook support Math equations?
+26 -2
View File
@@ -23,7 +23,27 @@ Adding a nested list to a parent chapter will create subchapters.
Each chapter has a dedicated page (`part#/README.md`) and is split into subchapters.
##### Example with subchapters split into parts
##### Anchors
Chapters in the Table of Contents can be pointing to specific part of a file using anchor.
```markdown
# Summary
### Part I
* [Part I](part1/README.md)
* [Writing is nice](part1/README.md#writing)
* [GitBook is nice](part1/README.md#gitbook)
* [Part II](part2/README.md)
* [We love feedback](part2/README.md#feedback)
* [Better tools for authors](part2/README.md#tools)
```
##### Parts
The Table of Contents can be divided into parts separated by headings or horizontal lines:
```markdown
# Summary
@@ -37,9 +57,13 @@ Each chapter has a dedicated page (`part#/README.md`) and is split into subchapt
* [We love feedback](part2/feedback_please.md)
* [Better tools for authors](part2/better_tools.md)
----
* [Last part without title](part3/title.md)
```
Here, parts are just groups of chapters and do not have dedicated pages, but will show in the navigation for example.
Parts are just groups of chapters and do not have dedicated pages, but according to the theme, it will show in the navigation.
### Pages
+97
View File
@@ -0,0 +1,97 @@
# Context and APIs
GitBooks provides different APIs and contexts to plugins. These APIs can vary according to the GitBook version being used, your plugin should specify the `engines.gitbook` field in `package.json` accordingly.
#### Book instance
The `Book` class is the central point of GitBook, it centralize all access read methods. This class is defined in [book.js](https://github.com/GitbookIO/gitbook/blob/master/lib/book.js).
```js
// Read configuration from book.json
var value = book.config.get('title', 'Default Value');
// Resolve a filename to an absolute path
var filepath = book.resolve('README.md');
// Render an inline markup string
book.renderInline('markdown', 'This is **Markdown**')
.then(function(str) { ... })
// Render a markup string (block mode)
book.renderBlock('markdown', '* This is **Markdown**')
.then(function(str) { ... })
```
#### Output instance
The `Output` class represent the output/write process.
```js
// Return root folder for the output
var root = output.root();
// Resolve a file in the output folder
var filepath = output.resolve('myimage.png');
// Convert a filename to an URL (returns a path to an html file)
var fileurl = output.toURL('mychapter/README.md');
// Write a file in the output folder
output.write('hello.txt', 'Hello World')
.then(function() { ... });
// Copy a file to the output folder
output.copyFile('./myfile.jpg', 'cover.jpg')
.then(function() { ... });
// Verify that a file exists
output.hasFile('hello.txt')
.then(function(exists) { ... });
```
#### Page instance
A page instance represent the current parsed page.
```js
// Title of the page (from SUMMARY)
page.title
// Content of the page (Markdown/Asciidoc/HTML according to the stage)
page.content
// Relative path in the book
page.path
// Absolute path to the file
page.rawPath
// Type of parser used for this file
page.type ('markdown' or 'asciidoc')
```
#### Context for Blocks and Filters
Blocks and filters have access to the same context, this context is bind to the template engine execution:
```js
{
// Current templating syntax
"ctx": {
// For example, after a {% set message = "hello" %}
"message": "hello"
},
// Book instance
"book" <Book>,
// Output instance
"output": <Output>
}
```
For example a filter or block function can access the current book using: `this.book`.
#### Context for Hooks
Hooks only have access to the `<Book>` instance using `this.book`.
+62
View File
@@ -0,0 +1,62 @@
# Extend Blocks
Extending templating blocks is the best way to provide extra functionalities to authors.
The most common usage is to process the content within some tags at runtime. It's like [filters](./filters.md), but on steroids because you aren't confined to a single expression.
### Defining a new block
Blocks are defined by the plugin, blocks is a map of name associated with a block descriptor. The block descriptor needs to contain at least a `process` method.
```js
module.exports = {
blocks: {
tag1: {
process: function(block) {
return "Hello "+block.body+", How are you?";
}
}
}
};
```
The `process` should return the html content that will replace the tag. Refer to [Context and APIs](./api.md) to learn more about `this` and GitBook API.
### Handling block arguments
Arguments can be passed to blocks:
```
{% tag1 "argument 1", "argument 2", name="Test" %}
This is the body of the block.
{% endtag1 %}
```
And arguments are easily accessible in the `process` method:
```js
module.exports = {
blocks: {
tag1: {
process: function(block) {
// block.args equals ["argument 1", "argument 2"]
// block.kwargs equals { "name": "Test" }
}
}
}
};
```
### Handling sub-blocks
A defined block can be parsed into different sub-blocks, for example let's consider the source:
```
{% myTag %}
Main body
{% subblock1 %}
Body of sub-block 1
{% subblock 2 %}
Body of sub-block 1
{% endmyTag %}
```
+12 -1
View File
@@ -8,13 +8,24 @@ A GitBook plugin is a node package published on NPM that follow a defined conven
The `package.json` is a manifest format for describing **Node.js modules**. GitBook plugins are built on top of Node modules. It declares dependencies, version, ownership, and other information required to run a plugin in GitBook. This document describes the schema in detail.
```
A plugin manifest `package.json` can also contain details about the required configuration. The configuration schema is defined in the `gitbook` field of the `package.json` (This field follow the [JSON-Schema](http://json-schema.org) guidelines):
```js
{
"name": "gitbook-plugin-mytest",
"version": "0.0.1",
"description": "This is my first GitBook plugin",
"engines": {
"gitbook": ">1.x.x"
},
"gitbook": {
"properties": {
"myConfigKey": {
"type": "string",
"default": "it's the default value",
"description": "It defines my awesome config!"
}
}
}
}
```
+57
View File
@@ -0,0 +1,57 @@
# Extend Filters
Filters are essentially functions that can be applied to variables. They are called with a pipe operator (`|`) and can take arguments.
```
{{ foo | title }}
{{ foo | join(",") }}
{{ foo | replace("foo", "bar") | capitalize }}
```
### Defining a new filter
Plugins can extend filters by defining custom functions in their entry point under the `filters` scope.
A filter function takes as first argument the content to filter, and should return the new content.
Refer to [Context and APIs](./api.md) to learn more about `this` and GitBook API.
```js
module.exports = {
filters: {
hello: function(name) {
return 'Hello '+name;
}
}
};
```
The filter `hello` can then be used in the book:
```
{{ "Aaron"|hello }}, how are you?
```
### Handling block arguments
Arguments can be passed to filters:
```
Hello {{ "Samy"|fullName("Pesse", man=true}} }}
```
Arguments are passed to the function, named-arguments are passed as a last argument (object).
```js
module.exports = {
filters: {
fullName: function(firstName, lastName, kwargs) {
var name = firstName + ' ' + lastName;
if (kwargs.man) name = "Mr" + name;
else name = "Mrs" + name;
return name;
}
}
};
```
+90
View File
@@ -0,0 +1,90 @@
# Hooks
Hooks is a method of augmenting or altering the behavior of the process, with custom callbacks.
### List of hooks
### Relative to the global pipeline
| Name | Description | Arguments |
| ---- | ----------- | --------- |
| `init` | Called after parsing the book, before generating output and pages. | None |
| `finish:before` | Called after generating the pages, before copying assets, cover, ... | None |
| `finish` | Called after everything else. | None |
### Relative to the page pipeline
> It is recommended using [templating](./templating.md) to extend page parsing.
| Name | Description | Arguments |
| ---- | ----------- | --------- |
| `page:before` | Called before running the templating engine on the page | Page Object |
| `page` | Called before outputting and indexing the page. | Page Object |
##### Page Object
```js
{
// Parser named
"type": "markdown",
// File Path relative to book root
"path": "page.md",
// Absolute file path
"rawpath": "/usr/...",
// Title of the page in the SUMMARY
"title": "",
// Content of the page
// Markdown/Asciidoc in "page:before"
// HTML in "page"
"content": "# Hello"
}
```
##### Example to add a title
In the `page:before` hook, `page.content` is the markdown/asciidoc content.
```js
{
"page:before": function(page) {
page.content = "# Title\n" +page.content;
return page;
}
}
```
##### Example to replace some html
In the `page` hook, `page.content` is the HTML generated from the markdown/asciidoc conversion.
```js
{
"page": function(page) {
page.content = page.content.replace("<b>", "<strong>")
.replace("</b>", "</strong>");
return page;
}
}
```
### Asynchronous Operations
Hooks callbacks can be asynchronous and return promises.
Example:
```js
{
"init": function() {
return writeSomeFile()
.then(function() {
return writeAnotherFile();
});
}
}
```
+223
View File
@@ -0,0 +1,223 @@
# Markdown
Most of the examples from this documentation are in Markdown. Markdown is default parser for GitBook, but one can also opt for the [AsciiDoc syntax](asciidoc.md).
Here’s an overview of Markdown syntax that you can use with GitBook (same as GitHub with some additions).
### Headings
To create a heading, add one to six `#` symbols before your heading text. The number of # you use will determine the size of the heading.
```markdown
# This is an <h1> tag
## This is an <h2> tag
###### This is an <h6> tag
```
GitBook supports a nice way for explicitly setting the header ID. If you follow the header text with an opening curly bracket (separated from the text with a least one space), a hash, the ID and a closing curly bracket, the ID is set on the header. If you use the trailing hash feature of atx style headers, the header ID has to go after the trailing hashes. For example:
```markdown
Hello {#id}
-----
# Hello {#id}
# Hello # {#id}
```
### Paragraphs and Line Breaks {#paragraphs}
A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line — a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.
```
Here's a line for us to start with.
This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
```
### Emphasis {#emphasis}
```markdown
*This text will be italic*
_This will also be italic_
**This text will be bold**
__This will also be bold__
~~This text will be crossed out.~~
_You **can** combine them_
```
### Lists {#lists}
Markdown supports ordered (numbered) and unordered (bulleted) lists.
##### Unordered
Unordered lists use asterisks, pluses, and hyphens — interchangably — as list markers:
```markdown
* Item 1
* Item 2
* Item 2a
* Item 2b
```
##### Ordered
Ordered lists use numbers followed by periods:
```markdown
1. Item 1
2. Item 2
3. Item 3
* Item 3a
* Item 3b
```
### Links {#links}
Markdown supports two style of links: inline and reference.
A simple link can be created by surrounding the text with square brackets and the link URL with parentheses:
```markdown
This is [an example](http://example.com/ "Title") inline link with a title.
[This link](http://example.net/) has no title attribute.
```
Links can point to relative paths, anchors or absolute urls.
### References
There is another way to create links which does not interrupt the text flow. The URL and title are defined using a reference name and this reference name is then used in square brackets instead of the link URL:
```markdown
This is [an example][id] reference-style link.
```
Then, anywhere in the document, you define your link label like this, on a line by itself:
```markdown
[id]: http://example.com/ "Optional Title Here"
```
### Images {#images}
Images can be created in a similar way than links: just use an exclamation mark before the square brackets. The link text will become the alternative text of the image and the link URL specifies the image source:
```markdown
An image: ![gras](img/image.jpg)
```
### Blockquotes {#blockquotes}
A blockquote is started using the `>` marker followed by an optional space; all following lines that are also started with the blockquote marker belong to the blockquote. You can use any block-level elements inside a blockquote:
```markdown
As Kanye West said:
> We're living the future so
> the present is our past.
```
### Tables {#tables}
You can create tables by assembling a list of words and dividing them with hyphens `-` (for the first row), and then separating each column with a pipe `|`:
```markdown
| First Header | Second Header |
| ------------- | ------------- |
| Content Cell | Content Cell |
| Content Cell | Content Cell |
```
The pipes on either end of the table are optional. Cells can vary in width and do not need to be perfectly aligned within columns. There must be at least three hyphens in each column of the header row.
### Code {#code}
Markdown supports two different code block styles. One uses lines indented with either four spaces or one tab whereas the other uses lines with tilde characters as delimiters – therefore the content does not need to be indented:
```markdown
This is a sample code block.
Continued here.
```
##### Fenced code blocks
You can create fenced code blocks by placing triple backticks ` ``` ` before and after the code block. We recommend placing a blank line before and after code blocks to make the raw formatting easier to read.
```
function test() {
console.log("notice the blank line before this function?");
}
```
##### Syntax highlighting
You can add an optional language identifier to enable syntax highlighting in your fenced code block.
For example, to syntax highlight Ruby code:
```ruby
require 'redcarpet'
markdown = Redcarpet.new("Hello World!")
puts markdown.to_html
```
##### Inline code
Text phrases can be marked up as code by surrounding them with backticks:
Use `gitbook` to convert the `text` in markdown
syntax to HTML.
### Footnotes
GitBook supports a simple syntax for such footnotes. Footnotes are relative to each pages.
```markdown
Text prior to footnote reference.[^2]
[^2]: Comment to include in footnote.
```
### HTML
GitBook supports use of raw HTML in your text, Markdown syntax in HTML is not processed:
```
<div>
Markdown here will not be **parsed**
</div>
```
### Horizontal Rule
Horizontal Rules can be inserted using three or more asterisks, dashes or underscores, optionally separated by spaces or tabs, on an otherwise blank line:
```markdown
Three or more...
---
Hyphens
***
Asterisks
```
### Ignoring Markdown formatting
You can tell GitBook to ignore (or escape) Markdown formatting by using `\` before the Markdown character.
```
Let's rename \*our-new-project\* to \*our-old-project\*.
```
+10
View File
@@ -87,3 +87,13 @@ Current version is {{ softwareVersion }}.
##### include and block
Inclusion and inheritance is detailled in the [Content References](conrefs.md) section.
### Escaping
If you want GitBook to ignore any of the special templating tags, you can use raw and anything inside of it will be output as plain text.
``` twig
{% raw %}
this will {{ not be processed }}
{% endraw %}
```
+18
View File
@@ -0,0 +1,18 @@
# Builtin Templating Helpers
GitBook provides a serie of builtin filters and blocks to help you write templates.
### Filters
`value|default(default, [boolean])`If value is strictly undefined, return default, otherwise value. If boolean is true, any JavaScript falsy value will return default (false, "", etc)
`arr|sort(reverse, caseSens, attr)`
Sort arr with JavaScript's arr.sort function. If reverse is true, result will be reversed. Sort is case-insensitive by default, but setting caseSens to true makes it case-sensitive. If attr is passed, will compare attr from each item.
### Blocks
`{% markdown %}Markdown string{% endmarkdown %}`
Render inline markdown
`{% asciidoc %}AsciiDoc string{% endasciidoc %}`
Render inline asciidoc
+22
View File
@@ -10,8 +10,11 @@ The following is a reference of the available data during book's parsing and the
| `gitbook` | GitBook specific information |
| `page` | Current page specific information |
| `file` | File associated with the current page specific information |
| `readme` | Information about the Readme |
| `glossary` | Information about the Glossary |
| `summary` | Information about the table of contents |
| `languages` | List of languages for multi-lingual books |
| `output` | Information about the output generator |
| `config` | Dump of the `book.json` |
### Book Variables
@@ -62,3 +65,22 @@ The whole table of contents (`SUMMARY.md`) can be accessed:
| `languages.list` | List of languages for this book |
Languages are defined by `{ id: 'en', title: 'English' }`.
### Output Variables
| Variable | Description |
| -------- | ----------- |
| `output.name` | Name of the output generator, possible values are `website`, `json`, `ebook` |
| `output.format` | When `output.name == "ebook"`, `format` defines the ebook format that will be generated, possible values are `pdf`, `epub` or `mobi` |
### Readme Variables
| Variable | Description |
| -------- | ----------- |
| `readme.path` | Path to the Readme in the book |
### Glossary Variables
| Variable | Description |
| -------- | ----------- |
| `glossary.path` | Path to the Glossary in the book |
+3 -5
View File
@@ -1,14 +1,12 @@
# Theming
Since version 3.0.0, GitBook can be easily themed. Books are using by default the [theme-default](https://github.com/GitbookIO/theme-default).
The theme to use is specified in the [book's configuration](config.md) using key `theme`.
Since version 3.0.0, GitBook can be easily themed. Books use the [theme-default](https://github.com/GitbookIO/theme-default) theme by default.
> **Caution**: Custom theming can block some plugins from working correctly.
### Structure of a theme
A theme is a folder containing templates and assets. All the templates are optionnal, since theme are always extending the default theme.
A theme is a plugin containing templates and assets. Overriding any individual template is optional, since themes always extend the default theme.
| Folder | Description |
| -------- | ----------- |
@@ -19,7 +17,7 @@ A theme is a folder containing templates and assets. All the templates are optio
### Extend/Customize theme in a book
Authors can extend the templates of a theme directly from the book source (without creating an external theme). Templates will be resolved in the `_layouts` folder of the book first, then in
Authors can extend the templates of a theme directly from their book's source (without creating an external theme). Templates will be resolved in the `_layouts` folder of the book first, then in
### Publish a theme
+25
View File
@@ -0,0 +1,25 @@
var path = require('path');
var fs = require('fs');
var matchers = {
/**
Verify that a file exists in a directory
*/
toHaveFile: function () {
return {
compare: function (actual, expected) {
var filePath = path.join(actual, expected);
var exists = fs.existsSync(filePath);
return {
pass: exists
};
}
};
}
};
jasmine.getEnv().beforeEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
jasmine.addMatchers(matchers);
});
+9
View File
@@ -0,0 +1,9 @@
var gitbook = require('../gitbook');
describe('satisfies', function() {
it('should return true for *', function() {
expect(gitbook.satisfies('*')).toBe(true);
});
});
+16
View File
@@ -0,0 +1,16 @@
var tmp = require('tmp');
var initBook = require('../init');
describe('initBook', function() {
pit('should create a README and SUMMARY for empty book', function() {
var dir = tmp.dirSync();
return initBook(dir.name)
.then(function() {
expect(dir.name).toHaveFile('README.md');
expect(dir.name).toHaveFile('SUMMARY.md');
});
});
});
+6
View File
@@ -0,0 +1,6 @@
describe('GitBook', function() {
it('should correctly export', function() {
require('../');
});
});
+19
View File
@@ -0,0 +1,19 @@
var Config = require('../models/config');
/**
Decode changes from a JS API to a config object
@param {Config} config
@param {Object} result: result from API
@return {Config}
*/
function decodeGlobal(config, result) {
var values = result.values;
delete values.generator;
delete values.output;
return Config.updateValues(config, values);
}
module.exports = decodeGlobal;
+22
View File
@@ -0,0 +1,22 @@
var decodeConfig = require('./decodeConfig');
/**
Decode changes from a JS API to a output object.
Only the configuration can be edited by plugin's hooks
@param {Output} output
@param {Object} result: result from API
@return {Output}
*/
function decodeGlobal(output, result) {
var book = output.getBook();
var config = book.getConfig();
// Update config
config = decodeConfig(config, result.config);
book = book.set('config', config);
return output.set('book', book);
}
module.exports = decodeGlobal;
+44
View File
@@ -0,0 +1,44 @@
var deprecate = require('./deprecate');
/**
Decode changes from a JS API to a page object.
Only the content can be edited by plugin's hooks.
@param {Output} output
@param {Page} page: page instance to edit
@param {Object} result: result from API
@return {Page}
*/
function decodePage(output, page, result) {
var originalContent = page.getContent();
// No returned value
// Existing content will be used
if (!result) {
return page;
}
deprecate.disable('page.sections');
// GitBook 3
// Use returned page.content if different from original content
if (result.content != originalContent) {
page = page.set('content', result.content);
}
// GitBook 2 compatibility
// Finally, use page.sections
else if (result.sections) {
page = page.set('content',
result.sections.map(function(section) {
return section.content;
}).join('\n')
);
}
deprecate.enable('page.sections');
return page;
}
module.exports = decodePage;
+122
View File
@@ -0,0 +1,122 @@
var is = require('is');
var objectPath = require('object-path');
var logged = {};
var disabled = {};
/**
Log a deprecated notice
@param {Book|Output} book
@param {String} key
@param {String} message
*/
function logNotice(book, key, message) {
if (logged[key] || disabled[key]) return;
logged[key] = true;
var logger = book.getLogger();
logger.warn.ln(message);
}
/**
Deprecate a function
@param {Book|Output} book
@param {String} key: unique identitifer for the deprecated
@param {Function} fn
@param {String} msg: message to print when called
@return {Function}
*/
function deprecateMethod(book, key, fn, msg) {
return function() {
logNotice(book, key, msg);
return fn.apply(this, arguments);
};
}
/**
Deprecate a property of an object
@param {Book|Output} book
@param {String} key: unique identitifer for the deprecated
@param {Object} instance
@param {String|Function} property
@param {String} msg: message to print when called
@return {Function}
*/
function deprecateField(book, key, instance, property, value, msg) {
var store = undefined;
var prepare = function() {
if (!is.undefined(store)) return;
if (is.fn(value)) store = value();
else store = value;
};
var getter = function(){
prepare();
logNotice(book, key, msg);
return store;
};
var setter = function(v) {
prepare();
logNotice(book, key, msg);
store = v;
return store;
};
Object.defineProperty(instance, property, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
/**
Enable a deprecation
@param {String} key: unique identitifer
*/
function enableDeprecation(key) {
disabled[key] = false;
}
/**
Disable a deprecation
@param {String} key: unique identitifer
*/
function disableDeprecation(key) {
disabled[key] = true;
}
/**
Deprecate a method in favor of another one
@param {Book} book
@param {String} key
@param {Object} instance
@param {String} oldName
@param {String} newName
*/
function deprecateRenamedMethod(book, key, instance, oldName, newName, msg) {
msg = msg || ('"' + oldName + '" is deprecated, use "' + newName + '()" instead');
var fn = objectPath.get(instance, newName);
instance[oldName] = deprecateMethod(book, key, fn, msg);
}
module.exports = {
method: deprecateMethod,
renamedMethod: deprecateRenamedMethod,
field: deprecateField,
enable: enableDeprecation,
disable: disableDeprecation
};
+36
View File
@@ -0,0 +1,36 @@
var objectPath = require('object-path');
var deprecate = require('./deprecate');
/**
Encode a config object into a JS config api
@param {Output} output
@param {Config} config
@return {Object}
*/
function encodeConfig(output, config) {
var result = {
values: config.getValues().toJS(),
get: function(key, defaultValue) {
return objectPath.get(result.values, key, defaultValue);
},
set: function(key, value) {
return objectPath.set(result.values, key, value);
}
};
deprecate.field(output, 'config.options', result, 'options',
result.values, '"config.options" property is deprecated, use "config.get(key)" instead');
deprecate.field(output, 'config.options.generator', result.values, 'generator',
output.getGenerator(), '"options.generator" property is deprecated, use "output.name" instead');
deprecate.field(output, 'config.options.generator', result.values, 'output',
output.getRoot(), '"options.output" property is deprecated, use "output.root()" instead');
return result;
}
module.exports = encodeConfig;
+178
View File
@@ -0,0 +1,178 @@
var path = require('path');
var Promise = require('../utils/promise');
var PathUtils = require('../utils/path');
var fs = require('../utils/fs');
var Plugins = require('../plugins');
var deprecate = require('./deprecate');
var fileToURL = require('../output/helper/fileToURL');
var defaultBlocks = require('../constants/defaultBlocks');
var gitbook = require('../gitbook');
var encodeConfig = require('./encodeConfig');
var encodeSummary = require('./encodeSummary');
var encodeNavigation = require('./encodeNavigation');
/**
Encode a global context into a JS object
It's the context for page's hook, etc
@param {Output} output
@return {Object}
*/
function encodeGlobal(output) {
var book = output.getBook();
var bookFS = book.getContentFS();
var logger = output.getLogger();
var outputFolder = output.getRoot();
var plugins = output.getPlugins();
var blocks = Plugins.listBlocks(plugins);
var result = {
log: logger,
config: encodeConfig(output, book.getConfig()),
summary: encodeSummary(output, book.getSummary()),
/**
Check if the book is a multilingual book
@return {Boolean}
*/
isMultilingual: function() {
return book.isMultilingual();
},
/**
Check if the book is a language book for a multilingual book
@return {Boolean}
*/
isLanguageBook: function() {
return book.isLanguageBook();
},
/**
Read a file from the book
@param {String} fileName
@return {Promise<Buffer>}
*/
readFile: function(fileName) {
return bookFS.read(fileName);
},
/**
Read a file from the book as a string
@param {String} fileName
@return {Promise<String>}
*/
readFileAsString: function(fileName) {
return bookFS.readAsString(fileName);
},
/**
Resolve a file from the book root
@param {String} fileName
@return {String}
*/
resolve: function(fileName) {
return path.resolve(book.getContentRoot(), fileName);
},
template: {
/**
Apply a templating block and returns its result
@param {String} name
@param {Object} blockData
@return {Promise|Object}
*/
applyBlock: function(name, blockData) {
var block = blocks.get(name) || defaultBlocks.get(name);
return Promise(block.applyBlock(blockData, result));
}
},
output: {
/**
Name of the generator being used
{String}
*/
name: output.getGenerator(),
/**
Return absolute path to the root folder of output
@return {String}
*/
root: function() {
return outputFolder;
},
/**
Resolve a file from the output root
@param {String} fileName
@return {String}
*/
resolve: function(fileName) {
return path.resolve(outputFolder, fileName);
},
/**
Convert a filepath into an url
@return {String}
*/
toURL: function(filePath) {
return fileToURL(output, filePath);
},
/**
Write a file to the output folder,
It creates the required folder
@param {String} fileName
@param {Buffer} content
@return {Promise}
*/
writeFile: function(fileName, content) {
return Promise()
.then(function() {
var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
return fs.ensureFile(filePath)
.then(function() {
return fs.writeFile(filePath, content);
});
});
}
},
gitbook: {
version: gitbook.version
}
};
// Deprecated properties
deprecate.renamedMethod(output, 'this.isSubBook', result, 'isSubBook', 'isLanguageBook');
deprecate.renamedMethod(output, 'this.contentLink', result, 'contentLink', 'output.toURL');
deprecate.field(output, 'this.generator', result, 'generator',
output.getGenerator(), '"this.generator" property is deprecated, use "this.output.name" instead');
deprecate.field(output, 'this.navigation', result, 'navigation', function() {
return encodeNavigation(output);
}, '"navigation" property is deprecated');
deprecate.field(output, 'this.book', result, 'book',
result, '"book" property is deprecated, use "this" directly instead');
deprecate.field(output, 'this.options', result, 'options',
result.config.values, '"options" property is deprecated, use config.get(key) instead');
return result;
}
module.exports = encodeGlobal;
+64
View File
@@ -0,0 +1,64 @@
var Immutable = require('immutable');
/**
Encode an article for next/prev
@param {Map<String:Page>}
@param {Article}
@return {Object}
*/
function encodeArticle(pages, article) {
var articlePath = article.getPath();
return {
path: articlePath,
title: article.getTitle(),
level: article.getLevel(),
exists: (articlePath && pages.has(articlePath)),
external: article.isExternal()
};
}
/**
this.navigation is a deprecated property from GitBook v2
@param {Output}
@return {Object}
*/
function encodeNavigation(output) {
var book = output.getBook();
var pages = output.getPages();
var summary = book.getSummary();
var articles = summary.getArticlesAsList();
var navigation = articles
.map(function(article, i) {
var ref = article.getRef();
if (!ref) {
return undefined;
}
var prev = articles.get(i - 1);
var next = articles.get(i + 1);
return [
ref,
{
index: i,
title: article.getTitle(),
introduction: (i === 0),
prev: prev? encodeArticle(pages, prev) : undefined,
next: next? encodeArticle(pages, next) : undefined,
level: article.getLevel()
}
];
})
.filter(function(e) {
return Boolean(e);
});
return Immutable.Map(navigation).toJS();
}
module.exports = encodeNavigation;
+39
View File
@@ -0,0 +1,39 @@
var JSONUtils = require('../json');
var deprecate = require('./deprecate');
var encodeProgress = require('./encodeProgress');
/**
Encode a page in a context to a JS API
@param {Output} output
@param {Page} page
@return {Object}
*/
function encodePage(output, page) {
var book = output.getBook();
var summary = book.getSummary();
var fs = book.getContentFS();
var file = page.getFile();
// JS Page is based on the JSON output
var result = JSONUtils.encodePage(page, summary);
result.type = file.getType();
result.path = file.getPath();
result.rawPath = fs.resolve(result.path);
deprecate.field(output, 'page.progress', result, 'progress', function() {
return encodeProgress(output, page);
}, '"page.progress" property is deprecated');
deprecate.field(output, 'page.sections', result, 'sections', [
{
content: result.content,
type: 'normal'
}
], '"sections" property is deprecated, use page.content instead');
return result;
}
module.exports = encodePage;
+63
View File
@@ -0,0 +1,63 @@
var Immutable = require('immutable');
var encodeNavigation = require('./encodeNavigation');
/**
page.progress is a deprecated property from GitBook v2
@param {Output}
@param {Page}
@return {Object}
*/
function encodeProgress(output, page) {
var current = page.getPath();
var navigation = encodeNavigation(output);
navigation = Immutable.Map(navigation);
var n = navigation.size;
var percent = 0, prevPercent = 0, currentChapter = null;
var done = true;
var chapters = navigation
.map(function(nav, chapterPath) {
nav.path = chapterPath;
return nav;
})
.valueSeq()
.sortBy(function(nav) {
return nav.index;
})
.map(function(nav, i) {
// Calcul percent
nav.percent = (i * 100) / Math.max((n - 1), 1);
// Is it done
nav.done = done;
if (nav.path == current) {
currentChapter = nav;
percent = nav.percent;
done = false;
} else if (done) {
prevPercent = nav.percent;
}
return nav;
})
.toJS();
return {
// Previous percent
prevPercent: prevPercent,
// Current percent
percent: percent,
// List of chapter with progress
chapters: chapters,
// Current chapter
current: currentChapter
};
}
module.exports = encodeProgress;
+51
View File
@@ -0,0 +1,51 @@
var encodeSummaryArticle = require('../json/encodeSummaryArticle');
/**
Encode summary to provide an API to plugin
@param {Output} output
@param {Config} config
@return {Object}
*/
function encodeSummary(output, summary) {
var result = {
/**
Iterate over the summary, it stops when the "iter" returns false
@param {Function} iter
*/
walk: function (iter) {
summary.getArticle(function(article) {
var jsonArticle = encodeSummaryArticle(article, false);
return iter(jsonArticle);
});
},
/**
Get an article by its level
@param {String} level
@return {Object}
*/
getArticleByLevel: function(level) {
var article = summary.getByLevel(level);
return (article? encodeSummaryArticle(article) : undefined);
},
/**
Get an article by its path
@param {String} level
@return {Object}
*/
getArticleByPath: function(level) {
var article = summary.getByPath(level);
return (article? encodeSummaryArticle(article) : undefined);
}
};
return result;
}
module.exports = encodeSummary;
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
encodePage: require('./encodePage'),
decodePage: require('./decodePage'),
encodeGlobal: require('./encodeGlobal'),
decodeGlobal: require('./decodeGlobal')
};
-69
View File
@@ -1,69 +0,0 @@
var _ = require('lodash');
function BackboneFile(book) {
if (!(this instanceof BackboneFile)) return new BackboneFile(book);
this.book = book;
this.log = this.book.log;
// Filename in the book
this.path = '';
this.parser;
_.bindAll(this);
}
// Type of the backbone file
BackboneFile.prototype.type = '';
// Parse a backbone file
BackboneFile.prototype.parse = function() {
// To be implemented by each child
};
// Handle case where file doesn't exists
BackboneFile.prototype.parseNotFound = function() {
};
// Return true if backbone file exists
BackboneFile.prototype.exists = function() {
return Boolean(this.path);
};
// Locate a backbone file, could be .md, .asciidoc, etc
BackboneFile.prototype.locate = function() {
var that = this;
var filename = this.book.config.getStructure(this.type, true);
this.log.debug.ln('locating', this.type, ':', filename);
return this.book.findParsableFile(filename)
.then(function(result) {
if (!result) return;
that.path = result.path;
that.parser = result.parser;
});
};
// Read and parse the file
BackboneFile.prototype.load = function() {
var that = this;
this.log.debug.ln('loading', this.type, ':', that.path);
return this.locate()
.then(function() {
if (!that.path) return that.parseNotFound();
that.log.debug.ln(that.type, 'located at', that.path);
return that.book.readFile(that.path)
// Parse it
.then(function(content) {
return that.parse(content);
});
});
};
module.exports = BackboneFile;
-99
View File
@@ -1,99 +0,0 @@
var _ = require('lodash');
var util = require('util');
var BackboneFile = require('./file');
// Normalize a glossary entry name into a unique id
function nameToId(name) {
return name.toLowerCase()
.replace(/[\/\\\?\%\*\:\;\|\"\'\\<\\>\#\$\(\)\!\.\@]/g, '')
.replace(/ /g, '_')
.trim();
}
/*
A glossary entry is represented by a name and a short description
An unique id for the entry is generated using its name
*/
function GlossaryEntry(name, description) {
if (!(this instanceof GlossaryEntry)) return new GlossaryEntry(name, description);
this.name = name;
this.description = description;
Object.defineProperty(this, 'id', {
get: _.bind(this.getId, this)
});
}
// Normalizes a glossary entry's name to create an ID
GlossaryEntry.prototype.getId = function() {
return nameToId(this.name);
};
/*
A glossary is a list of entries stored in a GLOSSARY.md file
*/
function Glossary() {
BackboneFile.apply(this, arguments);
this.entries = [];
}
util.inherits(Glossary, BackboneFile);
Glossary.prototype.type = 'glossary';
// Get templating context
Glossary.prototype.getContext = function() {
if (!this.path) return {};
return {
glossary: {
path: this.path
}
};
};
// Parse the readme content
Glossary.prototype.parse = function(content) {
var that = this;
return this.parser.glossary(content)
.then(function(entries) {
that.entries = _.map(entries, function(entry) {
return new GlossaryEntry(entry.name, entry.description);
});
});
};
// Return an entry by its id
Glossary.prototype.get = function(id) {
return _.find(this.entries, {
id: id
});
};
// Find an entry by its name
Glossary.prototype.find = function(name) {
return this.get(nameToId(name));
};
// Return false if glossary has entries (and exists)
Glossary.prototype.isEmpty = function(id) {
return _.size(this.entries) === 0;
};
// Convert the glossary to a list of annotations
Glossary.prototype.annotations = function() {
return _.map(this.entries, function(entry) {
return {
id: entry.id,
name: entry.name,
description: entry.description,
href: '/' + this.path + '#' + entry.id
};
}, this);
};
module.exports = Glossary;
-8
View File
@@ -1,8 +0,0 @@
module.exports = {
Readme: require('./readme'),
Summary: require('./summary'),
Glossary: require('./glossary'),
Langs: require('./langs')
};
-81
View File
@@ -1,81 +0,0 @@
var _ = require('lodash');
var path = require('path');
var util = require('util');
var BackboneFile = require('./file');
function Language(title, folder) {
var that = this;
this.title = title;
this.folder = folder;
Object.defineProperty(this, 'id', {
get: function() {
return path.basename(that.folder);
}
});
}
/*
A Langs is a list of languages stored in a LANGS.md file
*/
function Langs() {
BackboneFile.apply(this, arguments);
this.languages = [];
}
util.inherits(Langs, BackboneFile);
Langs.prototype.type = 'langs';
// Parse the readme content
Langs.prototype.parse = function(content) {
var that = this;
return this.parser.langs(content)
.then(function(langs) {
that.languages = _.map(langs, function(entry) {
return new Language(entry.title, entry.path);
});
});
};
// Return the list of languages
Langs.prototype.list = function() {
return this.languages;
};
// Return default/main language for the book
Langs.prototype.getDefault = function() {
return _.first(this.languages);
};
// Return true if a language is the default one
// "lang" cam be a string (id) or a Language entry
Langs.prototype.isDefault = function(lang) {
lang = lang.id || lang;
return (this.cound() > 0 && this.getDefault().id == lang);
};
// Return the count of languages
Langs.prototype.count = function() {
return _.size(this.languages);
};
// Return templating context for the languages list
Langs.prototype.getContext = function() {
if (this.count() == 0) return {};
return {
languages: {
list: _.map(this.languages, function(lang) {
return {
id: lang.id,
title: lang.title
};
})
}
};
};
module.exports = Langs;
-26
View File
@@ -1,26 +0,0 @@
var util = require('util');
var BackboneFile = require('./file');
function Readme() {
BackboneFile.apply(this, arguments);
this.title;
this.description;
}
util.inherits(Readme, BackboneFile);
Readme.prototype.type = 'readme';
// Parse the readme content
Readme.prototype.parse = function(content) {
var that = this;
return this.parser.readme(content)
.then(function(out) {
that.title = out.title;
that.description = out.description;
});
};
module.exports = Readme;
-348
View File
@@ -1,348 +0,0 @@
var _ = require('lodash');
var util = require('util');
var location = require('../utils/location');
var error = require('../utils/error');
var BackboneFile = require('./file');
/*
An article represent an entry in the Summary.
It's defined by a title, a reference, and children articles,
the reference (ref) can be a filename + anchor or an external file (optional)
*/
function TOCArticle(def, parent) {
// Title
this.title = def.title;
// Parent TOCPart or TOCArticle
this.parent = parent;
// As string indicating the overall position
// ex: '1.0.0'
this.level;
this._next;
this._prev;
// When README has been automatically added
this.isAutoIntro = def.isAutoIntro;
this.isIntroduction = def.isIntroduction;
this.validate();
// Path can be a relative path or an url, or nothing
this.ref = def.path;
if (this.ref && !this.isExternal()) {
var parts = this.ref.split('#');
this.path = (parts.length > 1? parts.slice(0, -1).join('#') : this.ref);
this.anchor = (parts.length > 1? '#' + _.last(parts) : null);
// Normalize path to remove ('./', etc)
this.path = location.normalize(this.path);
}
this.articles = _.map(def.articles || [], function(article) {
if (article instanceof TOCArticle) return article;
return new TOCArticle(article, this);
}, this);
}
// Validate the article
TOCArticle.prototype.validate = function() {
if (!this.title) {
throw error.ParsingError(new Error('SUMMARY entries should have an non-empty title'));
}
};
// Iterate over all articles in this articles
TOCArticle.prototype.walk = function(iter, base) {
base = base || this.level;
_.each(this.articles, function(article, i) {
var level = levelId(base, i);
if (iter(article, level) === false) {
return false;
}
article.walk(iter, level);
});
};
// Return templating context for an article
TOCArticle.prototype.getContext = function() {
return {
level: this.level,
title: this.title,
depth: this.depth(),
path: this.isExternal()? undefined : this.path,
anchor: this.isExternal()? undefined : this.anchor,
url: this.isExternal()? this.ref : undefined
};
};
// Return true if is pointing to a file
TOCArticle.prototype.hasLocation = function() {
return Boolean(this.path);
};
// Return true if is pointing to an external location
TOCArticle.prototype.isExternal = function() {
return location.isExternal(this.ref);
};
// Return true if this article is the introduction
TOCArticle.prototype.isIntro = function() {
return Boolean(this.isIntroduction);
};
// Return true if has children
TOCArticle.prototype.hasChildren = function() {
return this.articles.length > 0;
};
// Return true if has an article as parent
TOCArticle.prototype.hasParent = function() {
return !(this.parent instanceof TOCPart);
};
// Return depth of this article
TOCArticle.prototype.depth = function() {
return this.level.split('.').length;
};
// Return next article in the TOC
TOCArticle.prototype.next = function() {
return this._next;
};
// Return previous article in the TOC
TOCArticle.prototype.prev = function() {
return this._prev;
};
// Map over all articles
TOCArticle.prototype.map = function(iter) {
return _.map(this.articles, iter);
};
/*
A part of a ToC is a composed of a tree of articles and an optiona title
*/
function TOCPart(part, parent) {
if (!(this instanceof TOCPart)) return new TOCPart(part, parent);
TOCArticle.apply(this, arguments);
}
util.inherits(TOCPart, TOCArticle);
// Validate the part
TOCPart.prototype.validate = function() { };
// Return a sibling (next or prev) of this part
TOCPart.prototype.sibling = function(direction) {
var parts = this.parent.parts;
var pos = _.findIndex(parts, this);
if (parts[pos + direction]) {
return parts[pos + direction];
}
return null;
};
// Iterate over all entries of the part
TOCPart.prototype.walk = function(iter, base) {
var articles = this.articles;
if (articles.length == 0) return;
// Has introduction?
if (articles[0].isIntro()) {
if (iter(articles[0], '0') === false) {
return;
}
articles = articles.slice(1);
}
_.each(articles, function(article, i) {
var level = levelId(base, i);
if (iter(article, level) === false) {
return false;
}
article.walk(iter, level);
});
};
// Return templating context for a part
TOCPart.prototype.getContext = function(onArticle) {
onArticle = onArticle || function(article) {
return article.getContext();
};
return {
title: this.title,
articles: this.map(onArticle)
};
};
/*
A summary is composed of a list of parts, each composed wit a tree of articles.
*/
function Summary() {
BackboneFile.apply(this, arguments);
this.parts = [];
this._length = 0;
}
util.inherits(Summary, BackboneFile);
Summary.prototype.type = 'summary';
// Prepare summary when non existant
Summary.prototype.parseNotFound = function() {
this.update([]);
};
// Parse the summary content
Summary.prototype.parse = function(content) {
var that = this;
return this.parser.summary(content)
.then(function(summary) {
that.update(summary.parts);
});
};
// Return templating context for the summary
Summary.prototype.getContext = function() {
function onArticle(article) {
var result = article.getContext();
if (article.hasChildren()) {
result.articles = article.map(onArticle);
}
return result;
}
return {
summary: {
parts: _.map(this.parts, function(part) {
return part.getContext(onArticle);
})
}
};
};
// Iterate over all entries of the summary
// iter is called with an TOCArticle
Summary.prototype.walk = function(iter) {
var hasMultipleParts = this.parts.length > 1;
_.each(this.parts, function(part, i) {
part.walk(iter, hasMultipleParts? levelId('', i) : null);
});
};
// Find a specific article using a filter
Summary.prototype.find = function(filter) {
var result;
this.walk(function(article) {
if (filter(article)) {
result = article;
return false;
}
});
return result;
};
// Flatten the list of articles
Summary.prototype.flatten = function() {
var result = [];
this.walk(function(article) {
result.push(article);
});
return result;
};
// Return the first TOCArticle for a specific page (or path)
Summary.prototype.getArticle = function(page) {
if (!_.isString(page)) page = page.path;
return this.find(function(article) {
return article.path == page;
});
};
// Return the first TOCArticle for a specific level
Summary.prototype.getArticleByLevel = function(lvl) {
return this.find(function(article) {
return article.level == lvl;
});
};
// Return the count of articles in the summary
Summary.prototype.count = function() {
return this._length;
};
// Prepare the summary
Summary.prototype.update = function(parts) {
var that = this;
that.parts = _.map(parts, function(part) {
return new TOCPart(part, that);
});
// Create first part if none
if (that.parts.length == 0) {
that.parts.push(new TOCPart({}, that));
}
// Add README as first entry
var firstArticle = that.parts[0].articles[0];
if (!firstArticle || firstArticle.path != that.book.readme.path) {
that.parts[0].articles.unshift(new TOCArticle({
title: 'Introduction',
path: that.book.readme.path,
isAutoIntro: true
}, that.parts[0]));
}
that.parts[0].articles[0].isIntroduction = true;
// Update the count and indexing of "level"
var prev = undefined;
that._length = 0;
that.walk(function(article, level) {
// Index level
article.level = level;
// Chain articles
article._prev = prev;
if (prev) prev._next = article;
prev = article;
that._length += 1;
});
};
// Return a level string from a base level and an index
function levelId(base, i) {
i = i + 1;
return (base? [base || '', i] : [i]).join('.');
}
module.exports = Summary;
-388
View File
@@ -1,388 +0,0 @@
var _ = require('lodash');
var path = require('path');
var Ignore = require('ignore');
var Config = require('./config');
var Readme = require('./backbone/readme');
var Glossary = require('./backbone/glossary');
var Summary = require('./backbone/summary');
var Langs = require('./backbone/langs');
var Page = require('./page');
var pathUtil = require('./utils/path');
var error = require('./utils/error');
var Promise = require('./utils/promise');
var Logger = require('./utils/logger');
var parsers = require('./parsers');
var initBook = require('./init');
/*
The Book class is an interface for parsing books content.
It does not require to run on Node.js, isnce it only depends on the fs implementation
*/
function Book(opts) {
if (!(this instanceof Book)) return new Book(opts);
this.opts = _.defaults(opts || {}, {
fs: null,
// Root path for the book
root: '',
// Extend book configuration
config: {},
// Log function
log: function(msg) {
process.stdout.write(msg);
},
// Log level
logLevel: 'info'
});
if (!opts.fs) throw error.ParsingError(new Error('Book requires a fs instance'));
// Root path for the book
this.root = opts.root;
// If multi-lingual, book can have a parent
this.parent = opts.parent;
if (this.parent) {
this.language = path.relative(this.parent.root, this.root);
}
// A book is linked to an fs, to access its content
this.fs = opts.fs;
// Rules to ignore some files
this.ignore = Ignore();
this.ignore.addPattern([
// Skip Git stuff
'.git/',
// Skip OS X meta data
'.DS_Store',
// Skip stuff installed by plugins
'node_modules',
// Skip book outputs
'_book',
'*.pdf',
'*.epub',
'*.mobi'
]);
// Create a logger for the book
this.log = new Logger(opts.log, opts.logLevel);
// Create an interface to access the configuration
this.config = new Config(this, opts.config);
// Interfaces for the book structure
this.readme = new Readme(this);
this.summary = new Summary(this);
this.glossary = new Glossary(this);
// Multilinguals book
this.langs = new Langs(this);
this.books = [];
// List of page in the book
this.pages = {};
// Deprecation for templates
Object.defineProperty(this, 'options', {
get: function () {
this.log.warn.ln('"options" property is deprecated, use config.get(key) instead');
var cfg = this.config.dump();
error.deprecateField(cfg, 'book', (this.output? this.output.name : null), '"options.generator" property is deprecated, use "output.name" instead');
// options.generator
cfg.generator = this.output? this.output.name : null;
// options.output
cfg.output = this.output? this.output.root() : null;
return cfg;
}
});
_.bindAll(this);
// Loop for template filters/blocks
error.deprecateField(this, 'book', this, '"book" property is deprecated, use "this" directly instead');
}
// Return templating context for the book
Book.prototype.getContext = function() {
var variables = this.config.get('variables', {});
return {
book: _.extend({
language: this.language
}, variables)
};
};
// Parse and prepare the configuration, fail if invalid
Book.prototype.prepareConfig = function() {
var that = this;
return this.config.load()
.then(function() {
var rootFolder = that.config.get('root');
if (!rootFolder) return;
that.originalRoot = that.root;
that.root = path.resolve(that.root, rootFolder);
});
};
// Resolve a path in the book source
// Enforce that the output path is in the scope
Book.prototype.resolve = function() {
var filename = path.resolve.apply(path, [this.root].concat(_.toArray(arguments)));
if (!this.isFileInScope(filename)) {
throw error.FileOutOfScopeError({
filename: filename,
root: this.root
});
}
return filename;
};
// Return false if a file is outside the book' scope
Book.prototype.isFileInScope = function(filename) {
filename = path.resolve(this.root, filename);
// Is the file in the scope of the parent?
if (this.parent && this.parent.isFileInScope(filename)) return true;
// Is file in the root folder?
return pathUtil.isInRoot(this.root, filename);
};
// Parse .gitignore, etc to extract rules
Book.prototype.parseIgnoreRules = function() {
var that = this;
return Promise.serie([
'.ignore',
'.gitignore',
'.bookignore'
], function(filename) {
return that.readFile(filename)
.then(function(content) {
that.ignore.addPattern(content.toString().split(/\r?\n/));
}, function() {
return Promise();
});
});
};
// Parse the whole book
Book.prototype.parse = function() {
var that = this;
return Promise()
.then(this.prepareConfig)
.then(this.parseIgnoreRules)
// Parse languages
.then(function() {
return that.langs.load();
})
.then(function() {
if (that.isMultilingual()) {
if (that.isLanguageBook()) {
throw error.ParsingError(new Error('A multilingual book as a language book is forbidden'));
}
that.log.info.ln('Parsing multilingual book, with', that.langs.count(), 'languages');
// Create a new book for each language and parse it
return Promise.serie(that.langs.list(), function(lang) {
that.log.debug.ln('Preparing book for language', lang.id);
var langBook = new Book(_.extend({}, that.opts, {
parent: that,
config: that.config.dump(),
root: that.resolve(lang.id)
}));
that.books.push(langBook);
return langBook.parse();
});
}
return Promise()
// Parse the readme
.then(that.readme.load)
.then(function() {
if (!that.readme.exists()) {
throw new error.FileNotFoundError({ filename: 'README' });
}
// Default configuration to infos extracted from readme
if (!that.config.get('title')) that.config.set('title', that.readme.title);
if (!that.config.get('description')) that.config.set('description', that.readme.description);
})
// Parse the summary
.then(that.summary.load)
.then(function() {
if (!that.summary.exists()) {
that.log.warn.ln('no summary file in this book');
}
// Index summary's articles
that.summary.walk(function(article) {
if (!article.hasLocation() || article.isExternal()) return;
that.addPage(article.path);
});
})
// Parse the glossary
.then(that.glossary.load)
// Add the glossary as a page
.then(function() {
if (!that.glossary.exists()) return;
that.addPage(that.glossary.path);
});
});
};
// Mark a filename as being parsable
Book.prototype.addPage = function(filename) {
if (this.hasPage(filename)) return this.getPage(filename);
filename = pathUtil.normalize(filename);
this.pages[filename] = new Page(this, filename);
return this.pages[filename];
};
// Return a page by its filename (or undefined)
Book.prototype.getPage = function(filename) {
filename = pathUtil.normalize(filename);
return this.pages[filename];
};
// Return true, if has a specific page
Book.prototype.hasPage = function(filename) {
return Boolean(this.getPage(filename));
};
// Test if a file is ignored, return true if it is
Book.prototype.isFileIgnored = function(filename) {
return this.ignore.filter([filename]).length == 0;
};
// Read a file in the book, throw error if ignored
Book.prototype.readFile = function(filename) {
if (this.isFileIgnored(filename)) return Promise.reject(new error.FileNotFoundError({ filename: filename }));
return this.fs.readAsString(this.resolve(filename));
};
// Get stat infos about a file
Book.prototype.statFile = function(filename) {
if (this.isFileIgnored(filename)) return Promise.reject(new error.FileNotFoundError({ filename: filename }));
return this.fs.stat(this.resolve(filename));
};
// Find a parsable file using a filename
Book.prototype.findParsableFile = function(filename) {
var that = this;
var ext = path.extname(filename);
var basename = path.basename(filename, ext);
// Ordered list of extensions to test
var exts = parsers.extensions;
if (ext) exts = _.uniq([ext].concat(exts));
return _.reduce(exts, function(prev, ext) {
return prev.then(function(output) {
// Stop if already find a parser
if (output) return output;
var filepath = basename+ext;
return that.fs.findFile(that.root, filepath)
.then(function(realFilepath) {
if (!realFilepath) return null;
return {
parser: parsers.get(ext),
path: realFilepath
};
});
});
}, Promise(null));
};
// Return true if book is associated to a language
Book.prototype.isLanguageBook = function() {
return Boolean(this.parent);
};
Book.prototype.isSubBook = Book.prototype.isLanguageBook;
// Return true if the book is main instance of a multilingual book
Book.prototype.isMultilingual = function() {
return this.langs.count() > 0;
};
// Return true if file is in the scope of this book
Book.prototype.isInBook = function(filename) {
return pathUtil.isInRoot(
this.root,
filename
);
};
// Return true if file is in the scope of a child book
Book.prototype.isInLanguageBook = function(filename) {
var that = this;
return _.some(this.langs.list(), function(lang) {
return pathUtil.isInRoot(
that.resolve(lang.id),
that.resolve(filename)
);
});
};
// ----- DEPRECATED METHODS
Book.prototype.contentLink = error.deprecateMethod(function(s) {
return this.output.toURL(s);
}, '.contentLink() is deprecated, use ".output.toURL()" instead');
Book.prototype.contentPath = error.deprecateMethod(function(s) {
return this.output.toURL(s);
}, '.contentPath() is deprecated, use ".output.toURL()" instead');
Book.prototype.isSubBook = error.deprecateMethod(function() {
return this.isLanguageBook();
}, '.isSubBook() is deprecated, use ".isLanguageBook()" instead');
// Initialize a book
Book.init = function(fs, root, opts) {
var book = new Book(_.extend(opts || {}, {
root: root,
fs: fs
}));
return initBook(book);
};
module.exports = Book;
+15
View File
@@ -0,0 +1,15 @@
var Modifiers = require('./modifiers');
module.exports = {
Parse: require('./parse'),
// Models
Book: require('./models/book'),
FS: require('./models/fs'),
Summary: require('./models/summary'),
Glossary: require('./models/glossary'),
// Modifiers
SummaryModifier: Modifiers.Summary,
ConfigModifier: Modifiers.Config
};
+34
View File
@@ -0,0 +1,34 @@
var Parse = require('../parse');
var Output = require('../output');
var timing = require('../utils/timing');
var options = require('./options');
var getBook = require('./getBook');
var getOutputFolder = require('./getOutputFolder');
module.exports = {
name: 'build [book] [output]',
description: 'build a book',
options: [
options.log,
options.format,
options.timing
],
exec: function(args, kwargs) {
var book = getBook(args, kwargs);
var outputFolder = getOutputFolder(args);
var Generator = Output.getGenerator(kwargs.format);
return Parse.parseBook(book)
.then(function(resultBook) {
return Output.generate(Generator, resultBook, {
root: outputFolder
});
})
.fin(function() {
if (kwargs.timing) timing.dump(book.getLogger());
});
}
};
+76
View File
@@ -0,0 +1,76 @@
var path = require('path');
var tmp = require('tmp');
var Promise = require('../utils/promise');
var fs = require('../utils/fs');
var Parse = require('../parse');
var Output = require('../output');
var options = require('./options');
var getBook = require('./getBook');
module.exports = function(format) {
return {
name: (format + ' [book] [output]'),
description: 'build a book into an ebook file',
options: [
options.log
],
exec: function(args, kwargs) {
// Output file will be stored in
var outputFile = args[1] || ('book.' + format);
// Create temporary directory
var outputFolder = tmp.dirSync().name;
var book = getBook(args, kwargs);
var logger = book.getLogger();
var Generator = Output.getGenerator('ebook');
return Parse.parseBook(book)
.then(function(resultBook) {
return Output.generate(Generator, resultBook, {
root: outputFolder,
format: format
});
})
// Extract ebook file
.then(function(output) {
var book = output.getBook();
var languages = book.getLanguages();
if (book.isMultilingual()) {
return Promise.ForEach(languages, function(lang) {
var langID = lang.getID();
var langOutputFile = path.join(
path.dirname(outputFile),
path.basename(outputFile, format) + '_' + langID + '.' + format
);
return fs.copy(
path.resolve(outputFolder, langID, 'index.' + format),
langOutputFile
);
})
.thenResolve(languages.getCount());
} else {
return fs.copy(
path.resolve(outputFolder, 'index.' + format),
outputFile
).thenResolve(1);
}
})
// Log end
.then(function(count) {
logger.info.ok(count + ' file(s) generated');
logger.debug('cleaning up... ');
return logger.debug.promise(fs.rmDir(outputFolder));
});
}
};
};
+23
View File
@@ -0,0 +1,23 @@
var path = require('path');
var Book = require('../models/book');
var createNodeFS = require('../fs/node');
/**
Return a book instance to work on from
command line args/kwargs
@param {Array} args
@param {Object} kwargs
@return {Book}
*/
function getBook(args, kwargs) {
var input = path.resolve(args[0] || process.cwd());
var logLevel = kwargs.log;
var fs = createNodeFS(input);
var book = Book.createForFS(fs);
return book.setLogLevel(logLevel);
}
module.exports = getBook;
+17
View File
@@ -0,0 +1,17 @@
var path = require('path');
/**
Return path to output folder
@param {Array} args
@return {String}
*/
function getOutputFolder(args) {
var bookRoot = path.resolve(args[0] || process.cwd());
var defaultOutputRoot = path.join(bookRoot, '_book');
var outputFolder = args[1]? path.resolve(process.cwd(), args[1]) : defaultOutputRoot;
return outputFolder;
}
module.exports = getOutputFolder;
-140
View File
@@ -1,140 +0,0 @@
var _ = require('lodash');
var path = require('path');
var Book = require('../book');
var NodeFS = require('../fs/node');
var Logger = require('../utils/logger');
var Promise = require('../utils/promise');
var fs = require('../utils/fs');
var JSONOutput = require('../output/json');
var WebsiteOutput = require('../output/website');
var EBookOutput = require('../output/ebook');
var nodeFS = new NodeFS();
var LOG_OPTION = {
name: 'log',
description: 'Minimum log level to display',
values: _.chain(Logger.LEVELS)
.keys()
.map(function(s) {
return s.toLowerCase();
})
.value(),
defaults: 'info'
};
var FORMAT_OPTION = {
name: 'format',
description: 'Format to build to',
values: ['website', 'json', 'ebook'],
defaults: 'website'
};
var FORMATS = {
json: JSONOutput,
website: WebsiteOutput,
ebook: EBookOutput
};
// Commands which is processing a book
// the root of the book is the first argument (or current directory)
function bookCmd(fn) {
return function(args, kwargs) {
var input = path.resolve(args[0] || process.cwd());
var book = new Book({
fs: nodeFS,
root: input,
logLevel: kwargs.log
});
return fn(book, args.slice(1), kwargs);
};
}
// Commands which is working on a Output instance
function outputCmd(fn) {
return bookCmd(function(book, args, kwargs) {
var Out = FORMATS[kwargs.format];
var outputFolder = undefined;
// Set output folder
if (args[0]) {
outputFolder = path.resolve(process.cwd(), args[0]);
}
return fn(new Out(book, {
root: outputFolder
}), args);
});
}
// Command to generate an ebook
function ebookCmd(format) {
return {
name: format + ' [book] [output] [file]',
description: 'generates ebook '+format,
options: [
LOG_OPTION
],
exec: bookCmd(function(book, args, kwargs) {
return fs.tmpDir()
.then(function(dir) {
var ext = '.'+format;
var outputFile = path.resolve(process.cwd(), args[0] || ('book' + ext));
var output = new EBookOutput(book, {
root: dir,
format: format
});
return output.book.parse()
.then(function() {
return output.generate();
})
// Copy the ebook files
.then(function() {
if (output.book.isMultilingual()) {
return Promise.serie(output.book.langs.list(), function(lang) {
var _outputFile = path.join(
path.dirname(outputFile),
path.basename(outputFile, ext) + '_' + lang.id + ext
);
return fs.copy(
path.resolve(dir, lang.id, 'index' + ext),
_outputFile
);
})
.thenResolve(output.book.langs.count());
} else {
return fs.copy(
path.resolve(dir, 'index' + ext),
outputFile
).thenResolve(1);
}
})
.then(function(n) {
output.book.log.info.ok(n+' file(s) generated');
output.book.log.info('cleaning up... ');
return output.book.log.info.promise(fs.rmDir(dir));
});
});
})
};
}
module.exports = {
nodeFS: nodeFS,
bookCmd: bookCmd,
outputCmd: outputCmd,
ebookCmd: ebookCmd,
options: {
log: LOG_OPTION,
format: FORMAT_OPTION
},
FORMATS: FORMATS
};
+11 -198
View File
@@ -1,199 +1,12 @@
/* eslint-disable no-console */
var buildEbook = require('./buildEbook');
var _ = require('lodash');
var path = require('path');
var tinylr = require('tiny-lr');
var Promise = require('../utils/promise');
var PluginsManager = require('../plugins');
var Book = require('../book');
var helper = require('./helper');
var Server = require('./server');
var watch = require('./watch');
module.exports = {
commands: [
{
name: 'init [book]',
description: 'setup and create files for chapters',
options: [
helper.options.log
],
exec: function(args) {
var input = path.resolve(args[0] || process.cwd());
return Book.init(helper.nodeFS, input);
}
},
{
name: 'parse [book]',
description: 'parse and returns debug information for a book',
options: [
helper.options.log
],
exec: helper.bookCmd(function(book) {
return book.parse()
.then(function() {
book.log.info.ln('Book located in:', book.root);
book.log.info.ln('');
if (book.config.exists()) book.log.info.ln('Configuration:', book.config.path);
if (book.isMultilingual()) {
book.log.info.ln('Multilingual book detected:', book.langs.path);
} else {
book.log.info.ln('Readme:', book.readme.path);
book.log.info.ln('Summary:', book.summary.path);
if (book.glossary.exists()) book.log.info.ln('Glossary:', book.glossary.path);
book.log.info.ln('Pages:');
_.each(book.pages, function(page) {
book.log.info.ln('\t-', page.path);
});
}
});
})
},
{
name: 'install [book]',
description: 'install all plugins dependencies',
options: [
helper.options.log
],
exec: helper.bookCmd(function(book, args) {
var plugins = new PluginsManager(book);
return book.config.load()
.then(function() {
return plugins.install();
});
})
},
{
name: 'build [book] [output]',
description: 'build a book',
options: [
helper.options.log,
helper.options.format
],
exec: helper.outputCmd(function(output, args, kwargs) {
return output.book.parse()
.then(function() {
return output.generate();
});
})
},
helper.ebookCmd('pdf'),
helper.ebookCmd('epub'),
helper.ebookCmd('mobi'),
{
name: 'serve [book]',
description: 'Build then serve a book 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
},
helper.options.format,
helper.options.log
],
exec: function(args, kwargs) {
var input = path.resolve(args[0] || process.cwd());
var server = new Server();
// Init livereload server
var lrServer = tinylr({});
var port = kwargs.port;
var lrPath;
var generate = function() {
// Stop server if running
if (server.isRunning()) console.log('Stopping server');
return server.stop()
// Generate the book
.then(function() {
var book = new Book({
fs: helper.nodeFS,
root: input,
logLevel: kwargs.log
});
return book.parse()
.then(function() {
// Add livereload plugin
book.config.set('plugins',
book.config.get('plugins')
.concat([
{ name: 'livereload' }
])
);
var Out = helper.FORMATS[kwargs.format];
var output = new Out(book);
return output.generate()
.thenResolve(output);
});
})
// Start server and watch changes
.then(function(output) {
console.log();
console.log('Starting server ...');
return server.start(output.root(), port)
.then(function() {
console.log('Serving book on http://localhost:'+port);
if (lrPath) {
// trigger livereload
lrServer.changed({
body: {
files: [lrPath]
}
});
}
if (!kwargs.watch) return;
return watch(output.book.root)
.then(function(filepath) {
// set livereload path
lrPath = filepath;
console.log('Restart after change in file', filepath);
console.log('');
return generate();
});
});
});
};
return Promise.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();
});
}
}
]
};
module.exports = [
require('./build'),
require('./serve'),
require('./install'),
require('./parse'),
require('./init'),
buildEbook('pdf'),
buildEbook('epub'),
buildEbook('mobi')
];
+17
View File
@@ -0,0 +1,17 @@
var path = require('path');
var options = require('./options');
var initBook = require('../init');
module.exports = {
name: 'init [book]',
description: 'setup and create files for chapters',
options: [
options.log
],
exec: function(args, kwargs) {
var bookRoot = path.resolve(process.cwd(), args[0] || './');
return initBook(bookRoot);
}
};
+21
View File
@@ -0,0 +1,21 @@
var options = require('./options');
var getBook = require('./getBook');
var Parse = require('../parse');
var Plugins = require('../plugins');
module.exports = {
name: 'install [book]',
description: 'install all plugins dependencies',
options: [
options.log
],
exec: function(args, kwargs) {
var book = getBook(args, kwargs);
return Parse.parseConfig(book)
.then(function(resultBook) {
return Plugins.installPlugins(resultBook);
});
}
};
+31
View File
@@ -0,0 +1,31 @@
var Logger = require('../utils/logger');
var logOptions = {
name: 'log',
description: 'Minimum log level to display',
values: Logger.LEVELS
.keySeq()
.map(function(s) {
return s.toLowerCase();
}).toJS(),
defaults: 'info'
};
var formatOption = {
name: 'format',
description: 'Format to build to',
values: ['website', 'json', 'ebook'],
defaults: 'website'
};
var timingOption = {
name: 'timing',
description: 'Print timing debug information',
defaults: false
};
module.exports = {
log: logOptions,
format: formatOption,
timing: timingOption
};
+79
View File
@@ -0,0 +1,79 @@
var options = require('./options');
var getBook = require('./getBook');
var Parse = require('../parse');
function printBook(book) {
var logger = book.getLogger();
var config = book.getConfig();
var configFile = config.getFile();
var summary = book.getSummary();
var summaryFile = summary.getFile();
var readme = book.getReadme();
var readmeFile = readme.getFile();
var glossary = book.getGlossary();
var glossaryFile = glossary.getFile();
if (configFile.exists()) {
logger.info.ln('Configuration file is', configFile.getPath());
}
if (readmeFile.exists()) {
logger.info.ln('Introduction file is', readmeFile.getPath());
}
if (glossaryFile.exists()) {
logger.info.ln('Glossary file is', glossaryFile.getPath());
}
if (summaryFile.exists()) {
logger.info.ln('Table of Contents file is', summaryFile.getPath());
}
}
function printMultingualBook(book) {
var logger = book.getLogger();
var languages = book.getLanguages();
var books = book.getBooks();
logger.info.ln(languages.size + ' languages');
languages.forEach(function(lang) {
logger.info.ln('Language:', lang.getTitle());
printBook(books.get(lang.getID()));
logger.info.ln('');
});
}
module.exports = {
name: 'parse [book]',
description: 'parse and print debug information about a book',
options: [
options.log
],
exec: function(args, kwargs) {
var book = getBook(args, kwargs);
var logger = book.getLogger();
return Parse.parseBook(book)
.then(function(resultBook) {
var rootFolder = book.getRoot();
var contentFolder = book.getContentRoot();
logger.info.ln('Book located in:', rootFolder);
if (contentFolder != rootFolder) {
logger.info.ln('Content located in:', contentFolder);
}
if (resultBook.isMultilingual()) {
printMultingualBook(resultBook);
} else {
printBook(resultBook);
}
});
}
};
+140
View File
@@ -0,0 +1,140 @@
/* eslint-disable no-console */
var tinylr = require('tiny-lr');
var Parse = require('../parse');
var Output = require('../output');
var ConfigModifier = require('../modifiers').Config;
var Promise = require('../utils/promise');
var options = require('./options');
var getBook = require('./getBook');
var getOutputFolder = require('./getOutputFolder');
var Server = require('./server');
var watch = require('./watch');
var server, lrServer, lrPath;
function waitForCtrlC() {
var d = Promise.defer();
process.on('SIGINT', function() {
d.resolve();
});
return d.promise;
}
function generateBook(args, kwargs) {
var port = kwargs.port;
var outputFolder = getOutputFolder(args);
var book = getBook(args, kwargs);
var Generator = Output.getGenerator(kwargs.format);
var hasWatch = kwargs['watch'];
var hasLiveReloading = kwargs['live'];
// Stop server if running
if (server.isRunning()) console.log('Stopping server');
return server.stop()
.then(function() {
return Parse.parseBook(book)
.then(function(resultBook) {
if (hasLiveReloading) {
// Enable livereload plugin
resultBook = ConfigModifier.addPlugin(resultBook, 'livereload');
}
return Output.generate(Generator, resultBook, {
root: outputFolder
});
});
})
.then(function() {
console.log();
console.log('Starting server ...');
return server.start(outputFolder, port);
})
.then(function() {
console.log('Serving book on http://localhost:'+port);
if (lrPath && hasLiveReloading) {
// trigger livereload
lrServer.changed({
body: {
files: [lrPath]
}
});
}
})
.then(function() {
if (!hasWatch) {
return waitForCtrlC();
}
return watch(book.getRoot())
.then(function(filepath) {
// set livereload path
lrPath = filepath;
console.log('Restart after change in file', filepath);
console.log('');
return generateBook(args, kwargs);
});
});
}
module.exports = {
name: 'serve [book] [output]',
description: 'serve the book as a website for testing',
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 file watcher and live reloading',
defaults: true
},
{
name: 'live',
description: 'Enable live reloading',
defaults: true
},
options.log,
options.format
],
exec: function(args, kwargs) {
server = new Server();
var hasWatch = kwargs['watch'];
var hasLiveReloading = kwargs['live'];
return Promise()
.then(function() {
if (!hasWatch || !hasLiveReloading) {
return;
}
lrServer = tinylr({});
return Promise.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('');
});
})
.then(function() {
return generateBook(args, kwargs);
});
}
};
+17 -4
View File
@@ -6,20 +6,28 @@ var url = require('url');
var Promise = require('../utils/promise');
var Server = function() {
function Server() {
this.running = null;
this.dir = null;
this.port = 0;
this.sockets = [];
};
}
util.inherits(Server, events.EventEmitter);
// Return true if the server is running
/**
Return true if the server is running
@return {Boolean}
*/
Server.prototype.isRunning = function() {
return !!this.running;
};
// Stop the server
/**
Stop the server
@return {Promise}
*/
Server.prototype.stop = function() {
var that = this;
if (!this.isRunning()) return Promise();
@@ -40,6 +48,11 @@ Server.prototype.stop = function() {
return d.promise;
};
/**
Start the server
@return {Promise}
*/
Server.prototype.start = function(dir, port) {
var that = this, pre = Promise();
port = port || 8004;
+8 -4
View File
@@ -1,21 +1,25 @@
var _ = require('lodash');
var path = require('path');
var chokidar = require('chokidar');
var Promise = require('../utils/promise');
var parsers = require('../parsers');
// Watch a folder and resolve promise once a file is modified
/**
Watch a folder and resolve promise once a file is modified
@param {String} dir
@return {Promise}
*/
function watch(dir) {
var d = Promise.defer();
dir = path.resolve(dir);
var toWatch = [
'book.json', 'book.js'
'book.json', 'book.js', '_layouts/**'
];
// Watch all parsable files
_.each(parsers.extensions, function(ext) {
parsers.extensions.forEach(function(ext) {
toWatch.push('**/*'+ext);
});
-132
View File
@@ -1,132 +0,0 @@
var _ = require('lodash');
var semver = require('semver');
var gitbook = require('../gitbook');
var Promise = require('../utils/promise');
var validator = require('./validator');
var plugins = require('./plugins');
// Config files to tested (sorted)
var CONFIG_FILES = [
'book.js',
'book.json'
];
/*
Config is an interface for the book's configuration stored in "book.json" (or "book.js")
*/
function Config(book, baseConfig) {
this.book = book;
this.fs = book.fs;
this.log = book.log;
this.path = '';
this.baseConfig = baseConfig || {};
this.replace({});
}
// Load configuration of the book
// and verify that the configuration is satisfying
Config.prototype.load = function() {
var that = this;
var isLanguageBook = this.book.isLanguageBook();
// Try all potential configuration file
return Promise.some(CONFIG_FILES, function(filename) {
that.log.debug.ln('try loading configuration from', filename);
return that.fs.loadAsObject(that.book.resolve(filename))
.then(function(_config) {
that.log.debug.ln('configuration loaded from', filename);
that.path = filename;
return that.replace(_config);
})
.fail(function(err) {
if (err.code != 'MODULE_NOT_FOUND') throw(err);
else return Promise(false);
});
})
.then(function() {
if (!isLanguageBook) {
if (!gitbook.satisfies(that.options.gitbook)) {
throw new Error('GitBook version doesn\'t satisfy version required by the book: '+that.options.gitbook);
}
if (that.options.gitbook != '*' && !semver.satisfies(semver.inc(gitbook.version, 'patch'), that.options.gitbook)) {
that.log.warn.ln('gitbook version specified in your book.json might be too strict for future patches, \''+(_.first(gitbook.version.split('.'))+'.x.x')+'\' is more adequate');
}
that.options.plugins = plugins.toList(that.options.plugins);
} else {
// Multilingual book should inherits the plugins list from parent
that.options.plugins = that.book.parent.config.get('plugins');
}
that.options.gitbook = gitbook.version;
});
};
// Replace the whole configuration
Config.prototype.replace = function(options) {
var that = this;
// Extend base config
options = _.defaults(_.cloneDeep(options), this.baseConfig);
// Validate the config
this.options = validator.validate(options);
// options.input == book.root
Object.defineProperty(this.options, 'input', {
get: function () {
return that.book.root;
}
});
// options.originalInput == book.parent.root
Object.defineProperty(this.options, 'originalInput', {
get: function () {
return that.book.parent? that.book.parent.root : undefined;
}
});
};
// Return true if book has a configuration file
Config.prototype.exists = function() {
return Boolean(this.path);
};
// Return path to a structure file
// Strip the extension by default
Config.prototype.getStructure = function(name, dontStripExt) {
var filename = this.options.structure[name];
if (dontStripExt) return filename;
filename = filename.split('.').slice(0, -1).join('.');
return filename;
};
// Return a configuration using a key and a default value
Config.prototype.get = function(key, def) {
return _.get(this.options, key, def);
};
// Update a configuration
Config.prototype.set = function(key, value) {
return _.set(this.options, key, value);
};
// Return a dump of the configuration
Config.prototype.dump = function() {
return _.cloneDeep(this.options);
};
// Return templating context
Config.prototype.getContext = function() {
return {
config: this.book.config.dump()
};
};
module.exports = Config;
-67
View File
@@ -1,67 +0,0 @@
var _ = require('lodash');
// Default plugins added to each books
var DEFAULT_PLUGINS = ['highlight', 'search', 'sharing', 'fontsettings', 'theme-default'];
// Return true if a plugin is a default plugin
function isDefaultPlugin(name, version) {
return _.contains(DEFAULT_PLUGINS, name);
}
// 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('@');
var name = parts[0];
var version = parts[1];
return {
'name': name,
'version': version // 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
_.each(DEFAULT_PLUGINS, function(plugin) {
if (_.find(plugins, { name: plugin })) {
return;
}
plugins.push({
'name': plugin
});
});
// Remove plugin that start with '-'
plugins = _.filter(plugins, function(plugin) {
return !_.contains(toremove, plugin.name) && !(plugin.name.length > 0 && plugin.name[0] == '-');
});
// Remove duplicates
plugins = _.uniq(plugins, 'name');
return plugins;
}
module.exports = {
isDefaultPlugin: isDefaultPlugin,
toList: normalizePluginsList
};
+6
View File
@@ -0,0 +1,6 @@
var Immutable = require('immutable');
var jsonSchemaDefaults = require('json-schema-defaults');
var schema = require('./configSchema');
module.exports = Immutable.fromJS(jsonSchemaDefaults(schema));
+5
View File
@@ -0,0 +1,5 @@
// Configuration files to test (sorted)
module.exports = [
'book.js',
'book.json'
];
+51
View File
@@ -0,0 +1,51 @@
var Immutable = require('immutable');
var TemplateBlock = require('../models/templateBlock');
module.exports = Immutable.Map({
html: TemplateBlock({
name: 'html',
process: function(blk) {
return blk;
}
}),
code: TemplateBlock({
name: 'code',
process: function(blk) {
return {
html: false,
body: blk.body
};
}
}),
markdown: TemplateBlock({
name: 'markdown',
process: function(blk) {
return this.book.renderInline('markdown', blk.body)
.then(function(out) {
return { body: out };
});
}
}),
asciidoc: TemplateBlock({
name: 'asciidoc',
process: function(blk) {
return this.book.renderInline('asciidoc', blk.body)
.then(function(out) {
return { body: out };
});
}
}),
markup: TemplateBlock({
name: 'markup',
process: function(blk) {
return this.book.renderInline(this.ctx.file.type, blk.body)
.then(function(out) {
return { body: out };
});
}
})
});
@@ -1,7 +1,7 @@
var Immutable = require('immutable');
var moment = require('moment');
module.exports = {
module.exports = Immutable.Map({
// Format a date
// ex: 'MMMM Do YYYY, h:mm:ss a
date: function(time, format) {
@@ -12,4 +12,4 @@ module.exports = {
dateFromNow: function(time) {
return moment(time).fromNow();
}
};
});
+14
View File
@@ -0,0 +1,14 @@
var Immutable = require('immutable');
/*
List of default plugins for all books,
default plugins should be installed in node dependencies of GitBook
*/
module.exports = Immutable.List([
'highlight',
'search',
'lunr',
'sharing',
'fontsettings',
'theme-default'
]);
+6
View File
@@ -0,0 +1,6 @@
// Files containing ignore pattner (sorted by priority)
module.exports = [
'.ignore',
'.gitignore',
'.bookignore'
];
+2
View File
@@ -0,0 +1,2 @@
module.exports = '_assets';
+8
View File
@@ -0,0 +1,8 @@
module.exports = [
'init',
'finish',
'finish:before',
'config',
'page',
'page:before'
];
+5
View File
@@ -0,0 +1,5 @@
/*
All GitBook plugins are NPM packages starting with this prefix.
*/
module.exports = 'gitbook-plugin-';
+6
View File
@@ -0,0 +1,6 @@
var Immutable = require('immutable');
module.exports = Immutable.List([
'js',
'css'
]);
+2
View File
@@ -0,0 +1,2 @@
module.exports = '_layouts';
+83
View File
@@ -0,0 +1,83 @@
jest.autoMockOff();
describe('MockFS', function() {
var createMockFS = require('../mock');
var fs = createMockFS({
'README.md': 'Hello World',
'SUMMARY.md': '# Summary',
'folder': {
'test.md': 'Cool',
'folder2': {
'hello.md': 'Hello',
'world.md': 'World'
}
}
});
describe('exists', function() {
pit('must return true for a file', function() {
return fs.exists('README.md')
.then(function(result) {
expect(result).toBeTruthy();
});
});
pit('must return false for a non existing file', function() {
return fs.exists('README_NOTEXISTS.md')
.then(function(result) {
expect(result).toBeFalsy();
});
});
pit('must return true for a directory', function() {
return fs.exists('folder')
.then(function(result) {
expect(result).toBeTruthy();
});
});
pit('must return true for a deep file', function() {
return fs.exists('folder/test.md')
.then(function(result) {
expect(result).toBeTruthy();
});
});
pit('must return true for a deep file (2)', function() {
return fs.exists('folder/folder2/hello.md')
.then(function(result) {
expect(result).toBeTruthy();
});
});
});
describe('readAsString', function() {
pit('must return content for a file', function() {
return fs.readAsString('README.md')
.then(function(result) {
expect(result).toBe('Hello World');
});
});
pit('must return content for a deep file', function() {
return fs.readAsString('folder/test.md')
.then(function(result) {
expect(result).toBe('Cool');
});
});
});
describe('readDir', function() {
pit('must return content for a directory', function() {
return fs.readDir('./')
.then(function(files) {
expect(files.size).toBe(3);
expect(files.includes('README.md')).toBeTruthy();
expect(files.includes('SUMMARY.md')).toBeTruthy();
expect(files.includes('folder/')).toBeTruthy();
});
});
});
});
-106
View File
@@ -1,106 +0,0 @@
var _ = require('lodash');
var path = require('path');
var Promise = require('../utils/promise');
/*
A filesystem is an interface to read files
GitBook can works with a virtual filesystem, for example in the browser.
*/
// .readdir return files/folder as a list of string, folder ending with '/'
function pathIsFolder(filename) {
return _.last(filename) == '/' || _.last(filename) == '\\';
}
function FS() {
}
// Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
FS.prototype.exists = function(filename) {
// To implement for each fs
};
// Read a file and returns a promise with the content as a buffer
FS.prototype.read = function(filename) {
// To implement for each fs
};
// Read stat infos about a file
FS.prototype.stat = function(filename) {
// To implement for each fs
};
// List files/directories in a directory
FS.prototype.readdir = function(folder) {
// To implement for each fs
};
// These methods don't require to be redefined, by default it uses .exists, .read, .write, .list
// For optmization, it can be redefined:
// List files in a directory
FS.prototype.listFiles = function(folder) {
return this.readdir(folder)
.then(function(files) {
return _.reject(files, pathIsFolder);
});
};
// List all files in the fs
FS.prototype.listAllFiles = function(folder) {
var that = this;
return this.readdir(folder)
.then(function(files) {
return _.reduce(files, function(prev, file) {
return prev.then(function(output) {
var isDirectory = pathIsFolder(file);
if (!isDirectory) {
output.push(file);
return output;
} else {
return that.listAllFiles(path.join(folder, file))
.then(function(files) {
return output.concat(_.map(files, function(_file) {
return path.join(file, _file);
}));
});
}
});
}, Promise([]));
});
};
// Read a file as a string (utf-8)
FS.prototype.readAsString = function(filename) {
return this.read(filename)
.then(function(buf) {
return buf.toString('utf-8');
});
};
// Find a file in a folder (case incensitive)
// Return the real filename
FS.prototype.findFile = function findFile(root, filename) {
return this.listFiles(root)
.then(function(files) {
return _.find(files, function(file) {
return (file.toLowerCase() == filename.toLowerCase());
});
});
};
// Load a JSON file
// By default, fs only supports JSON
FS.prototype.loadAsObject = function(filename) {
return this.readAsString(filename)
.then(function(str) {
return JSON.parse(str);
});
};
module.exports = FS;
+95
View File
@@ -0,0 +1,95 @@
var path = require('path');
var is = require('is');
var Buffer = require('buffer').Buffer;
var Immutable = require('immutable');
var FS = require('../models/fs');
var error = require('../utils/error');
/**
Create a fake filesystem for unit testing GitBook.
@param {Map<String:String|Map>}
*/
function createMockFS(files) {
files = Immutable.fromJS(files);
var mtime = new Date();
function getFile(filePath) {
var parts = path.normalize(filePath).split(path.sep);
return parts.reduce(function(list, part, i) {
if (!list) return null;
var file;
if (!part || part === '.') file = list;
else file = list.get(part);
if (!file) return null;
if (is.string(file)) {
if (i === (parts.length - 1)) return file;
else return null;
}
return file;
}, files);
}
function fsExists(filePath) {
return Boolean(getFile(filePath) !== null);
}
function fsReadFile(filePath) {
var file = getFile(filePath);
if (!is.string(file)) {
throw error.FileNotFoundError({
filename: filePath
});
}
return new Buffer(file, 'utf8');
}
function fsStatFile(filePath) {
var file = getFile(filePath);
if (!file) {
throw error.FileNotFoundError({
filename: filePath
});
}
return {
mtime: mtime
};
}
function fsReadDir(filePath) {
var dir = getFile(filePath);
if (!dir || is.string(dir)) {
throw error.FileNotFoundError({
filename: filePath
});
}
return dir
.map(function(content, name) {
if (!is.string(content)) {
name = name + '/';
}
return name;
})
.valueSeq();
}
return FS.create({
root: '',
fsExists: fsExists,
fsReadFile: fsReadFile,
fsStatFile: fsStatFile,
fsReadDir: fsReadDir
});
}
module.exports = createMockFS;
+27 -51
View File
@@ -1,36 +1,16 @@
var _ = require('lodash');
var util = require('util');
var path = require('path');
var Immutable = require('immutable');
var fresh = require('fresh-require');
var fs = require('../utils/fs');
var Promise = require('../utils/promise');
var BaseFS = require('./');
var FS = require('../models/fs');
function NodeFS() {
BaseFS.call(this);
}
util.inherits(NodeFS, BaseFS);
// Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
NodeFS.prototype.exists = function(filename) {
return fs.exists(filename);
};
// Read a file and returns a promise with the content as a buffer
NodeFS.prototype.read = function(filename) {
return fs.readFile(filename);
};
// Read stat infos about a file
NodeFS.prototype.stat = function(filename) {
return fs.stat(filename);
};
// List files in a directory
NodeFS.prototype.readdir = function(folder) {
function fsReadDir(folder) {
return fs.readdir(folder)
.then(function(files) {
return _.chain(files)
files = Immutable.List(files);
return files
.map(function(file) {
if (file == '.' || file == '..') return;
@@ -38,29 +18,25 @@ NodeFS.prototype.readdir = function(folder) {
if (stat.isDirectory()) file = file + path.sep;
return file;
})
.compact()
.value();
.filter(function(file) {
return Boolean(file);
});
});
}
function fsLoadObject(filename) {
return fresh(filename, require);
}
module.exports = function createNodeFS(root) {
return FS.create({
root: root,
fsExists: fs.exists,
fsReadFile: fs.readFile,
fsStatFile: fs.stat,
fsReadDir: fsReadDir,
fsLoadObject: fsLoadObject,
fsReadAsStream: fs.readStream
});
};
// Load a JSON/JS file
NodeFS.prototype.loadAsObject = function(filename) {
return Promise()
.then(function() {
var jsFile;
try {
jsFile = require.resolve(filename);
// Invalidate node.js cache for livreloading
delete require.cache[jsFile];
return require(jsFile);
}
catch(err) {
return Promise.reject(err);
}
});
};
module.exports = NodeFS;
+8 -13
View File
@@ -6,8 +6,13 @@ var VERSION_STABLE = VERSION.replace(/\-(\S+)/g, '');
var START_TIME = new Date();
// Verify that this gitbook version satisfies a requirement
// We can't directly use samver.satisfies since it will break all plugins when gitbook version is a prerelease (beta, alpha)
/**
Verify that this gitbook version satisfies a requirement
We can't directly use samver.satisfies since it will break all plugins when gitbook version is a prerelease (beta, alpha)
@param {String} condition
@return {Boolean}
*/
function satisfies(condition) {
// Test with real version
if (semver.satisfies(VERSION, condition)) return true;
@@ -16,18 +21,8 @@ function satisfies(condition) {
return semver.satisfies(VERSION_STABLE, condition);
}
// Return templating/json context for gitbook itself
function getContext() {
return {
gitbook: {
version: pkg.version,
time: START_TIME
}
};
}
module.exports = {
version: pkg.version,
satisfies: satisfies,
getContext: getContext
START_TIME: START_TIME
};
+9 -6
View File
@@ -1,7 +1,10 @@
var Book = require('./book');
var cli = require('./cli');
var extend = require('extend');
module.exports = {
Book: Book,
commands: cli.commands
};
var common = require('./browser');
module.exports = extend({
initBook: require('./init'),
createNodeFS: require('./fs/node'),
Output: require('./output'),
commands: require('./cli')
}, common);
+68 -52
View File
@@ -1,66 +1,82 @@
var path = require('path');
var createNodeFS = require('./fs/node');
var fs = require('./utils/fs');
var Promise = require('./utils/promise');
var File = require('./models/file');
var Readme = require('./models/readme');
var Book = require('./models/book');
var Parse = require('./parse');
// Initialize folder structure for a book
// Read SUMMARY to created the right chapter
function initBook(book) {
var extensionToUse = '.md';
/**
Initialize folder structure for a book
Read SUMMARY to created the right chapter
book.log.info.ln('init book at', book.root);
return fs.mkdirp(book.root)
@param {Book}
@param {String}
@return {Promise}
*/
function initBook(rootFolder) {
var extension = '.md';
return fs.mkdirp(rootFolder)
// Parse the summary and readme
.then(function() {
return book.config.load();
})
.then(function() {
book.log.info.ln('detect structure from SUMMARY (if it exists)');
return book.summary.load();
})
.then(function() {
var summary = book.summary.path || 'SUMMARY.md';
var articles = book.summary.flatten();
var fs = createNodeFS(rootFolder);
var book = Book.createForFS(fs);
// Use extension of summary
extensionToUse = path.extname(summary);
return Parse.parseReadme(book)
// Readme doesn't have a path
if (!articles[0].path) {
articles[0].path = 'README' + extensionToUse;
}
// Summary doesn't exists? create one
if (!book.summary.path) {
articles.push({
title: 'Summary',
path: 'SUMMARY'+extensionToUse
});
}
// Create files that don't exist
return Promise.serie(articles, function(article) {
if (!article.path) return;
var absolutePath = book.resolve(article.path);
return fs.exists(absolutePath)
.then(function(exists) {
if(exists) {
book.log.info.ln('found', article.path);
return;
} else {
book.log.info.ln('create', article.path);
}
return fs.mkdirp(path.dirname(absolutePath))
.then(function() {
return fs.writeFile(absolutePath, '# '+article.title+'\n\n');
});
});
// Setup default readme if doesn't found one
.fail(function() {
var readmeFile = File.createWithFilepath('README' + extension);
var readme = Readme.create(readmeFile);
return book.setReadme(readme);
});
})
.then(function() {
book.log.info.ln('initialization is finished');
.then(Parse.parseSummary)
.then(function(book) {
var logger = book.getLogger();
var summary = book.getSummary();
var summaryFile = summary.getFile();
var summaryFilename = summaryFile.getPath() || ('SUMMARY' + extension);
var articles = summary.getArticlesAsList();
// Write pages
return Promise.forEach(articles, function(article) {
var articlePath = article.getPath();
var filePath = articlePath? path.join(rootFolder, articlePath) : null;
if (!filePath) {
return;
}
return fs.assertFile(filePath, function() {
return fs.ensureFile(filePath)
.then(function() {
logger.info.ln('create', article.getPath());
return fs.writeFile(filePath, '# ' + article.getTitle() + '\n\n');
});
});
})
// Write summary
.then(function() {
var filePath = path.join(rootFolder, summaryFilename);
return fs.ensureFile(filePath)
.then(function() {
logger.info.ln('create ' + path.basename(filePath));
return fs.writeFile(filePath, summary.toText(extension));
});
})
// Log end
.then(function() {
logger.info.ln('initialization is finished');
});
});
}
+35
View File
@@ -0,0 +1,35 @@
var extend = require('extend');
var gitbook = require('../gitbook');
var encodeSummary = require('./encodeSummary');
var encodeGlossary = require('./encodeGlossary');
var encodeReadme = require('./encodeReadme');
/**
Encode a book to JSON
@param {Book}
@return {Object}
*/
function encodeBookToJson(book) {
var config = book.getConfig();
var language = book.getLanguage();
var variables = config.getValue('variables', {});
return {
summary: encodeSummary(book.getSummary()),
glossary: encodeGlossary(book.getGlossary()),
readme: encodeReadme(book.getReadme()),
config: book.getConfig().getValues().toJS(),
gitbook: {
version: gitbook.version,
time: gitbook.START_TIME
},
book: extend({
language: language? language : undefined
}, variables.toJS())
};
}
module.exports = encodeBookToJson;
+22
View File
@@ -0,0 +1,22 @@
var encodeBook = require('./encodeBook');
var encodePage = require('./encodePage');
var encodeFile = require('./encodeFile');
/**
Return a JSON representation of a book with a specific file
@param {Book} output
@param {Page} page
@return {Object}
*/
function encodeBookWithPage(book, page) {
var file = page.getFile();
var result = encodeBook(book);
result.page = encodePage(page, book.getSummary());
result.file = encodeFile(file);
return result;
}
module.exports = encodeBookWithPage;
+21
View File
@@ -0,0 +1,21 @@
/**
Return a JSON representation of a file
@param {File} file
@return {Object}
*/
function encodeFileToJson(file) {
var filePath = file.getPath();
if (!filePath) {
return undefined;
}
return {
path: filePath,
mtime: file.getMTime(),
type: file.getType()
};
}
module.exports = encodeFileToJson;
+21
View File
@@ -0,0 +1,21 @@
var encodeFile = require('./encodeFile');
var encodeGlossaryEntry = require('./encodeGlossaryEntry');
/**
Encode a glossary to JSON
@param {Glossary}
@return {Object}
*/
function encodeGlossary(glossary) {
var file = glossary.getFile();
var entries = glossary.getEntries();
return {
file: encodeFile(file),
entries: entries
.map(encodeGlossaryEntry).toJS()
};
}
module.exports = encodeGlossary;
+16
View File
@@ -0,0 +1,16 @@
/**
Encode a SummaryArticle to JSON
@param {GlossaryEntry}
@return {Object}
*/
function encodeGlossaryEntry(entry) {
return {
id: entry.getID(),
name: entry.getName(),
description: entry.getDescription()
};
}
module.exports = encodeGlossaryEntry;
+25
View File
@@ -0,0 +1,25 @@
var encodeBook = require('./encodeBook');
/**
Encode an output to JSON
@param {Output}
@return {Object}
*/
function encodeOutputToJson(output) {
var book = output.getBook();
var generator = output.getGenerator();
var options = output.getOptions();
var result = encodeBook(book);
result.output = {
name: generator
};
result.options = options.toJS();
return result;
}
module.exports = encodeOutputToJson;
+39
View File
@@ -0,0 +1,39 @@
var encodeSummaryArticle = require('./encodeSummaryArticle');
/**
Return a JSON representation of a page
@param {Page} page
@param {Summary} summary
@return {Object}
*/
function encodePage(page, summary) {
var file = page.getFile();
var attributes = page.getAttributes();
var article = summary.getByPath(file.getPath());
var result = attributes.toJS();
if (article) {
result.title = article.getTitle();
result.level = article.getLevel();
result.depth = article.getDepth();
var nextArticle = summary.getNextArticle(article);
if (nextArticle) {
result.next = encodeSummaryArticle(nextArticle);
}
var prevArticle = summary.getPrevArticle(article);
if (prevArticle) {
result.previous = encodeSummaryArticle(prevArticle);
}
}
result.content = page.getContent();
result.dir = page.getDir();
return result;
}
module.exports = encodePage;
+17
View File
@@ -0,0 +1,17 @@
var encodeFile = require('./encodeFile');
/**
Encode a readme to JSON
@param {Readme}
@return {Object}
*/
function encodeReadme(readme) {
var file = readme.getFile();
return {
file: encodeFile(file)
};
}
module.exports = encodeReadme;
+20
View File
@@ -0,0 +1,20 @@
var encodeFile = require('./encodeFile');
var encodeSummaryPart = require('./encodeSummaryPart');
/**
Encode a summary to JSON
@param {Summary}
@return {Object}
*/
function encodeSummary(summary) {
var file = summary.getFile();
var parts = summary.getParts();
return {
file: encodeFile(file),
parts: parts.map(encodeSummaryPart).toJS()
};
}
module.exports = encodeSummary;
+28
View File
@@ -0,0 +1,28 @@
/**
Encode a SummaryArticle to JSON
@param {SummaryArticle}
@return {Object}
*/
function encodeSummaryArticle(article, recursive) {
var articles = undefined;
if (recursive !== false) {
articles = article.getArticles()
.map(encodeSummaryArticle)
.toJS();
}
return {
title: article.getTitle(),
level: article.getLevel(),
depth: article.getDepth(),
anchor: article.getAnchor(),
url: article.getUrl(),
path: article.getPath(),
ref: article.getRef(),
articles: articles
};
}
module.exports = encodeSummaryArticle;
+17
View File
@@ -0,0 +1,17 @@
var encodeSummaryArticle = require('./encodeSummaryArticle');
/**
Encode a SummaryPart to JSON
@param {SummaryPart}
@return {Object}
*/
function encodeSummaryPart(part) {
return {
title: part.getTitle(),
articles: part.getArticles()
.map(encodeSummaryArticle).toJS()
};
}
module.exports = encodeSummaryPart;
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
encodeOutput: require('./encodeOutput'),
encodeBookWithPage: require('./encodeBookWithPage'),
encodeBook: require('./encodeBook'),
encodeFile: require('./encodeFile'),
encodePage: require('./encodePage'),
encodeSummary: require('./encodeSummary'),
encodeSummaryArticle: require('./encodeSummaryArticle'),
encodeReadme: require('./encodeReadme')
};
+63
View File
@@ -0,0 +1,63 @@
jest.autoMockOff();
var Immutable = require('immutable');
describe('Config', function() {
var Config = require('../config');
var config = Config.createWithValues({
hello: {
world: 1,
test: 'Hello',
isFalse: false
}
});
describe('getValue', function() {
it('must return value as immutable', function() {
var value = config.getValue('hello');
expect(Immutable.Map.isMap(value)).toBeTruthy();
});
it('must return deep value', function() {
var value = config.getValue('hello.world');
expect(value).toBe(1);
});
it('must return default value if non existant', function() {
var value = config.getValue('hello.nonExistant', 'defaultValue');
expect(value).toBe('defaultValue');
});
it('must not return default value for falsy values', function() {
var value = config.getValue('hello.isFalse', 'defaultValue');
expect(value).toBe(false);
});
});
describe('setValue', function() {
it('must set value as immutable', function() {
var testConfig = config.setValue('hello', {
'cool': 1
});
var value = testConfig.getValue('hello');
expect(Immutable.Map.isMap(value)).toBeTruthy();
expect(value.size).toBe(1);
expect(value.has('cool')).toBeTruthy();
});
it('must set deep value', function() {
var testConfig = config.setValue('hello.world', 2);
var hello = testConfig.getValue('hello');
var world = testConfig.getValue('hello.world');
expect(Immutable.Map.isMap(hello)).toBeTruthy();
expect(hello.size).toBe(3);
expect(world).toBe(2);
});
});
});
+42
View File
@@ -0,0 +1,42 @@
jest.autoMockOff();
describe('Glossary', function() {
var File = require('../file');
var Glossary = require('../glossary');
var GlossaryEntry = require('../glossaryEntry');
var glossary = Glossary.createFromEntries(File(), [
{
name: 'Hello World',
description: 'Awesome!'
},
{
name: 'JavaScript',
description: 'This is a cool language'
}
]);
describe('createFromEntries', function() {
it('must add all entries', function() {
var entries = glossary.getEntries();
expect(entries.size).toBe(2);
});
it('must add entries as GlossaryEntries', function() {
var entries = glossary.getEntries();
var entry = entries.get('hello-world');
expect(entry instanceof GlossaryEntry).toBeTruthy();
});
});
describe('toText', function() {
pit('return as markdown', function() {
return glossary.toText('.md')
.then(function(text) {
expect(text).toContain('# Glossary');
});
});
});
});
+17
View File
@@ -0,0 +1,17 @@
jest.autoMockOff();
describe('GlossaryEntry', function() {
var GlossaryEntry = require('../glossaryEntry');
describe('getID', function() {
it('must return a normalized ID', function() {
var entry = new GlossaryEntry({
name: 'Hello World'
});
expect(entry.getID()).toBe('hello-world');
});
});
});
+27
View File
@@ -0,0 +1,27 @@
describe('Plugin', function() {
var Plugin = require('../plugin');
describe('createFromString', function() {
it('must parse name', function() {
var plugin = Plugin.createFromString('hello');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('*');
});
it('must parse version', function() {
var plugin = Plugin.createFromString('hello@1.0.0');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('1.0.0');
});
});
describe('isLoaded', function() {
it('must return false for empty plugin', function() {
var plugin = Plugin.createFromString('hello');
expect(plugin.isLoaded()).toBe(false);
});
});
});
+47
View File
@@ -0,0 +1,47 @@
describe('PluginDependency', function() {
var PluginDependency = require('../pluginDependency');
describe('createFromString', function() {
it('must parse name', function() {
var plugin = PluginDependency.createFromString('hello');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('*');
});
it('must parse state', function() {
var plugin = PluginDependency.createFromString('-hello');
expect(plugin.getName()).toBe('hello');
expect(plugin.isEnabled()).toBe(false);
});
describe('Version', function() {
it('must parse version', function() {
var plugin = PluginDependency.createFromString('hello@1.0.0');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('1.0.0');
});
it('must parse semver', function() {
var plugin = PluginDependency.createFromString('hello@>=4.0.0');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('>=4.0.0');
});
});
describe('GIT Version', function() {
it('must handle HTTPS urls', function() {
var plugin = PluginDependency.createFromString('hello@git+https://github.com/GitbookIO/plugin-ga.git');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('git+https://github.com/GitbookIO/plugin-ga.git');
});
it('must handle SSH urls', function() {
var plugin = PluginDependency.createFromString('hello@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
});
});
});
});

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