summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamy Pessé <samypesse@gmail.com>2015-01-24 18:04:03 +0100
committerSamy Pessé <samypesse@gmail.com>2015-01-24 18:04:03 +0100
commit796dd4c13ac55e8b6232cbf38d49d9cf8c6676d2 (patch)
treeebbb0853571aec61384188402f673b9834f32a88
parente02f29e76392660b31be87063649abd4adb22826 (diff)
downloadgitbook-796dd4c13ac55e8b6232cbf38d49d9cf8c6676d2.zip
gitbook-796dd4c13ac55e8b6232cbf38d49d9cf8c6676d2.tar.gz
gitbook-796dd4c13ac55e8b6232cbf38d49d9cf8c6676d2.tar.bz2
Add base loader to resolve git dependencies
-rw-r--r--lib/template.js37
-rw-r--r--lib/utils/fs.js7
-rw-r--r--lib/utils/git.js110
-rw-r--r--package.json6
-rw-r--r--test/fixtures/test5/README.md1
5 files changed, 157 insertions, 4 deletions
diff --git a/lib/template.js b/lib/template.js
index 315a373..76bb9ab 100644
--- a/lib/template.js
+++ b/lib/template.js
@@ -1,15 +1,50 @@
var _ = require("lodash");
var Q = require("q");
+var path = require("path");
var nunjucks = require("nunjucks");
+var git = require("./utils/git");
+var fs = require("./utils/fs");
var pkg = require("../package.json");
+
+// The loader should handle relative and git url
+var BookLoader = nunjucks.Loader.extend({
+ async: true,
+
+ init: function(book) {
+ this.book = book;
+ },
+
+ getSource: function(fileurl, callback) {
+ var that = this;
+
+ git.resolveFile(fileurl)
+ .then(function(filepath) {
+ // Is local file
+ if (!filepath) filepath = path.resolve(that.book.root, fileurl);
+ else that.book.logDebug("resolve from git", fileurl, "to", filepath)
+
+ // Read file from absolute path
+ return fs.readFile(filepath)
+ .then(function(source) {
+ return {
+ src: source.toString(),
+ path: filepath
+ }
+ });
+ })
+ .nodeify(callback);
+ }
+});
+
+
var TemplateEngine = function(book) {
this.book = book;
// Nunjucks env
this.env = new nunjucks.Environment(
- new nunjucks.FileSystemLoader(book.root),
+ new BookLoader(book),
{
// Escaping is done after by the markdown parser
autoescape: false
diff --git a/lib/utils/fs.js b/lib/utils/fs.js
index 95fa73d..51577c6 100644
--- a/lib/utils/fs.js
+++ b/lib/utils/fs.js
@@ -1,5 +1,6 @@
var Q = require("q");
-var fs = require('graceful-fs');
+var tmp = require("tmp");
+var fs = require("graceful-fs");
var fsExtra = require("fs-extra");
var Ignore = require("fstream-ignore");
@@ -62,6 +63,10 @@ var getFiles = function(path) {
};
module.exports = {
+ tmp: {
+ file: Q.denodeify(tmp.file.bind(tmp)),
+ dir: Q.denodeify(tmp.dir.bind(tmp))
+ },
list: getFiles,
stat: Q.denodeify(fs.stat),
readdir: Q.denodeify(fs.readdir),
diff --git a/lib/utils/git.js b/lib/utils/git.js
new file mode 100644
index 0000000..8b515ec
--- /dev/null
+++ b/lib/utils/git.js
@@ -0,0 +1,110 @@
+var Q = require("q");
+var _ = require("lodash");
+var url = require("url");
+var tmp = require("tmp");
+var path = require("path");
+var crc = require("crc");
+var exec = Q.denodeify(require("child_process").exec);
+
+var fs = require("./fs");
+
+var GIT_PREFIX = "git+";
+var GIT_TMP = null;
+
+
+// Check if an url is a git dependency url
+function checkGitUrl(giturl) {
+ return (giturl.indexOf(GIT_PREFIX) === 0);
+}
+
+// Validates a SHA in hexadecimal
+function validateSha(str) {
+ return (/[0-9a-f]{40}/).test(str);
+}
+
+// Parse and extract infos
+function parseGitUrl(giturl) {
+ if (!checkGitUrl(giturl)) return null;
+ giturl = giturl.slice(GIT_PREFIX.length);
+
+ var parts = url.parse(giturl);
+
+ var ref = parts.hash;
+
+ // Extract file inside the repo (after the .git)
+ var fileParts = parts.pathname.split(".git");
+ var filepath = fileParts.length > 1? fileParts.slice(1).join(".git") : "";
+ if (filepath[0] == "/") filepath = filepath.slice(1);
+
+ // Rebuild
+ parts.pathname = _.first(fileParts)+".git";
+ var githost = url.format(parts);
+
+ return {
+ host: githost,
+ ref: ref,
+ filepath: filepath
+ };
+}
+
+// Clone a git repo from a specific ref
+function cloneGitRepo(host, ref) {
+ var isBranch = false;
+
+ ref = ref || "master";
+ if (!validateSha(ref)) isBranch = true;
+
+ return Q()
+
+ // Create temporary folder to store git repos
+ .then(function() {
+ if (GIT_TMP) return;
+ return fs.tmp.dir().get(0)
+ .then(function(_tmp) {
+ GIT_TMP = _tmp;
+ });
+ })
+
+ // Return or clone the git repo
+ .then(function() {
+ // Unique ID for repo/ref combinaison
+ var repoId = crc.crc32(host+"#"+ref).toString(16);
+
+ // Absolute path to the folder
+ var repoPath = path.resolve(GIT_TMP, repoId);
+
+ return fs.exists(repoPath)
+ .then(function(doExists) {
+ if (doExists) return;
+
+ // Clone repo
+ return exec("git clone "+host+" "+repoPath)
+ .then(function() {
+ return exec("git checkout "+ref, { cwd: repoPath });
+ })
+ })
+ .thenResolve(repoPath);
+ });
+}
+
+
+// Get file from a git repo
+function resolveFileFromGit(giturl) {
+ if (_.isString(giturl)) giturl = parseGitUrl(giturl);
+ if (!giturl) return Q(null);
+
+ // Clone or get from cache
+ return cloneGitRepo(giturl.host, giturl.ref)
+ .then(function(repo) {
+
+ // Resolve relative path
+ return path.resolve(repo, giturl.filepath);
+ });
+};
+
+
+module.exports = {
+ checkUrl: checkGitUrl,
+ parseUrl: parseGitUrl,
+ resolveFile: resolveFileFromGit
+};
diff --git a/package.json b/package.json
index 2377d6f..a545cc9 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,9 @@
"gitbook-plugin-livereload": "0.0.1",
"gaze": "~0.5.1",
"send": "0.2.0",
- "tiny-lr-fork": "0.0.5"
+ "tiny-lr-fork": "0.0.5",
+ "tmp": "0.0.24",
+ "crc": "3.2.1"
},
"devDependencies": {
"mocha": "1.18.2",
@@ -36,7 +38,7 @@
"grunt-bower-install-simple": "0.9.2"
},
"scripts": {
- "test": "export TESTING=true; mocha --reporter list"
+ "test": "export TESTING=true; mocha --reporter list --timeout 15000"
},
"repository": {
"type": "git",
diff --git a/test/fixtures/test5/README.md b/test/fixtures/test5/README.md
index 67536b3..33ee83a 100644
--- a/test/fixtures/test5/README.md
+++ b/test/fixtures/test5/README.md
@@ -1,3 +1,4 @@
# Test Content inclusion
{% include "./test.md" %}
+{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}