Compare commits

...

8 Commits

Author SHA1 Message Date
Samy Pessé e436115a4a Bump version to 3.0.0-pre.2 2016-02-29 09:16:00 +01:00
Samy Pessé 1c61f93b5f Add simple test for exports 2016-02-29 09:14:43 +01:00
Samy Pessé 0494f7d1fd Fix #1165: correctly normalize path in summary to accept unicode 2016-02-28 21:54:03 +01:00
Samy Pessé 4aa66338fc Add init command 2016-02-28 14:47:34 +01:00
Samy Pessé 8cda844ef1 Export output for plugins context 2016-02-28 12:46:47 +01:00
Samy Pesse dde1ef92f0 Add doc for ls-remote 2016-02-27 22:47:42 +01:00
Samy Pesse 999f8b481f Improve setup doc 2016-02-27 22:45:30 +01:00
Samy Pessé 294951f22a Change note in changelog 2016-02-26 10:07:11 +01:00
15 changed files with 269 additions and 25 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 3.x.x (unreleased)
## 3.0.0 (pre)
- Summary can contain external links and anchors (Fix [#776](https://github.com/GitbookIO/gitbook/issues/776))
- Summary can contain differents entitled sections
- Glossary is generated as a normal page
+26 -2
View File
@@ -27,14 +27,38 @@ $ npm install gitbook-cli -g
`gitbook-cli` is an utility to install and use multiple versions of GitBook on the same system. It will automatically install the required version to build a book.
##### Using pre-releases
##### Create a book
GitBook can setup a boilerplate book:
```
$ gitbook init
```
If you wish to create the book into a new directory, you can do so by running `gitbook init ./directory`
Preview and serve your book using:
```
$ gitbook serve
```
or build the static website using:
```
$ gitbook build
```
##### Install pre-releases
`gitbook-cli` makes it easy to install and test other versions of GitBook with your book:
```
$ gitbook install beta
$ gitbook fetch beta
```
Use `gitbook ls-remote` to list remote versions available for install.
##### Debugging
You can use the options `--log=debug` and `--debug` to get better error messages (with stack trace). For example:
+17 -3
View File
@@ -6,7 +6,6 @@ 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,
@@ -37,8 +36,12 @@ function TOCArticle(def, parent) {
var parts = url.parse(this.ref);
if (!this.isExternal()) {
this.path = parts.pathname;
this.anchor = parts.hash;
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);
}
}
@@ -264,6 +267,17 @@ Summary.prototype.find = function(filter) {
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;
+21
View File
@@ -13,6 +13,7 @@ var error = require('./utils/error');
var Promise = require('./utils/promise');
var Logger = require('./utils/logger');
var parsers = require('./parsers');
var initBook = require('./init');
/*
@@ -92,7 +93,21 @@ function Book(opts) {
// 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');
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
@@ -354,5 +369,11 @@ Book.setup = function(fs, root, opts) {
});
};
// Initialize a book
Book.init = function(fs, root, opts) {
return Book.setup(fs, root, opts)
.then(initBook);
};
module.exports = Book;
+12
View File
@@ -7,6 +7,7 @@ var tinylr = require('tiny-lr');
var Promise = require('../utils/promise');
var PluginsManager = require('../plugins');
var Book = require('../book');
var initBook = require('../init');
var helper = require('./helper');
var Server = require('./server');
@@ -14,6 +15,17 @@ 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]',
+68
View File
@@ -0,0 +1,68 @@
var _ = require('lodash');
var path = require('path');
var fs = require('./utils/fs');
var Promise = require('./utils/promise');
// Initialize folder structure for a book
// Read SUMMARY to created the right chapter
function initBook(book) {
var extensionToUse = '.md';
book.log.info.ln('init book at', book.root);
return fs.mkdirp(book.root)
.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();
// Use extension of summary
extensionToUse = path.extname(summary);
// 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');
});
});
});
})
.then(function() {
book.log.info.ln('initialization is finished');
});
}
module.exports = initBook;
+1
View File
@@ -24,6 +24,7 @@ function Output(book, opts, parent) {
});
this.book = book;
book.output = this;
this.log = this.book.log;
// Create plugins manager
+1 -13
View File
@@ -7,19 +7,7 @@ var error = require('../utils/error');
*/
function pluginCtx(plugin) {
var book = plugin.book;
var ctx = {
config: book.config,
log: plugin.log,
// Paths
resolve: book.resolve
};
// Deprecation
error.deprecateField(ctx, 'options', book.config.dump(), '"options" property is deprecated, use config.get(key) instead');
// Loop for template filters/blocks
error.deprecateField(ctx, 'book', ctx, '"book" property is deprecated, use "this" directly instead');
var ctx = book;
return ctx;
}
+2 -2
View File
@@ -27,7 +27,7 @@ function isAnchor(href) {
// Normalize a path to be a link
function normalize(s) {
return s.replace(/\\/g, '/');
return path.normalize(s).replace(/\\/g, '/');
}
// Convert relative to absolute path
@@ -36,7 +36,7 @@ function normalize(s) {
function toAbsolute(_href, dir, outdir) {
if (isExternal(_href)) return _href;
outdir = outdir == undefined? dir : outdir;
_href = normalize(_href);
dir = normalize(dir);
outdir = normalize(outdir);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "3.0.0-pre.1",
"version": "3.0.0-pre.2",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
+6
View File
@@ -1,4 +1,7 @@
// Module API
require('./exports');
// Utilities
require('./location');
require('./paths');
@@ -25,3 +28,6 @@ require('./assets-inliner');
require('./output-json');
require('./output-website');
require('./output-ebook');
// Misc
require('./init');
+16 -3
View File
@@ -1,18 +1,31 @@
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var cheerio = require('cheerio');
var should = require('should');
// Assertions to test if an Output has generated a file
should.Assertion.add('file', function(file, description) {
var rootFolder;
if (_.isFunction(this.obj.root)) {
rootFolder = this.obj.root();
} else {
rootFolder = this.obj;
}
this.params = {
actual: this.obj.root(),
actual: rootFolder,
operator: 'have file ' + file,
message: description
};
this.obj.should.have.property('resolve').which.is.a.Function;
this.assert(fs.existsSync(this.obj.resolve(file)));
if (_.isFunction(this.obj.resolve)) {
file = this.obj.resolve(file);
} else {
file = path.resolve(rootFolder, file);
}
this.assert(fs.existsSync(file));
});
should.Assertion.add('html', function(rules, description) {
+12
View File
@@ -0,0 +1,12 @@
var should = require('should');
var gitbook = require('../');
describe('Exports', function() {
it('should export the Book class', function() {
should(gitbook.Book).be.a.Function();
});
it('should export the list of commands', function() {
should(gitbook.commands).be.an.Array();
});
});
+57
View File
@@ -0,0 +1,57 @@
var Book = require('../lib/book');
var mock = require('./mock');
describe('Init', function() {
it('should create file according to summary', function() {
return mock.setupFS({
'SUMMARY.md': '# Summary\n\n'
+ '* [Hello](hello.md)\n'
+ '* [Hello 2](hello 2.md)\n'
})
.then(function(rootFolder) {
return Book.init(mock.fs, rootFolder, {
log: function() {}
})
.then(function() {
rootFolder.should.have.file('SUMMARY.md');
rootFolder.should.have.file('README.md');
rootFolder.should.have.file('hello.md');
rootFolder.should.have.file('hello 2.md');
});
})
});
it('should create file subfolder', function() {
return mock.setupFS({
'SUMMARY.md': '# Summary\n\n'
+ '* [Hello](test/hello.md)\n'
+ '* [Hello 2](test/test2/world.md)\n'
})
.then(function(rootFolder) {
return Book.init(mock.fs, rootFolder, {
log: function() {}
})
.then(function() {
rootFolder.should.have.file('README.md');
rootFolder.should.have.file('SUMMARY.md');
rootFolder.should.have.file('test/hello.md');
rootFolder.should.have.file('test/test2/world.md');
});
})
});
it('should create SUMMARY if non-existant', function() {
return mock.setupFS({})
.then(function(rootFolder) {
return Book.init(mock.fs, rootFolder, {
log: function() {}
})
.then(function() {
rootFolder.should.have.file('SUMMARY.md');
rootFolder.should.have.file('README.md');
});
})
});
});
+28
View File
@@ -59,6 +59,34 @@ describe('Summary / Table of contents', function() {
});
});
describe('Unicode summary', function() {
var book;
before(function() {
return mockSummary({
'SUMMARY.md': '# Summary\n\n'
+ '* [Hello](./hello world.md)\n'
+ '* [Spanish](./Descripción del problema.md)\n\n'
+ '* [Chinese](读了这本书.md)\n\n'
})
.then(function(_book) {
book = _book;
});
});
it('should accept article with spaces', function() {
should(book.summary.getArticle('hello world.md')).be.ok();
});
it('should accept article with chinese filename', function() {
should(book.summary.getArticle('读了这本书.md')).be.ok();
});
it('should accept article with accents', function() {
should(book.summary.getArticle('Descripción del problema.md')).be.ok();
});
});
describe('Non-empty summary list', function() {
var book;