From 05e78fcf522b56115805a3b79083bf54db189c22 Mon Sep 17 00:00:00 2001 From: Paul Young Date: Fri, 20 Sep 2013 14:13:27 -0400 Subject: [PATCH] Initial commit. --- .gitignore | 1 + Cakefile | 11 +++++ LICENSE | 19 ++++++++ README.md | 112 +++++++++++++++++++++++++++++++++++++++++++ bin/jade-inheritance | 37 ++++++++++++++ lib/index.js | 22 +++++++++ lib/parser.js | 108 +++++++++++++++++++++++++++++++++++++++++ package.json | 32 +++++++++++++ src/index.coffee | 10 ++++ src/parser.coffee | 83 ++++++++++++++++++++++++++++++++ 10 files changed, 435 insertions(+) create mode 100644 .gitignore create mode 100644 Cakefile create mode 100644 LICENSE create mode 100644 README.md create mode 100755 bin/jade-inheritance create mode 100644 lib/index.js create mode 100644 lib/parser.js create mode 100644 package.json create mode 100644 src/index.coffee create mode 100644 src/parser.coffee diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/Cakefile b/Cakefile new file mode 100644 index 0000000..0f91e6f --- /dev/null +++ b/Cakefile @@ -0,0 +1,11 @@ +{print} = require 'util' +{spawn} = require 'child_process' + +task 'build', 'Build lib/ from src/', -> + coffee = spawn 'coffee', ['-c', '-o', 'lib', 'src'] + coffee.stderr.on 'data', (data) -> + process.stderr.write data.toString() + coffee.stdout.on 'data', (data) -> + print data.toString() + coffee.on 'exit', (code) -> + callback?() if code is 0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..327d344 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Paul Young + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..75aef1c --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# jade-inheritance +This tool can be used to reduce compilation time for Jade files. + +For example, without `jade-inheritance`, a `watch` task on a directory would recompile all template files when any change was made within that directory. With `jade-inheritance`, only the affected files can be recompiled. + + +## Example +```javascript +var JadeInheritance = require('jade-inheritance'); +var inheritance = JadeInheritance('foo.jade'); +``` + +### Inheritance tree +```javascript +console.log(inheritance.tree); +``` + +Output: +```json +{ + "foo.jade": { + "extendedBy": { + "bar.jade": { + "includedBy": { + "baz.jade": {} + } + } + }, + "extendedBy": { + "qux.jade": {} + } + } +} +``` + +### Dependant files +```javascript +console.log(inheritance.files); +``` + +Output: +```json +[ + "foo.jade", + "bar.jade", + "baz.jade", + "qux.jade" +] +``` + +### Integration with [grunt-contrib-jade](https://github.com/gruntjs/grunt-contrib-jade) +```javascript +// Gruntfile.js +grunt.initConfig({ + jade: { + compile: { + options: { + basedir: 'app', + pretty: true + }, + files: [{ + expand: true, + src: 'app/**/*.jade', + dest: 'assets/', + ext: '.html' + }] + } + } +}); + + +var JadeInheritance = require('jade-inheritance'); +var changedFiles = []; + +var onChange = grunt.util._.debounce(function() { + var options = grunt.config('jade.compile.options'); + var dependantFiles = []; + + files.forEach(function(filename) { + var directory = options.basedir; + var inheritance = new JadeInheritance(filename, directory, options); + dependantFiles = dependantFiles.concat(inheritance.files); + }); + + var config = grunt.config('jade.compile.files')[0]; + config.src = dependantFiles; + grunt.config('jade.compile.files', [config]); + + changedFiles = []; +}, 200) + +grunt.event.on('watch', function(action, filepath) { + changedFiles.push(filepath); + onChange(); +}); +``` + +## Installation +```sh +$ npm install -g jade-inheritance +``` + +## Command line usage +```sh +$ jade-inheritance --help +``` + +## Development +### Build +```sh +$ npm run-script build +``` diff --git a/bin/jade-inheritance b/bin/jade-inheritance new file mode 100755 index 0000000..18ee667 --- /dev/null +++ b/bin/jade-inheritance @@ -0,0 +1,37 @@ +#!/usr/bin/env node +var colors = require('colors'); +var JadeInheritance = require('../lib/index'); +var pjson = require(__dirname + '/../package.json'); +var program = require('commander'); + + +program + .version(pjson.version) + .usage(' ') + .option('--basedir [string]', 'Required for includes and extends that use absolute paths.') + .option('--files', 'Instead of the inheritance tree, print an array containing the files which would be affected by a change in the input file.') + .parse(process.argv); + + +var filename = program.args[0]; + +if (!filename) { + console.log('jade-inheritance: no file provided'.red); + process.exit(1); +} + + +var directory = program.args[1]; + +if (!directory) { + directory = '.' +} + + +options = {}; +options.basedir = program.basedir || directory; + + +var inheritance = new JadeInheritance(filename, directory, options); +var output = program.files ? inheritance.files : inheritance.tree; +console.log(JSON.stringify(output, null, 2)); diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..b5cb4fc --- /dev/null +++ b/lib/index.js @@ -0,0 +1,22 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var JadeInheritance, Parser; + + Parser = require('./parser'); + + JadeInheritance = (function() { + function JadeInheritance(filename, directory, options) { + var parser; + parser = new Parser(filename, directory, options); + this.tree = parser.tree; + this.files = parser.files; + return this; + } + + return JadeInheritance; + + })(); + + module.exports = JadeInheritance; + +}).call(this); diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..6ee7d89 --- /dev/null +++ b/lib/parser.js @@ -0,0 +1,108 @@ +// Generated by CoffeeScript 1.6.3 +(function() { + var JadeParser, Parser, fs, glob, nodePath; + + nodePath = require('path'); + + fs = require('fs'); + + glob = require('glob'); + + JadeParser = require('jade/lib/parser'); + + Parser = (function() { + function Parser(filename, directory, options) { + var files; + this.filename = filename; + this.directory = directory; + this.options = options; + this.cache = {}; + this.files = {}; + filename = nodePath.relative(this.options.basedir, filename); + this.addFile(filename); + files = glob.sync("" + this.directory + "/**/*.jade"); + this.tree = this.getInheritance(filename, files); + files = this.files; + this.files = []; + for (filename in files) { + this.files.push(filename); + } + return this; + } + + Parser.prototype.getInheritance = function(filename, files) { + var branch, file, _fn, _i, _len, + _this = this; + this.addFile(filename); + branch = {}; + if (branch[filename] == null) { + branch[filename] = {}; + } + _fn = function(file) { + var inheritance, newFile, parser, path, relationship, relativeFile, string, type, _base, _base1, _results; + file = nodePath.normalize(file); + relativeFile = nodePath.relative(_this.options.basedir, file); + file = nodePath.join(_this.options.basedir, relativeFile); + if ((_base = _this.cache)[file] == null) { + _base[file] = {}; + } + if (_this.cache[file].string != null) { + string = _this.cache[file].string; + } else { + string = _this.cache[file].string = fs.readFileSync(file, 'utf8'); + } + parser = new JadeParser(string, file, _this.options); + _results = []; + while ((type = parser.peek().type) !== 'eos') { + switch (type) { + case 'extends': + case 'include': + path = parser.expect(type).val.trim(); + path = parser.resolvePath(path, type); + if (path === nodePath.join(_this.options.basedir, filename)) { + if (type === 'extends') { + relationship = 'extendedBy'; + } else if (type === 'include') { + relationship = 'includedBy'; + } + newFile = {}; + if (_this.cache[file].inheritance != null) { + inheritance = _this.cache[file].inheritance; + } else { + inheritance = _this.cache[file].inheritance = _this.getInheritance(relativeFile, files); + } + newFile = inheritance; + if ((_base1 = branch[filename])[relationship] == null) { + _base1[relationship] = []; + } + _results.push(branch[filename][relationship].push(newFile)); + } else { + _results.push(void 0); + } + break; + default: + _results.push(parser.advance()); + } + } + return _results; + }; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _fn(file); + } + return branch; + }; + + Parser.prototype.addFile = function(filename) { + if (this.files[filename] == null) { + return this.files[filename] = null; + } + }; + + return Parser; + + })(); + + module.exports = Parser; + +}).call(this); diff --git a/package.json b/package.json new file mode 100644 index 0000000..8e33fb7 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "jade-inheritance", + "description": "Determine the inheritance of Jade templates.", + "url": "https://github.com/paulyoung/jade-inheritance", + "keywords": [ + "jade" + ], + "version": "0.0.1", + "bin": { + "jade-inheritance": "./bin/jade-inheritance" + }, + "main": "./lib/index", + "engines": { + "node": ">=0.8.x" + }, + "dependencies": { + "colors": "0.6.2", + "commander": "1.2.0", + "glob": "3.2.6", + "jade": "~0.34.1" + }, + "devDependencies": { + "coffee-script": "1.6.3" + }, + "scripts": { + "build": "./node_modules/.bin/cake build" + }, + "repository": { + "type": "git", + "url": "https://github.com/paulyoung/jade-inheritance.git" + } +} diff --git a/src/index.coffee b/src/index.coffee new file mode 100644 index 0000000..56eb551 --- /dev/null +++ b/src/index.coffee @@ -0,0 +1,10 @@ +Parser = require './parser' + +class JadeInheritance + constructor: (filename, directory, options) -> + parser = new Parser filename, directory, options + @tree = parser.tree + @files = parser.files + return this + +module.exports = JadeInheritance diff --git a/src/parser.coffee b/src/parser.coffee new file mode 100644 index 0000000..3f1cdca --- /dev/null +++ b/src/parser.coffee @@ -0,0 +1,83 @@ +nodePath = require 'path' +fs = require 'fs' +glob = require 'glob' +JadeParser = require 'jade/lib/parser' + + +class Parser + + constructor: (filename, directory, options) -> + @filename = filename + @directory = directory + @options = options + + @cache = {} + @files = {} + + filename = nodePath.relative @options.basedir, filename + @addFile filename + + files = glob.sync "#{@directory}/**/*.jade" + @tree = @getInheritance filename, files + + files = @files + @files = [] + @files.push filename for filename of files + + return this + + + getInheritance: (filename, files) -> + @addFile filename + branch = {} + branch[filename] ?= {} + + for file in files + do (file) => + file = nodePath.normalize file + relativeFile = nodePath.relative @options.basedir, file + file = nodePath.join @options.basedir, relativeFile + @cache[file] ?= {} + + if @cache[file].string? + {string} = @cache[file] + else + string = @cache[file].string = fs.readFileSync file, 'utf8' + + parser = new JadeParser string, file, @options + + while (type = parser.peek().type) isnt 'eos' + switch type + when 'extends', 'include' + path = parser.expect(type).val.trim() + path = parser.resolvePath path, type + + if path is nodePath.join(@options.basedir, filename) + if type is 'extends' + relationship = 'extendedBy' + else if type is 'include' + relationship = 'includedBy' + + newFile = {} + + if @cache[file].inheritance? + {inheritance} = @cache[file] + else + inheritance = @cache[file].inheritance = @getInheritance relativeFile, files + + newFile = inheritance + + branch[filename][relationship] ?= [] + branch[filename][relationship].push newFile + + else + parser.advance() + + return branch + + + addFile: (filename) -> + @files[filename] = null unless @files[filename]? + + +module.exports = Parser