├── .gitignore ├── package.json ├── LICENSE ├── README.md └── index.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | /node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jade-glob-include", 3 | "version": "1.0.1", 4 | "description": "Extend Jade's Include to Support File Globbing", 5 | "main": "index.coffee", 6 | "dependencies": { 7 | "jade": "~1.3.1", 8 | "glob": "~3.2.9", 9 | "colors": "^0.6.2" 10 | }, 11 | "devDependencies": { 12 | "jade": "~1.3.1", 13 | "jasmine-node": ">=1.14.3", 14 | "glob": "~3.2.9", 15 | "colors": "^0.6.2" 16 | }, 17 | "scripts": { 18 | "test": "jasmine-node spec/" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:KellyLSB/jade-glob-include" 23 | }, 24 | "keywords": [ 25 | "jade", 26 | "glob", 27 | "include" 28 | ], 29 | "author": "Kelly Becker", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/KellyLSB/jade-glob-include/issues" 33 | }, 34 | "homepage": "https://github.com/KellyLSB/jade-glob-include" 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 Kelly Lauren Summer Becker-Neuding 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jade Glob Include 2 | ## (Globbing Include Directive for Jade) 3 | 4 | **Author: Kelly Becker** 5 | **Website: http://kellybecker.me** 6 | **GitHub: http://github.com/KellyLSB** 7 | **License: [MIT](./LICENSE)** 8 | 9 | Like many other engineers I love Jade. I stumbled upon it the other day and was very pleased with it's syntax and feature sets, but with every piece of software there is always something we dislike. Thankfully, due to mutable states, we have monkey patching! 10 | 11 | ~~There was one issue that stood out like a Bumble Bee buzzing around my head while I'm not wearing a sweater or gloves. I wanted to swat it away but I was afraid of the aftermath. So... of course this meant that I had to get over my fear.~~ 12 | 13 | ~~The problem was I knew if I swatted this one Bumble Bee and then brushed it off... I more than likely would face more again in the future. So I went to war.~~ 14 | 15 | I did not want to manually include all of the items in my resume manually into my template. 10 include directives in a row... I mean that's a whole lot! It could take days to write those ten measly lines. I had to come up with a plan and by happenstance it meant writing 122 lines of Coffee. Now, I've seen "The Venture Brothers" and I learned from Hank's experience that maybe 122 lines of ground coffee is not the best idea... so I did it anyway. **So emerging from my laziness I present to you: Globbing Include Directive for Jade!** 16 | 17 | ## Requirements 18 | 19 | I do not generally write Node much, I have not tested this with many versions. In the past I know Node has been known to change a lot. Because of this, here are the versions I developed with. 20 | 21 | - [Node](http://nodejs.org) ~ v0.10.28 22 | - [Jade](https://github.com/visionmedia/jade) ~ 1.3.1 23 | - [Glob](https://github.com/isaacs/node-glob) ~ 3.2.9 24 | - [Colors](https://github.com/Marak/colors.js) ~ ^0.6.2 `For informational output` 25 | 26 | ## Installing 27 | 28 | **To install globally** 29 | 30 | `npm install -g jade-glob-include` 31 | 32 | **To include in a project** 33 | 34 | `npm install jade-glob-include --save-dev` 35 | 36 | **Code modifications** 37 | 38 | All you need to do to add glob support is send jade as the only argument to `.Patch(jade_object)`. Please reference the following code. 39 | 40 | ```js 41 | var jade = require('jade'); 42 | 43 | // Patch in Globbing Include! 44 | require('jade-glob-include').Patch(jade); 45 | 46 | var fn = jade.compile(jadeTemplate); 47 | var htmlOutput = fn({ 48 | maintainer: { 49 | name: 'Forbes Lindesay', 50 | twitter: '@ForbesLindesay', 51 | blog: 'forbeslindesay.co.uk' 52 | } 53 | }); 54 | ``` 55 | 56 | ## Using Grunt 57 | 58 | This was a painpoint for me to figure out the best way to implement this library with Grunt, especially considering the goal of this was to create a library you could install and include with little to no modification of external libraries. 59 | 60 | Please view [grunt-contrib-jade](https://github.com/gruntjs/grunt-contrib-jad e) for additional reference beyond the scope of what I will display here. 61 | 62 | **Code Modifications** 63 | 64 | This may seem a little weird, but we are going to Hijack the data input for jade in your Gruntfile! Please consider the following `Gruntfile.js` 65 | 66 | ```js 67 | // Load in Jade Glob Include 68 | JadeGlobInclude = require('jade-glob-include'); 69 | 70 | module.exports = function(grunt) { 71 | grunt.initConfig({ 72 | pkg: grunt.file.readJSON('package.json'), 73 | 74 | jade: { 75 | options: { 76 | pretty: true, 77 | data: function() { 78 | // Patch in Globbing Include! 79 | JadeGlobInclude.Patch(this.jade); 80 | 81 | return { 82 | debug: true, 83 | timestamp: "<%= grunt.template.today() %>", 84 | } 85 | } 86 | }, 87 | dist: { 88 | files: {'index.html': 'jade/index.jade'} 89 | } 90 | } 91 | }); 92 | 93 | grunt.loadNpmTasks('grunt-contrib-jade'); 94 | grunt.registerTask('default', ['jade']); 95 | } 96 | ``` 97 | 98 | ## How does this change `include`? 99 | 100 | Because Jade Glob Include uses [node-glob](https://github.com/isaacs/node-glob) you can use any of the formats that are allowed there as well. Please consider the following. 101 | 102 | ```jade 103 | h1 Welcome 104 | //- Include all files in includes 105 | include includes/* 106 | 107 | //- Include from multiple sources 108 | include {~/jade/mixins/**/*, includes/*} 109 | ``` 110 | 111 | This allows for additional flexibility when naming files and folders and compiling items such as static page blogs, etc... 112 | 113 | *Note: If you load includes from outside your project directory you will need to set the basedir option for Jade to a higher level in your file system tree.* 114 | 115 | ## Todo 116 | 117 | 1. Get back up to date on Jasmine 118 | 2. Write proper specs. 119 | 120 | ## Priority Todo 121 | 122 | 1. Drink More Coffee (Hell Yeah, Four Barrel)! 123 | 2. TBD 124 | 125 | ![Coffee Time](http://www.tshirtvortex.net/wp-content/uploads/Coffee-Time-A.gif) 126 | -------------------------------------------------------------------------------- /index.coffee: -------------------------------------------------------------------------------- 1 | # Title: Jade-Glob-Include 2 | # Author: Kelly Becker 3 | # Website: http://kellybecker.me 4 | # Repository: http://github.com/KellyLSB/jade-glob-include 5 | 6 | 7 | # Include node tools 8 | nodePath = require('path') 9 | nodeFs = require('fs') 10 | 11 | # Console Text Coloring 12 | colors = require('colors') 13 | 14 | # Directory Globber 15 | glob = require('glob') 16 | 17 | # NOTE: Include jade utils; this is a REQUIRED module. 18 | # While I am Hijacking Jade, the code still runs in the context of this file. 19 | utils = require('jade/lib/utils') 20 | 21 | 22 | # Array Unique 23 | Array.prototype.uniq = -> 24 | cache = {}; for data, index in @ 25 | if cache[data] then continue 26 | cache[data] = true 27 | data 28 | 29 | 30 | # Array is all matching CB 31 | Array.prototype.isAll = (cb) -> 32 | result = (!!cb(data) for data in @).uniq() 33 | if result.length == 1 then result.shift() else false 34 | 35 | 36 | # String Repeat 37 | String.prototype.repeat = (num, mult) -> 38 | if typeof mult is 'undefined' then mult = 1 39 | Array((num * mult) + 1).join(@) 40 | 41 | 42 | # Setup the Jade Glob Include patcher class 43 | class JadeGlobInclude 44 | @tmp_dir: nodePath.resolve('tmp') 45 | @tmp_file: nodePath.resolve(@tmp_dir, 'glob_tmp.jade') 46 | @log_indent: 0 47 | 48 | 49 | @log: (message, indent) -> 50 | indent = if ! indent then @log_indent 51 | 52 | prefix = if indent > 0 then '~'.repeat(indent)+'> ' else '' 53 | 54 | console.info prefix.cyan + message 55 | 56 | 57 | @tmpDir: (tmp) -> 58 | @tmp_dir = nodePath.resolve(tmp) 59 | @tmpFile(nodePath.basename(@tmp_file)) 60 | return @ 61 | 62 | 63 | @prepTmpDir: -> 64 | # Remove 'tmp' if it is a file 65 | if nodeFs.existsSync(@tmp_dir) && ! nodeFs.statSync(@tmp_dir).isDirectory() 66 | nodeFs.unlinkSync(@tmp_dir) 67 | 68 | # Create the 'tmp' directory 69 | if ! nodeFs.existsSync(@tmp_dir) 70 | @log "Preparing temporary directory '#{@tmp_dir}'....".cyan 71 | nodeFs.mkdirSync(@tmp_dir) 72 | 73 | 74 | @tmpFile: (file) -> 75 | @tmp_file = nodePath.resolve(@tmp_dir, nodePath.basename(file)) 76 | return @ 77 | 78 | 79 | @useTmpFile: (data, cb) -> 80 | # Bump log indent up 81 | @log_indent++ 82 | 83 | # Enforce the use an an Array containing strings 84 | if ! data instanceof Array && typeof data is 'string' then data = [data] 85 | if ! data instanceof Array && ! typeof data is 'string' 86 | return @log 'Received non Array or String as argument in `JadeGlobInclude.useTmpFile()` ...'.red 87 | if data instanceof Array && ! data.isAll((d) -> typeof d is 'string') 88 | return @log 'Received non String in Array in `JadeGlobInclude.useTmpFile()` ...'.red 89 | 90 | # Add notice to the top if the file describing it. 91 | data.unshift "//- Generated On: #{Date()}.\n" 92 | data.unshift '//- Temporary Jade File For Globbed Includes.' 93 | 94 | # Write the temporary file 95 | @log "Creating temporary file '#{@tmp_file}'.".yellow 96 | nodeFs.writeFileSync @tmp_file, data.join("\n"), flags: 'w+' 97 | 98 | # Run callback 99 | captured_result = cb @tmp_file 100 | 101 | # Cleanup and remove temporary file 102 | if nodeFs.existsSync @tmp_file 103 | @log "Removing temporary file '#{@tmp_file}'.".yellow 104 | nodeFs.unlinkSync @tmp_file 105 | 106 | # Knock log indent down 107 | @log_indent-- 108 | 109 | # Reuturn the result 110 | captured_result 111 | 112 | 113 | @patch: (jade) -> 114 | JadeParser = jade.Parser 115 | 116 | # Don't patch a second time 117 | # NOTE: Used for grunt-jade data hack. 118 | if JadeParser.prototype.glob_includer then return jade 119 | 120 | # Notice to user :D 121 | @log 'Initializing Jade Glob Include....'.cyan 122 | 123 | # Prepare temporary directory 124 | @prepTmpDir() 125 | 126 | # Notice to user :D 127 | @log 'Mokeypatching Jade Parser....'.cyan 128 | 129 | # Copy out original method so it may be monkeypatched. 130 | jadeParseInclude = JadeParser.prototype.parseInclude.toString() 131 | 132 | # Split at comment to grab the include token data 133 | # TODO: This will need to be continuously updated as Jade changes. 134 | jadeParseInclude = jadeParseInclude.split('// has-filter') 135 | 136 | # Dual Assignment of the array halves 137 | [jadeParsePrefix, jadeParseInclude] = jadeParseInclude 138 | 139 | # Notice to user :D 140 | @log 'Carefully placing modified original parser back into Jade....'.cyan 141 | 142 | # Create new Include Parser 143 | eval "JadeParser.prototype.jadeParseInclude = function(fs, tok, path) {\n#{jadeParseInclude}" 144 | 145 | # Make a var of self 146 | $ = @ 147 | 148 | # Replace the parser 149 | JadeParser.prototype.parseInclude = -> 150 | 151 | # Include the tokenized data from Jade 152 | eval jadeParsePrefix.split("\n").splice(1).join("\n") 153 | 154 | # NOTE: Remove CWD from the path; for readability and that we 155 | # are adding it back in for path's that won't return with it.... 156 | path = nodePath.normalize(path).replace("#{process.cwd()}/", '') 157 | 158 | # Get list of files to include 159 | files = glob.sync nodePath.normalize(path) 160 | 161 | # If there was only one file; process normally. 162 | if files.length == 1 163 | file = files.shift() 164 | $.log "Including: '#{file}'.".yellow 165 | return @jadeParseInclude fs, tok, file 166 | 167 | # Prepare data for temporary file 168 | if files.length > 0 169 | $.log "Including #{files.length} files via '#{path}' match.".cyan 170 | 171 | # Prepare the include statements 172 | data = for file in files 173 | "include #{nodePath.relative($.tmp_dir, nodePath.resolve(file))}" 174 | 175 | # No data was found, in order to prevent error return empty array 176 | else 177 | $.log "Zero files matched '#{path}'; will continue with using an" + 178 | "empty temporary file in order to prevent error.".red, @log_indent + 1 179 | data = [] 180 | 181 | $.useTmpFile data, (file) => @jadeParseInclude(fs, tok, file) 182 | 183 | # Mark as initialized 184 | JadeParser.prototype.glob_includer = true 185 | 186 | # Notice to console of initialization. 187 | @log "Oh my Glob! Jade Glob Includer is now initialized....".green 188 | 189 | # Return modified jade instance. 190 | # NOTE: also returned by reference; the original object is modified. 191 | jade 192 | 193 | 194 | # Export the class 195 | module.exports = JadeGlobInclude 196 | --------------------------------------------------------------------------------