├── .gitignore ├── README.md ├── examples └── viewmodel │ ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions │ └── client │ ├── index.jade │ ├── lib │ ├── helpers.js │ └── register-bind.js │ └── views │ ├── paren.tpl.jade │ └── space.tpl.jade └── packages ├── jade-compiler ├── .gitignore ├── .npm │ ├── package │ │ ├── .gitignore │ │ ├── README │ │ └── npm-shrinkwrap.json │ └── plugin │ │ └── compileJade │ │ ├── .gitignore │ │ ├── README │ │ └── npm-shrinkwrap.json ├── .versions ├── lib │ ├── exports.js │ ├── lexer.js │ ├── parser.js │ └── transpilers.js ├── package.js ├── regex │ ├── attribute-args.regex │ └── component-args.regex ├── tests │ └── tests.js └── versions.json └── jade ├── .gitignore ├── .versions ├── package.js ├── plugin └── handler.js ├── tests ├── body.tpl.jade ├── img_tag_here.tpl.jade ├── match.html ├── match.jade ├── match.js ├── runtime.jade └── runtime.js └── versions.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-workspace 2 | *.sublime-project 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dalgard:jade 0.5.4 2 | ================== 3 | 4 | This package is a fork of [`mquandalle:jade`](https://github.com/mquandalle/meteor-jade) and will be kept in sync with the original. 5 | 6 | Hopefully, the two packages can be merged at some point. 7 | 8 | #### Install 9 | 10 | `meteor add dalgard:jade` 11 | 12 | #### Rationale 13 | 14 | The latest development on the `mquandalle:jade` package was on 29 April 2015. Judging from the lack of responsiveness in the issues forum, it looks like the project have been set on stand-by. 15 | 16 | Until development is resumed, this package may improve things a bit in some areas (especially in relation to the [`dalgard:viewmodel`](https://github.com/dalgard/meteor-viewmodel/) package). 17 | 18 | A live version of `/examples/viewmodel` can be found [here](http://dalgard-jade.meteor.com/). 19 | 20 | 21 | ## Changes 22 | 23 | Although only a few lines of code have been added to the package, they enable some useful features. 24 | 25 | Like the existing syntax, the new syntax comes in two variants – space separated or with parentheses. I recommend using the parenthesized version, since this is the only available style for attribute helpers. 26 | 27 | **Note:** The parenthesis format for helper arguments may also be used after includes, components, and built-ins (`if helper(args)`), as long as they don't span multiple lines. 28 | 29 | #### Arguments in interpolation 30 | 31 | Positional and keyword arguments have been missing from Jade's interpolation syntax, but may now be used in one of the two mentioned forms, alleviating the need for Blaze syntax: 32 | 33 | ```jade 34 | body 35 | // Space separated version – similar to Blaze 36 | | Hello #{person name prefix='Lord'} 37 | 38 | // Parenthesis version – similar to attribute helpers 39 | | Hello #{person(name prefix='Lord')} 40 | ``` 41 | 42 | #### Arguments in attributes 43 | 44 | Positional and keyword arguments can be passed to helpers that are used in attributes: 45 | 46 | ```jade 47 | input(type='text' placeholder=person(name prefix='Lord')) 48 | ``` 49 | 50 | #### Dollar sign attributes 51 | 52 | Dynamic attributes were added to the Meteor version of Jade using the `$dyn` syntax: 53 | 54 | ```jade 55 | input(type='text' $dyn=bind('value: value')) 56 | ``` 57 | 58 | This package introduces the concept of using a dollar sign (`$`) in front of an attribute name as a shorthand for designating a dynamic attribute helper. These two examples are equivalent: 59 | 60 | ```jade 61 | div($attr) 62 | ``` 63 | 64 | ```html 65 |
66 | ``` 67 | 68 | If a value is set on the attribute, it becomes the first positional argument of the helper. Consequently, the `$dyn` example may be rewritten in two ways: 69 | 70 | ```jade 71 | input(type='text' $bind='value: value') 72 | ``` 73 | 74 | Or, when more arguments are needed: 75 | 76 | ```jade 77 | input(type='text' $bind('value: value' throttle=500)) 78 | ``` 79 | 80 | #### @index 81 | 82 | The special `@index` variable inside `each` loops may now be used in interpolation or as an argument to a helper. 83 | 84 | The only limitation is direct attribute assignment, which should be written as `attr='#{@index}'`, not as `attr=@index`. 85 | 86 | 87 | ## Compatibility 88 | 89 | So far, I these improvements are fully backwards compatible, since the added syntax previously resulted in errors (with the exception of `$dyn`, which this package leaves untouched). 90 | 91 | 92 | ## History 93 | 94 | - 0.5.4  –  Fixed missing @index variable and `Uncaught TypeError: Cannot read property 'nodeType' of undefined` 95 | - 0.5.3  –  Added tests and fixed corner case with parenthesis syntax 96 | - 0.5.2  –  Bug fix: Broken multiline component arguments. 97 | - 0.5.1  –  Extended parenthesis syntax to includes, components, and built-ins. 98 | - 0.5.0  –  Arguments for attributes and interpolation. -------------------------------------------------------------------------------- /examples/viewmodel/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | -------------------------------------------------------------------------------- /examples/viewmodel/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /examples/viewmodel/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 21o36snkarto1wxddzm 8 | -------------------------------------------------------------------------------- /examples/viewmodel/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | standard-minifiers 8 | ecmascript 9 | meteor-base 10 | mobile-experience 11 | blaze-html-templates 12 | reload 13 | spacebars 14 | jquery 15 | underscore 16 | 17 | dalgard:jade 18 | dalgard:viewmodel 19 | -------------------------------------------------------------------------------- /examples/viewmodel/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /examples/viewmodel/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.2.0.2 2 | -------------------------------------------------------------------------------- /examples/viewmodel/.meteor/versions: -------------------------------------------------------------------------------- 1 | autoupdate@1.2.3 2 | babel-compiler@5.8.24_1 3 | babel-runtime@0.1.4 4 | base64@1.0.4 5 | binary-heap@1.0.4 6 | blaze@2.1.3 7 | blaze-html-templates@1.0.1 8 | blaze-tools@1.0.4 9 | boilerplate-generator@1.0.4 10 | caching-compiler@1.0.0 11 | caching-html-compiler@1.0.2 12 | callback-hook@1.0.4 13 | check@1.0.6 14 | dalgard:jade@0.5.4_1 15 | dalgard:jade-compiler@0.5.4_1 16 | dalgard:reactive-map@0.1.0 17 | dalgard:viewmodel@0.9.1 18 | ddp@1.2.2 19 | ddp-client@1.2.1 20 | ddp-common@1.2.1 21 | ddp-server@1.2.1 22 | deps@1.0.9 23 | diff-sequence@1.0.1 24 | ecmascript@0.1.5 25 | ecmascript-collections@0.1.6 26 | ejson@1.0.7 27 | fastclick@1.0.7 28 | geojson-utils@1.0.4 29 | hot-code-push@1.0.0 30 | html-tools@1.0.5 31 | htmljs@1.0.5 32 | http@1.1.1 33 | id-map@1.0.4 34 | jquery@1.11.4 35 | launch-screen@1.0.4 36 | livedata@1.0.15 37 | logging@1.0.8 38 | meteor@1.1.9 39 | meteor-base@1.0.1 40 | minifiers@1.1.7 41 | minimongo@1.0.10 42 | mobile-experience@1.0.1 43 | mobile-status-bar@1.0.6 44 | mongo@1.1.2 45 | mongo-id@1.0.1 46 | npm-mongo@1.4.39_1 47 | observe-sequence@1.0.7 48 | ordered-dict@1.0.4 49 | promise@0.5.0 50 | random@1.0.4 51 | reactive-dict@1.1.2 52 | reactive-var@1.0.6 53 | reload@1.1.4 54 | retry@1.0.4 55 | routepolicy@1.0.6 56 | sha@1.0.4 57 | spacebars@1.0.7 58 | spacebars-compiler@1.0.7 59 | standard-minifiers@1.0.1 60 | stevezhu:lodash@3.10.1 61 | templating@1.1.4 62 | templating-tools@1.0.0 63 | tracker@1.0.9 64 | ui@1.0.8 65 | underscore@1.0.4 66 | url@1.0.5 67 | webapp@1.2.2 68 | webapp-hashing@1.0.5 69 | -------------------------------------------------------------------------------- /examples/viewmodel/client/index.jade: -------------------------------------------------------------------------------- 1 | body 2 | +space color='green' 3 | +paren(color='blue') 4 | -------------------------------------------------------------------------------- /examples/viewmodel/client/lib/helpers.js: -------------------------------------------------------------------------------- 1 | Template.registerHelper("$name", numeral => "Name " + numeral); 2 | 3 | Template.registerHelper("person", (name, kw) => { 4 | let intro = "Hi, my name is " + name; 5 | 6 | if (kw && kw.hash) 7 | intro += " and my favorite color is " + kw.hash.color; 8 | 9 | return intro; 10 | }); 11 | 12 | Template.registerHelper("isNonEmpty", function (arg) { 13 | return _.isString(arg) && !!arg; 14 | }); 15 | -------------------------------------------------------------------------------- /examples/viewmodel/client/lib/register-bind.js: -------------------------------------------------------------------------------- 1 | ViewModel.registerHelper("bind"); 2 | -------------------------------------------------------------------------------- /examples/viewmodel/client/views/paren.tpl.jade: -------------------------------------------------------------------------------- 1 | // Parenthesis style 2 | div 3 | input(type='text' placeholder=$name('two (throttled)') $bind('value: name2' throttle=1500)) 4 | 5 | if isNonEmpty(name2) 6 | | #{person(name2 color=color)} 7 | -------------------------------------------------------------------------------- /examples/viewmodel/client/views/space.tpl.jade: -------------------------------------------------------------------------------- 1 | // Space separated style 2 | div 3 | input(type='text' placeholder=$name('one') $bind='value: name1') 4 | 5 | if isNonEmpty name1 6 | | #{person name1 color=color} 7 | -------------------------------------------------------------------------------- /packages/jade-compiler/.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /packages/jade-compiler/.npm/package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /packages/jade-compiler/.npm/package/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /packages/jade-compiler/.npm/package/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "jade": { 4 | "version": "https://github.com/mquandalle/jade/tarball/f3f956fa1031e05f85be7bc7b67f12e9ec80ba37", 5 | "dependencies": { 6 | "commander": { 7 | "version": "2.1.0" 8 | }, 9 | "mkdirp": { 10 | "version": "0.3.5" 11 | }, 12 | "transformers": { 13 | "version": "2.1.0", 14 | "dependencies": { 15 | "promise": { 16 | "version": "2.0.0", 17 | "dependencies": { 18 | "is-promise": { 19 | "version": "1.0.1" 20 | } 21 | } 22 | }, 23 | "css": { 24 | "version": "1.0.8", 25 | "dependencies": { 26 | "css-parse": { 27 | "version": "1.0.4" 28 | }, 29 | "css-stringify": { 30 | "version": "1.0.5" 31 | } 32 | } 33 | }, 34 | "uglify-js": { 35 | "version": "2.2.5", 36 | "dependencies": { 37 | "source-map": { 38 | "version": "0.1.40", 39 | "dependencies": { 40 | "amdefine": { 41 | "version": "0.1.0" 42 | } 43 | } 44 | }, 45 | "optimist": { 46 | "version": "0.3.7", 47 | "dependencies": { 48 | "wordwrap": { 49 | "version": "0.0.2" 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | }, 57 | "character-parser": { 58 | "version": "1.2.0" 59 | }, 60 | "monocle": { 61 | "version": "1.1.51", 62 | "dependencies": { 63 | "readdirp": { 64 | "version": "0.2.5", 65 | "dependencies": { 66 | "minimatch": { 67 | "version": "2.0.1", 68 | "dependencies": { 69 | "brace-expansion": { 70 | "version": "1.0.0", 71 | "dependencies": { 72 | "balanced-match": { 73 | "version": "0.2.0" 74 | }, 75 | "concat-map": { 76 | "version": "0.0.0" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } 85 | }, 86 | "with": { 87 | "version": "3.0.1", 88 | "dependencies": { 89 | "uglify-js": { 90 | "version": "2.4.15", 91 | "dependencies": { 92 | "async": { 93 | "version": "0.2.10" 94 | }, 95 | "source-map": { 96 | "version": "0.1.34", 97 | "dependencies": { 98 | "amdefine": { 99 | "version": "0.1.0" 100 | } 101 | } 102 | }, 103 | "optimist": { 104 | "version": "0.3.7", 105 | "dependencies": { 106 | "wordwrap": { 107 | "version": "0.0.2" 108 | } 109 | } 110 | }, 111 | "uglify-to-browserify": { 112 | "version": "1.0.2" 113 | } 114 | } 115 | } 116 | } 117 | }, 118 | "constantinople": { 119 | "version": "2.0.1", 120 | "dependencies": { 121 | "uglify-js": { 122 | "version": "2.4.15", 123 | "dependencies": { 124 | "async": { 125 | "version": "0.2.10" 126 | }, 127 | "source-map": { 128 | "version": "0.1.34", 129 | "dependencies": { 130 | "amdefine": { 131 | "version": "0.1.0" 132 | } 133 | } 134 | }, 135 | "optimist": { 136 | "version": "0.3.7", 137 | "dependencies": { 138 | "wordwrap": { 139 | "version": "0.0.2" 140 | } 141 | } 142 | }, 143 | "uglify-to-browserify": { 144 | "version": "1.0.2" 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packages/jade-compiler/.npm/plugin/compileJade/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /packages/jade-compiler/.npm/plugin/compileJade/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /packages/jade-compiler/.npm/plugin/compileJade/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "jade": { 4 | "version": "https://github.com/mquandalle/jade/tarball/f3f956fa1031e05f85be7bc7b67f12e9ec80ba37", 5 | "dependencies": { 6 | "commander": { 7 | "version": "2.1.0" 8 | }, 9 | "mkdirp": { 10 | "version": "0.3.5" 11 | }, 12 | "transformers": { 13 | "version": "2.1.0", 14 | "dependencies": { 15 | "promise": { 16 | "version": "2.0.0", 17 | "dependencies": { 18 | "is-promise": { 19 | "version": "1.0.1" 20 | } 21 | } 22 | }, 23 | "css": { 24 | "version": "1.0.8", 25 | "dependencies": { 26 | "css-parse": { 27 | "version": "1.0.4" 28 | }, 29 | "css-stringify": { 30 | "version": "1.0.5" 31 | } 32 | } 33 | }, 34 | "uglify-js": { 35 | "version": "2.2.5", 36 | "dependencies": { 37 | "source-map": { 38 | "version": "0.1.40", 39 | "dependencies": { 40 | "amdefine": { 41 | "version": "0.1.0" 42 | } 43 | } 44 | }, 45 | "optimist": { 46 | "version": "0.3.7", 47 | "dependencies": { 48 | "wordwrap": { 49 | "version": "0.0.2" 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | }, 57 | "character-parser": { 58 | "version": "1.2.0" 59 | }, 60 | "monocle": { 61 | "version": "1.1.51", 62 | "dependencies": { 63 | "readdirp": { 64 | "version": "0.2.5", 65 | "dependencies": { 66 | "minimatch": { 67 | "version": "2.0.0", 68 | "dependencies": { 69 | "brace-expansion": { 70 | "version": "1.0.0", 71 | "dependencies": { 72 | "balanced-match": { 73 | "version": "0.2.0" 74 | }, 75 | "concat-map": { 76 | "version": "0.0.0" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } 85 | }, 86 | "with": { 87 | "version": "3.0.1", 88 | "dependencies": { 89 | "uglify-js": { 90 | "version": "2.4.15", 91 | "dependencies": { 92 | "async": { 93 | "version": "0.2.10" 94 | }, 95 | "source-map": { 96 | "version": "0.1.34", 97 | "dependencies": { 98 | "amdefine": { 99 | "version": "0.1.0" 100 | } 101 | } 102 | }, 103 | "optimist": { 104 | "version": "0.3.7", 105 | "dependencies": { 106 | "wordwrap": { 107 | "version": "0.0.2" 108 | } 109 | } 110 | }, 111 | "uglify-to-browserify": { 112 | "version": "1.0.2" 113 | } 114 | } 115 | } 116 | } 117 | }, 118 | "constantinople": { 119 | "version": "2.0.1", 120 | "dependencies": { 121 | "uglify-js": { 122 | "version": "2.4.15", 123 | "dependencies": { 124 | "async": { 125 | "version": "0.2.10" 126 | }, 127 | "source-map": { 128 | "version": "0.1.34", 129 | "dependencies": { 130 | "amdefine": { 131 | "version": "0.1.0" 132 | } 133 | } 134 | }, 135 | "optimist": { 136 | "version": "0.3.7", 137 | "dependencies": { 138 | "wordwrap": { 139 | "version": "0.0.2" 140 | } 141 | } 142 | }, 143 | "uglify-to-browserify": { 144 | "version": "1.0.2" 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /packages/jade-compiler/.versions: -------------------------------------------------------------------------------- 1 | babel-compiler@5.8.24_1 2 | babel-runtime@0.1.4 3 | base64@1.0.4 4 | binary-heap@1.0.4 5 | blaze@2.1.3 6 | blaze-tools@1.0.4 7 | boilerplate-generator@1.0.4 8 | callback-hook@1.0.4 9 | check@1.0.6 10 | dalgard:jade-compiler@0.5.4_1 11 | ddp@1.2.2 12 | ddp-client@1.2.1 13 | ddp-common@1.2.1 14 | ddp-server@1.2.1 15 | deps@1.0.9 16 | diff-sequence@1.0.1 17 | ecmascript@0.1.5 18 | ecmascript-collections@0.1.6 19 | ejson@1.0.7 20 | geojson-utils@1.0.4 21 | html-tools@1.0.5 22 | htmljs@1.0.5 23 | id-map@1.0.4 24 | jquery@1.11.4 25 | local-test:dalgard:jade-compiler@0.5.4_1 26 | logging@1.0.8 27 | meteor@1.1.9 28 | minifiers@1.1.7 29 | minimongo@1.0.10 30 | mongo@1.1.2 31 | mongo-id@1.0.1 32 | npm-mongo@1.4.39_1 33 | observe-sequence@1.0.7 34 | ordered-dict@1.0.4 35 | promise@0.5.0 36 | random@1.0.4 37 | reactive-var@1.0.6 38 | retry@1.0.4 39 | routepolicy@1.0.6 40 | spacebars@1.0.7 41 | spacebars-compiler@1.0.7 42 | tinytest@1.0.6 43 | tracker@1.0.9 44 | ui@1.0.8 45 | underscore@1.0.4 46 | webapp@1.2.2 47 | webapp-hashing@1.0.5 48 | -------------------------------------------------------------------------------- /packages/jade-compiler/lib/exports.js: -------------------------------------------------------------------------------- 1 | var codeGen = SpacebarsCompiler.codeGen; 2 | 3 | JadeCompiler = { 4 | parse: function(source, options) { 5 | options = options || {}; 6 | var parser, Compiler; 7 | 8 | try { 9 | parser = new Parser(source, options.filename || "", { lexer: Lexer }); 10 | Compiler = (options.fileMode) ? FileCompiler : TemplateCompiler; 11 | return new Compiler(parser.parse(), options).compile(); 12 | 13 | } catch (err) { 14 | throw err; 15 | } 16 | }, 17 | 18 | compile: function(source) { 19 | var ast = JadeCompiler.parse(source, { fileMode: false }); 20 | return codeGen(ast); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /packages/jade-compiler/lib/lexer.js: -------------------------------------------------------------------------------- 1 | // We modify a bit the Jade grammar in order to define both user defined and 2 | // build-in components. 3 | // 4 | // We save components as "Mixins" nodes. That's a good host because like 5 | // "Mixins", components have: 6 | // 1. a name 7 | // 2. some arguments (optionnal) 8 | 9 | Lexer = Npm.require("jade").Lexer; 10 | 11 | 12 | var component_re = /^(\+[\.\w-]+|if|unless|else if|else|with|each)\b/, 13 | plus_re = /^\+/, 14 | args_re = /^\((.*)\)\s*$/m, 15 | singleline_re = /([^\n]*)/, 16 | parens_re = /(?:(['"])\1|(['"]).*?[^\\]\2)|((?:\.{1,2}\/)?[\w\.-]+)\(((?:(['"])\5|(['"]).*?[^\\]\6|[^)]*?(['"])\7|[^)]*?(['"]).*?[^\\]\8)*[^)]*)\)/g, 17 | multiline_re = /^\(((?:(['"])\2|(['"]).*?[^\\]\3|[^)]*[^\\](['"])\4|[^)]*?[^\\](['"]).*[^\\]\5)*[^)]*?)\)/, 18 | newline_re = /\n/g, 19 | attrs_re = /(?:(['"])\1|(['"]).*?[^\\]\2)|(!?=?[$=])((?:\.{1,2}\/)?[\w\.-]+)\(((?:(['"])\6|(['"]).*?[^\\]\7|[^)]*?(['"])\8|[^)]*?(['"]).*?[^\\]\9)*[^)]*)\)/g, 20 | quote_re = /(^|[^\\])'/g; 21 | 22 | function parens_replace(match, $1, $2, helper, args) { 23 | if (_.isString($1) || _.isString($2)) 24 | return match; 25 | 26 | return helper + " " + args; 27 | } 28 | 29 | function attrs_replace(match, $1, $2, prefix, helper, args) { 30 | if (_.isString($1) || _.isString($2)) 31 | return match; 32 | 33 | var helper_pre = ""; 34 | 35 | if (prefix.slice(-2) == "=$") { 36 | prefix = prefix.slice(0, -1); 37 | helper_pre = "$"; 38 | } 39 | 40 | var begin = (prefix === "$" ? "$dyn='" : (prefix === "!=" ? "!='{" : "='")) + "{{", 41 | end = "}}" + (prefix === "!=" ? "}'" : "'"); 42 | 43 | return begin + helper_pre + helper + " " + args.replace(quote_re, "$1\\'") + end; 44 | } 45 | 46 | 47 | // Blaze helper used as a component 48 | Lexer.prototype.blazeComponent = function () { 49 | var key = this.input.match(component_re); 50 | 51 | if (key) { 52 | this.consume(key[0].length); 53 | 54 | var tok = this.tok("mixin", key[1].replace(plus_re, "")), 55 | is_paren = this.input.charAt(0) === "(", 56 | args = this.input.match(args_re), 57 | is_singleline = !is_paren || args; 58 | 59 | if (is_singleline) { 60 | if (!args) 61 | args = this.input.match(singleline_re); 62 | 63 | tok.args = args[1].replace(parens_re, parens_replace); 64 | } 65 | else { 66 | args = this.input.match(multiline_re); 67 | 68 | if (args) 69 | tok.args = args[1].replace(newline_re, ""); 70 | } 71 | 72 | this.consume(args[0].length); 73 | 74 | return tok; 75 | } 76 | }; 77 | 78 | 79 | // Super method 80 | var attrs = Lexer.prototype.attrs; 81 | 82 | // Make attributes accept Blaze helper expressions inside parentheses 83 | Lexer.prototype.attrs = function (push) { 84 | if (this.input.charAt(0) === "(") { 85 | var range = this.bracketExpression(), 86 | attrs_str = this.input.slice(range.start, range.end); 87 | 88 | attrs_str = attrs_str.replace(attrs_re, attrs_replace); 89 | 90 | this.input = "(" + attrs_str + this.input.slice(range.end); 91 | } 92 | 93 | return attrs.call(this, push); 94 | }; 95 | 96 | 97 | // Super 98 | var next = Lexer.prototype.next; 99 | 100 | Lexer.prototype.next = function () { 101 | return this.blazeComponent() || next.call(this); 102 | }; 103 | -------------------------------------------------------------------------------- /packages/jade-compiler/lib/parser.js: -------------------------------------------------------------------------------- 1 | // XXX Remove this file when PR jade#1392 is merged and released 2 | // https://github.com/visionmedia/jade/pull/1392 3 | // 4 | // The goal of the PR is to add a `lexer` option to the parser so that the user 5 | // could provide his own customized Lexer object. 6 | 7 | Parser = function Parser(str, filename, options){ 8 | //Strip any UTF-8 BOM off of the start of `str`, if it exists. 9 | this.input = str.replace(/^\uFEFF/, ''); 10 | this.filename = filename; 11 | this.blocks = {}; 12 | this.mixins = {}; 13 | this.options = options || {}; 14 | // 15 | var Constructor = this.options.lexer || Lexer; 16 | this.lexer = new Constructor(this.input, filename); 17 | // 18 | this.contexts = [this]; 19 | this.inMixin = false; 20 | }; 21 | 22 | Parser.prototype = Npm.require('jade').Parser.prototype; 23 | 24 | var nodes = Npm.require('jade').nodes; 25 | 26 | // XXX Horrible hack 27 | // We overwrite the `parseMixin` function to introduce a special case for the 28 | // `markdown` component. 29 | var _super = Parser.prototype.parseMixin; 30 | Parser.prototype.parseMixin = function() { 31 | var tok = this.peek(); 32 | var mixin; 33 | 34 | if (tok.type === "mixin" && tok.val === "markdown") { 35 | this.advance(); 36 | this.lexer.pipeless = true; 37 | mixin = new nodes.Mixin("markdown", "", this.parseTextBlock(), false); 38 | this.lexer.pipeless = false; 39 | return mixin; 40 | 41 | } else { 42 | return _super.call(this); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /packages/jade-compiler/lib/transpilers.js: -------------------------------------------------------------------------------- 1 | // This compiler is based on the meteor core Spacebars compiler. The main goal 2 | // of this object is to transform the jade syntax-tree to a spacebars 3 | // syntax-tree. 4 | // 5 | // XXX Source-mapping: Jade give us the line number, so we could implement a 6 | // simple line-mapping but it's not yet supported by the spacebars compiler. 7 | 8 | // Internal identifier to indicate that we should not insert a new line 9 | // character before a value. This has the side effect that a user cannot start 10 | // a new line starting with this value in one of its templates. 11 | var noNewLinePrefix = "__noNewLine__"; 12 | var startsWithNoNewLinePrefix = new RegExp("^" + noNewLinePrefix); 13 | 14 | var stringRepresentationToLiteral = function(val) { 15 | if (! _.isString(val)) 16 | return null; 17 | 18 | var scanner = new HTMLTools.Scanner(val); 19 | var parsed = BlazeTools.parseStringLiteral(scanner); 20 | return parsed ? parsed.value : null; 21 | }; 22 | 23 | // XXX Obiously we shouldn't have a special case for the markdown component 24 | var isSpecialMarkdownComponent = function(node) { 25 | return node.type === "Mixin" && node.name === "markdown"; 26 | }; 27 | 28 | var isTextOnlyNode = function(node) { 29 | // XXX Is this list defined somewhere in spacebars-compiler? 30 | var textOnlyTags = ['textarea', 'script', 'style']; 31 | return node.textOnly && 32 | node.type === "Tag" && 33 | textOnlyTags.indexOf(node.name) !== -1; 34 | }; 35 | 36 | // Helper function to generation an error from a message and a node 37 | var throwError = function (message, node) { 38 | message = message || "Syntax error"; 39 | if (node.line) 40 | message += " on line " + node.line; 41 | 42 | throw new Error(message); 43 | }; 44 | 45 | FileCompiler = function(tree, options) { 46 | var self = this; 47 | self.nodes = tree.nodes; 48 | self.filename = options && options.filename || ""; 49 | self.head = null; 50 | self.body = null; 51 | self.bodyAttrs = {}; 52 | self.templates = {}; 53 | }; 54 | 55 | _.extend(FileCompiler.prototype, { 56 | compile: function () { 57 | var self = this; 58 | for (var i = 0; i < self.nodes.length; i++) 59 | self.registerRootNode(self.nodes[i]); 60 | 61 | return { 62 | head: self.head, 63 | body: self.body, 64 | bodyAttrs: self.bodyAttrs, 65 | templates: self.templates 66 | }; 67 | }, 68 | 69 | registerRootNode: function(node) { 70 | // XXX This is mostly the same code as the `templating` core package 71 | // The `templating` package should be more generic to allow others templates 72 | // engine to use its methods. 73 | 74 | var self = this; 75 | 76 | // Ignore top level comments 77 | if (node.type === "Comment" || node.type === "BlockComment" || 78 | node.type === "TAG" && _.isUndefined(node.name)) { 79 | return; 80 | } 81 | 82 | // Doctypes 83 | else if (node.type === "Doctype") { 84 | throwError("Meteor sets the doctype for you", node); 85 | } 86 | 87 | // There are two specials templates: head and body 88 | else if (node.name === "body" || node.name === "head") { 89 | var template = node.name; 90 | 91 | if (self[template] !== null) 92 | throwError(template + " is set twice", node); 93 | if (node.name === "head" && node.attrs.length > 0) 94 | throwError("Attributes on head are not supported", node); 95 | else if(node.name === "body" && node.attrs.length > 0) 96 | self.bodyAttrs = self.formatBodyAttrs(node.attrs); 97 | 98 | self[template] = new TemplateCompiler(node.block).compile(); 99 | } 100 | 101 | // Templates 102 | else if (node.name === "template") { 103 | if (node.attrs.length !== 1 || node.attrs[0].name !== 'name') 104 | throwError('Templates must only have a "name" attribute', node); 105 | 106 | var name = node.attrs[0].val.slice(1, -1); 107 | 108 | if (_.has(self.templates, name)) 109 | throwError('Template "' + name + '" is set twice', node); 110 | 111 | self.templates[name] = new TemplateCompiler(node.block).compile(); 112 | } 113 | 114 | // Otherwise this is an error, we do not allow tags, mixins, if, etc. 115 | // outside templates 116 | else 117 | throwError(node.type + ' must be in a template', node); 118 | }, 119 | 120 | formatBodyAttrs: function(attrsList) { 121 | var attrsDict = {}; 122 | _.each(attrsList, function(attr) { 123 | if (attr.escaped) 124 | attr.val = attr.val.slice(1, -1); 125 | attrsDict[attr.name] = attr.val; 126 | }); 127 | return attrsDict; 128 | } 129 | }); 130 | 131 | 132 | 133 | TemplateCompiler = function(tree, options) { 134 | var self = this; 135 | self.tree = tree; 136 | self.filename = options && options.filename || ""; 137 | }; 138 | 139 | _.extend(TemplateCompiler.prototype, { 140 | compile: function () { 141 | var self = this; 142 | return self._optimize(self.visitBlock(self.tree)); 143 | }, 144 | 145 | visitBlock: function (block) { 146 | if (_.isUndefined(block) || _.isNull(block) || ! _.has(block, 'nodes')) 147 | return []; 148 | 149 | var self = this; 150 | var buffer = []; 151 | var nodes = block.nodes; 152 | var currentNode, elseNode, stack; 153 | 154 | for (var i = 0; i < nodes.length; i++) { 155 | currentNode = nodes[i]; 156 | 157 | // If the node is a Mixin (ie Component), we check if there are some 158 | // `else if` and `else` blocks after it and if so, we groups thoses 159 | // nodes by two with the following transformation: 160 | // if a if a 161 | // else if b else 162 | // else => if b 163 | // else 164 | 165 | if (currentNode.type === "Mixin") { 166 | // Create the stack [nodeIf, nodeElseIf..., nodeElse] 167 | stack = []; 168 | while (currentNode.name === "if" && nodes[i+1] && 169 | nodes[i+1].type === "Mixin" && nodes[i+1].name === "else if") 170 | stack.push(nodes[++i]); 171 | 172 | if (nodes[i+1] && nodes[i+1].type === "Mixin" && 173 | nodes[i+1].name === "else") 174 | stack.push(nodes[++i]); 175 | 176 | // Transform the stack 177 | elseNode = stack.shift(); 178 | if (elseNode && elseNode.name === "else if") { 179 | elseNode.name = "if"; 180 | elseNode = { 181 | name: "else", 182 | type: "Mixin", 183 | block: { nodes: [elseNode].concat(stack) }, 184 | call: false 185 | }; 186 | } 187 | } 188 | 189 | buffer.push(self.visitNode(currentNode, elseNode)); 190 | } 191 | 192 | 193 | return buffer; 194 | }, 195 | 196 | getRawText: function(block) { 197 | var self = this; 198 | var parts = _(block.nodes).pluck('val'); 199 | parts = self._interposeEOL(parts); 200 | return parts.reduce(function(a, b) { return a + b; }, ''); 201 | }, 202 | 203 | visitNode: function(node, elseNode) { 204 | var self = this; 205 | var attrs = self.visitAttributes(node.attrs); 206 | var content; 207 | 208 | if (node.code) { 209 | content = self.visitCode(node.code); 210 | } else if (isTextOnlyNode(node) || isSpecialMarkdownComponent(node)) { 211 | content = self.getRawText(node.block); 212 | if (isSpecialMarkdownComponent(node)) { 213 | content = self.parseText(content, {textMode: HTML.TEXTMODE.STRING}); 214 | } 215 | } else { 216 | content = self.visitBlock(node.block); 217 | } 218 | 219 | var elseContent = self.visitBlock(elseNode && elseNode.block); 220 | 221 | return self['visit' + node.type](node, attrs, content, elseContent); 222 | }, 223 | 224 | visitCode: function(code) { 225 | // XXX Need to improve this for "anonymous helpers" 226 | var val = code.val; 227 | // First case this is a string 228 | var strLiteral = stringRepresentationToLiteral(val); 229 | if (strLiteral !== null) { 230 | return noNewLinePrefix + strLiteral; 231 | } else { 232 | return [ this._spacebarsParse(this.lookup(code.val, code.escape)) ]; 233 | } 234 | }, 235 | 236 | // We interpret "Mixins" as "Components" 237 | // Thanks to our customize Lexer, `if`, `unless`, `with` and `each` are 238 | // retrieved as Mixins by the parser 239 | visitMixin: function(node, attrs, content, elseContent) { 240 | var self = this; 241 | var componentName = node.name; 242 | 243 | if (componentName === "else") 244 | throwError("Unexpected else block", node); 245 | 246 | var spacebarsSymbol = content.length === 0 ? ">" : "#"; 247 | var args = node.args || ""; 248 | var mustache = "{{" + spacebarsSymbol + componentName + " " + args + "}}"; 249 | var tag = self._spacebarsParse(mustache); 250 | 251 | // Optimize arrays 252 | content = self._optimize(content); 253 | elseContent = self._optimize(elseContent); 254 | if (content) 255 | tag.content = content; 256 | if (elseContent) 257 | tag.elseContent = elseContent; 258 | 259 | return tag; 260 | }, 261 | 262 | visitTag: function(node, attrs, content) { 263 | var self = this; 264 | var tagName = node.name.toLowerCase(); 265 | 266 | content = self._optimize(content, true); 267 | 268 | if (tagName === "textarea") { 269 | attrs.value = content; 270 | content = null; 271 | } else if (tagName === "style") { 272 | content = self.parseText(content); 273 | } 274 | 275 | if (! _.isArray(content)) 276 | content = content ? [content] : []; 277 | 278 | if (! _.isEmpty(attrs)) 279 | content.unshift(attrs); 280 | 281 | return HTML.getTag(tagName).apply(null, content); 282 | }, 283 | 284 | visitText: function(node) { 285 | var self = this; 286 | return node.val ? self.parseText(node.val) : null; 287 | }, 288 | 289 | parseText: function(text, options) { 290 | // Solve problem with @index not being a correct javascript identifier 291 | text = text.replace(/[#!]\{\s*@index\s*\}/g, "{{@index}}"); 292 | 293 | // The parser doesn't parse #{expression} and !{unescapedExpression} 294 | // syntaxes. So let's do it. 295 | // Since we rely on the Spacebars parser for this, we support the 296 | // {{mustache}} and {{{unescapedMustache}}} syntaxes as well. 297 | // We capture everything until the closing curly brace. We do not want to 298 | // match a closing curly brace if we are inside a string literal, i.e. 299 | // inside non-escaped single or double quotes. 300 | var regex = /([#!])\{([^}]*?(?:[^\\](['"]).*?[^\\]\3[^}]*?)*)\}/g; 301 | 302 | text = text.replace(regex, function (match, prefix, expression) { 303 | var begin = (prefix === "!" ? "{{{" : "{{"), 304 | end = (prefix === "!" ? "}}}" : "}}"), 305 | paren_re = /((?:\.{1,2}\/)?[\w\.-]+)\((.*)\)$/g; // Allow #{helper(args)} syntax 306 | 307 | return begin + expression.replace(paren_re, "$1 $2") + end; 308 | }); 309 | 310 | options = options || {}; 311 | options.getTemplateTag = SpacebarsCompiler.TemplateTag.parseCompleteTag; 312 | 313 | return HTMLTools.parseFragment(text, options); 314 | }, 315 | 316 | visitComment: function (comment) { 317 | // If buffer boolean is true we want to display this comment in the DOM 318 | if (comment.buffer) 319 | return HTML.Comment(comment.val); 320 | }, 321 | 322 | visitBlockComment: function (comment) { 323 | var self = this; 324 | comment.val = "\n" + _.pluck(comment.block.nodes, "val").join("\n") + "\n"; 325 | return self.visitComment(comment); 326 | }, 327 | 328 | visitFilter: function (filter, attrs, content) { 329 | throwError("Jade filters are not supported in meteor-jade", filter); 330 | }, 331 | 332 | visitWhen: function (node) { 333 | throwError("Case statements are not supported in meteor-jade", node); 334 | }, 335 | 336 | visitAttributes: function (attrs) { 337 | // The jade parser provide an attribute tree of this type: 338 | // [{name: "class", val: "val1", escaped: true}, {name: "id" val: "val2"}] 339 | // Let's transform that into: 340 | // {"class": "val1", id: "val2"} 341 | // Moreover if an "id" or "class" attribute is used more than once we need 342 | // to concatenate the values. 343 | if (_.isUndefined(attrs)) 344 | return; 345 | 346 | if (_.isString(attrs)) 347 | return attrs; 348 | 349 | 350 | var self = this; 351 | var dict = {}; 352 | 353 | var concatAttributes = function(a, b) { 354 | if (_.isString(a) && _.isString(b)) 355 | return a + b; 356 | if (_.isUndefined(a)) 357 | return b; 358 | 359 | if (! _.isArray(a)) a = [a]; 360 | if (! _.isArray(b)) b = [b]; 361 | return a.concat(b); 362 | }; 363 | var dynamicAttrs = []; 364 | 365 | _.each(attrs, function (attr) { 366 | // Rewrite $ attributes directly to dynamic attributes 367 | if (attr.name.charAt(0) === "$" && attr.name !== "$dyn") { 368 | attr.val = attr.name.slice(1) + " " + attr.val; 369 | attr.name = "$dyn"; 370 | } 371 | 372 | var val = attr.val; 373 | var key = attr.name; 374 | 375 | // XXX We need a better handler for JavaScript code 376 | // First case this is a string 377 | var strLiteral = stringRepresentationToLiteral(val); 378 | if (strLiteral) { 379 | val = self.parseText(strLiteral, { textMode: HTML.TEXTMODE.STRING }); 380 | val.position = HTMLTools.TEMPLATE_TAG_POSITION.IN_ATTRIBUTE; 381 | } 382 | 383 | // For cases like Spacebars compiler expect the attribute 384 | // to have the value `""` but Jade parser returns `true` 385 | else if (val === true || val === "''" || val === '""') { 386 | val = ""; 387 | 388 | // Otherwise this is some code we need to evaluate 389 | } else { 390 | val = self._spacebarsParse(self.lookup(val, attr.escaped)); 391 | val.position = HTMLTools.TEMPLATE_TAG_POSITION.IN_ATTRIBUTE; 392 | } 393 | 394 | if (key === "$dyn") { 395 | val.position = HTMLTools.TEMPLATE_TAG_POSITION.IN_START_TAG; 396 | return dynamicAttrs.push(val); 397 | } 398 | 399 | // If a user has defined such kind of tag: div.myClass(class="myClass2") 400 | // we need to concatenate classes (and ids) 401 | else if ((key === "class" || key === "id") && dict[key]) 402 | val = [" ", val]; 403 | 404 | dict[key] = concatAttributes(dict[key], val); 405 | }); 406 | 407 | if (dynamicAttrs.length === 0) { 408 | return dict; 409 | } else { 410 | dynamicAttrs.unshift(dict); 411 | return HTML.Attrs.apply(null, dynamicAttrs); 412 | } 413 | }, 414 | 415 | lookup: function (val, escape) { 416 | var mustache = "{{" + val + "}}"; 417 | if (! escape) 418 | mustache = "{" + mustache + "}"; 419 | return HTMLTools.parseFragment(mustache); 420 | }, 421 | 422 | _spacebarsParse: SpacebarsCompiler.TemplateTag.parse, 423 | 424 | _removeNewLinePrefixes: function(array) { 425 | var removeNewLinePrefix = function(val) { 426 | if (startsWithNoNewLinePrefix.test(val)) 427 | return val.slice(noNewLinePrefix.length); 428 | else 429 | return val; 430 | }; 431 | 432 | if (! _.isArray(array)) 433 | return removeNewLinePrefix(array); 434 | else 435 | return _.map(array, removeNewLinePrefix); 436 | }, 437 | 438 | _interposeEOL: function(array) { 439 | for (var i = array.length - 1; i > 0; i--) { 440 | if (! startsWithNoNewLinePrefix.test(array[i])) 441 | array.splice(i, 0, "\n"); 442 | } 443 | return array; 444 | }, 445 | 446 | _optimize: function(content, interposeEOL) { 447 | var self = this; 448 | 449 | if (! _.isArray(content)) 450 | return self._removeNewLinePrefixes(content); 451 | 452 | if (content.length === 0) 453 | return undefined; 454 | if (content.length === 1) 455 | content = self._optimize(content[0]); 456 | else if (interposeEOL) 457 | content = self._interposeEOL(content); 458 | else 459 | content = content; 460 | 461 | return self._removeNewLinePrefixes(content); 462 | } 463 | }); 464 | -------------------------------------------------------------------------------- /packages/jade-compiler/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "dalgard:jade-compiler", 3 | version: "0.5.4_1", 4 | summary: "Improved Jade compiler for Meteor", 5 | git: "https://github.com/dalgard/meteor-jade.git", 6 | documentation: "../../README.md" 7 | }); 8 | 9 | Npm.depends({ 10 | jade: "https://github.com/mquandalle/jade/tarball/f3f956fa1031e05f85be7bc7b67f12e9ec80ba37" 11 | }); 12 | 13 | Package.onUse(function(api) { 14 | api.versionsFrom("METEOR@0.9.0"); 15 | api.use([ 16 | 'underscore', 17 | 'htmljs', 18 | 'html-tools', 19 | 'blaze-tools', 20 | 'spacebars-compiler' 21 | ]); 22 | api.use('minifiers', ['server'], { weak: true }); 23 | api.addFiles([ 24 | 'lib/lexer.js', 25 | 'lib/parser.js', 26 | 'lib/transpilers.js', 27 | 'lib/exports.js' 28 | ]); 29 | api.export('JadeCompiler'); 30 | }); 31 | 32 | Package.onTest(function (api) { 33 | api.versionsFrom("METEOR@0.9.0"); 34 | api.use("tinytest"); 35 | api.use("minifiers"); 36 | api.use("dalgard:jade-compiler@0.5.4_1", "server"); 37 | api.addFiles(["tests/tests.js"], "server"); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/jade-compiler/regex/attribute-args.regex: -------------------------------------------------------------------------------- 1 | / 2 | (?: # Group 3 | ( \1 # Capturing group 4 | ['"] # Quotation mark 5 | ) 6 | \1 # Previous quotation mark 7 | | # Or 8 | ( \2 # Capturing group 9 | ['"] # Quotation mark 10 | ) 11 | .*? # Lazy zero or more characters 12 | [^\\] # Any character except backslash 13 | \2 # Previous quotation mark 14 | ) 15 | | # Or 16 | ( \3 # Capturing group 17 | !? # Zero or one exclamation mark 18 | =? # Zero or one equals sign 19 | [$=] # One dollar sign or equals sign 20 | ) 21 | ( \4 # Capturing group 22 | (?: # Optional group 23 | \.{1,2} # One or two periods 24 | \/ # One slash 25 | )? 26 | [\w\.-]+ # One or more characters 27 | ) 28 | \( # Start parenthesis 29 | ( \5 # Capturing group 30 | (?: # Zero or more of group 31 | ( \6 # Capturing group 32 | ['"] # Quotation mark 33 | ) 34 | \6 # Previous quotation mark 35 | | # Or 36 | ( \7 # Capturing group 37 | ['"] # Quotation mark 38 | ) 39 | .*? # Lazy zero or more characters 40 | [^\\] # Any character except backslash 41 | \7 # Previous quotation mark 42 | | # Or 43 | [^)]*? # Lazy zero or more characters except end parenthesis 44 | ( \8 # Capturing group 45 | ['"] # Quotation mark 46 | ) 47 | \8 # Previous quotation mark 48 | | # Or 49 | [^)]*? # Lazy zero or more characters except end parenthesis 50 | ( \9 # Capturing group 51 | ['"] # Quotation mark 52 | ) 53 | .*? # Lazy zero or more characters 54 | [^\\] # Any character except backslash 55 | \9 # Previous quotation mark 56 | )* 57 | [^)]* # Zero or more characters except end parenthesis 58 | ) 59 | \) # End parenthesis 60 | /g 61 | -------------------------------------------------------------------------------- /packages/jade-compiler/regex/component-args.regex: -------------------------------------------------------------------------------- 1 | # This regex throws away anything between quotes at the root level, and captures 2 | # anything between parentheses preceded by a keyword at the root level 3 | 4 | # It uses this technique: http://www.rexegg.com/regex-best-trick.html 5 | 6 | / 7 | (?: # Group 8 | ( \1 # Capturing group 9 | ['"] # Quotation mark 10 | ) 11 | \1 # Previous quotation mark 12 | | # Or 13 | ( \2 # Capturing group 14 | ['"] # Quotation mark 15 | ) 16 | .*? # Lazy zero or more characters 17 | [^\\] # Any character except backslash 18 | \2 # Previous quotation mark 19 | ) 20 | | # Or 21 | ( \3 # Capturing group 22 | (?: # Optional group 23 | \.{1,2} # One or two periods 24 | \/ # One slash 25 | )? 26 | [\w\.-]+ # One or more characters 27 | ) 28 | \( # Start parenthesis 29 | ( \4 # Capturing group 30 | (?: # Zero or more of group 31 | ( \5 # Capturing group 32 | ['"] # Quotation mark 33 | ) 34 | \5 # Previous quotation mark 35 | | # Or 36 | ( \6 # Capturing group 37 | ['"] # Quotation mark 38 | ) 39 | .*? # Lazy zero or more characters 40 | [^\\] # Any character except backslash 41 | \6 # Previous quotation mark 42 | | # Or 43 | [^)]*? # Lazy zero or more characters except end parenthesis 44 | ( \7 # Capturing group 45 | ['"] # Quotation mark 46 | ) 47 | \7 # Previous quotation mark 48 | | # Or 49 | [^)]*? # Lazy zero or more characters except end parenthesis 50 | ( \8 # Capturing group 51 | ['"] # Quotation mark 52 | ) 53 | .*? # Lazy zero or more characters 54 | [^\\] # Any character except backslash 55 | \8 # Previous quotation mark 56 | )* 57 | [^)]* # Zero or more characters except end parenthesis 58 | ) 59 | \) # End parenthesis 60 | /g 61 | -------------------------------------------------------------------------------- /packages/jade-compiler/tests/tests.js: -------------------------------------------------------------------------------- 1 | 2 | var template = ["p", 3 | " | hello world"].join("\n"); 4 | 5 | var wrapInTemplate = function(tplName, template) { 6 | return "template(name='"+tplName+"')\n " + template.replace("\n", "\n "); 7 | }; 8 | 9 | Tinytest.add("JadeCompiler - parse templates", function(test) { 10 | test.equal(JadeCompiler.parse(template), { children: ["hello world"] }); 11 | }); 12 | 13 | Tinytest.add("JadeCompiler - parse files", function(test) { 14 | test.throws( 15 | function(){ JadeCompiler.parse(template, {fileMode: true}); }, 16 | "Tag must be in a template on line 1"); 17 | 18 | var template2 = wrapInTemplate("hello", template); 19 | test.equal(JadeCompiler.parse(template2, {fileMode: true}), { 20 | head: null, 21 | body: null, 22 | bodyAttrs: {}, 23 | templates: { 24 | hello: { children: ["hello world"] } 25 | } 26 | }); 27 | }); 28 | 29 | Tinytest.add("JadeCompiler - compile templates", function(test) { 30 | test.equal(JadeCompiler.compile(template), 31 | "(function() {\n return HTML.P(\"hello world\");\n})"); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/jade-compiler/versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | [ 4 | "blaze-tools", 5 | "1.0.1" 6 | ], 7 | [ 8 | "deps", 9 | "1.0.5" 10 | ], 11 | [ 12 | "html-tools", 13 | "1.0.2" 14 | ], 15 | [ 16 | "htmljs", 17 | "1.0.2" 18 | ], 19 | [ 20 | "meteor", 21 | "1.1.3" 22 | ], 23 | [ 24 | "minifiers", 25 | "1.1.2" 26 | ], 27 | [ 28 | "spacebars-compiler", 29 | "1.0.3" 30 | ], 31 | [ 32 | "tracker", 33 | "1.0.3" 34 | ], 35 | [ 36 | "underscore", 37 | "1.0.1" 38 | ] 39 | ], 40 | "pluginDependencies": [], 41 | "toolVersion": "meteor-tool@1.0.35", 42 | "format": "1.0" 43 | } -------------------------------------------------------------------------------- /packages/jade/.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /packages/jade/.versions: -------------------------------------------------------------------------------- 1 | babel-compiler@5.8.24_1 2 | babel-runtime@0.1.4 3 | base64@1.0.4 4 | binary-heap@1.0.4 5 | blaze@2.1.3 6 | blaze-tools@1.0.4 7 | boilerplate-generator@1.0.4 8 | caching-compiler@1.0.0 9 | caching-html-compiler@1.0.2 10 | callback-hook@1.0.4 11 | check@1.0.6 12 | dalgard:jade@0.5.4_1 13 | dalgard:jade-compiler@0.5.4_1 14 | ddp@1.2.2 15 | ddp-client@1.2.1 16 | ddp-common@1.2.1 17 | ddp-server@1.2.1 18 | deps@1.0.9 19 | diff-sequence@1.0.1 20 | ecmascript@0.1.5 21 | ecmascript-collections@0.1.6 22 | ejson@1.0.7 23 | geojson-utils@1.0.4 24 | html-tools@1.0.5 25 | htmljs@1.0.5 26 | id-map@1.0.4 27 | jquery@1.11.4 28 | local-test:dalgard:jade@0.5.4_1 29 | logging@1.0.8 30 | meteor@1.1.9 31 | minifiers@1.1.7 32 | minimongo@1.0.10 33 | mongo@1.1.2 34 | mongo-id@1.0.1 35 | npm-mongo@1.4.39_1 36 | observe-sequence@1.0.7 37 | ordered-dict@1.0.4 38 | promise@0.5.0 39 | random@1.0.4 40 | reactive-var@1.0.6 41 | retry@1.0.4 42 | routepolicy@1.0.6 43 | spacebars@1.0.7 44 | spacebars-compiler@1.0.7 45 | templating@1.1.4 46 | templating-tools@1.0.0 47 | tinytest@1.0.6 48 | tracker@1.0.9 49 | ui@1.0.8 50 | underscore@1.0.4 51 | webapp@1.2.2 52 | webapp-hashing@1.0.5 53 | -------------------------------------------------------------------------------- /packages/jade/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: "dalgard:jade", 3 | version: "0.5.4_1", 4 | summary: "Jade template engine for Meteor", 5 | git: "https://github.com/dalgard/meteor-jade.git", 6 | documentation: "../../README.md" 7 | }); 8 | 9 | Package.registerBuildPlugin({ 10 | name: "compileJade", 11 | use: [ 12 | "underscore@1.0.0", 13 | "htmljs@1.0.0", 14 | "minifiers@1.0.0", 15 | "spacebars-compiler@1.0.0", 16 | "dalgard:jade-compiler@0.5.4_1" 17 | ], 18 | sources: [ 19 | "plugin/handler.js", 20 | ] 21 | }); 22 | 23 | Package.onUse(function (api) { 24 | api.use("blaze@2.0.0"); 25 | }); 26 | 27 | Package.onTest(function (api) { 28 | api.versionsFrom("METEOR@0.9.0"); 29 | api.use("tinytest"); 30 | api.use(["dalgard:jade@0.5.4_1", "ui", "underscore", "jquery", "spacebars", "templating"]); 31 | api.addFiles([ 32 | "tests/match.jade", 33 | "tests/match.html", 34 | "tests/runtime.jade", 35 | "tests/body.tpl.jade", 36 | "tests/img_tag_here.tpl.jade" 37 | ]); 38 | api.addFiles(["tests/match.js", "tests/runtime.js"], "client"); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/jade/plugin/handler.js: -------------------------------------------------------------------------------- 1 | var path = Npm.require('path'); 2 | 3 | // XXX Handle body attributes 4 | var bodyGen = function (tpl, attrs) { 5 | var res = ""; 6 | if (attrs !== {}) { 7 | res += "\nMeteor.startup(function() { $('body').attr("; 8 | res += JSON.stringify(attrs) + "); });\n"; 9 | } 10 | res += "\nTemplate.body.addContent("; 11 | res += SpacebarsCompiler.codeGen(tpl, { 12 | isBody: true, 13 | sourceName: "" 14 | }); 15 | res += ");\n"; 16 | res += "Meteor.startup(Template.body.renderToDocument);\n"; 17 | return res; 18 | }; 19 | 20 | var templateGen = function (tree, tplName) { 21 | var nameLiteral = JSON.stringify(tplName); 22 | var templateDotNameLiteral = JSON.stringify("Template." + tplName); 23 | var res = ""; 24 | res += "\nTemplate.__checkName(" + nameLiteral + ");"; 25 | res += "\nTemplate[" + nameLiteral + "] = new Template("; 26 | res += templateDotNameLiteral + ", "; 27 | res += SpacebarsCompiler.codeGen(tree, { 28 | isTemplate: true, 29 | sourceName: 'Template "' + tplName + '"' 30 | }); 31 | res += ");\n"; 32 | return res; 33 | }; 34 | 35 | var getCompilerResult = function (compileStep, fileMode) { 36 | var content = compileStep.read().toString('utf8'); 37 | try { 38 | return JadeCompiler.parse(content, { 39 | filename: compileStep.inputPath, 40 | fileMode: fileMode 41 | }); 42 | } catch (err) { 43 | return compileStep.error({ 44 | message: "Jade syntax error: " + err.message, 45 | sourcePath: compileStep.inputPath 46 | }); 47 | } 48 | }; 49 | 50 | var fileModeHandler = function (compileStep) { 51 | var results = getCompilerResult(compileStep, true); 52 | 53 | // Head 54 | if (results.head !== null) { 55 | compileStep.appendDocument({ 56 | section: "head", 57 | data: HTML.toHTML(results.head) 58 | }); 59 | } 60 | 61 | var jsContent = ""; 62 | if (results.body !== null) { 63 | jsContent += bodyGen(results.body, results.bodyAttrs); 64 | } 65 | if (! _.isEmpty(results.templates)) { 66 | jsContent += _.map(results.templates, templateGen).join(""); 67 | } 68 | 69 | if (jsContent !== "") { 70 | compileStep.addJavaScript({ 71 | path: compileStep.inputPath + '.js', 72 | sourcePath: compileStep.inputPath, 73 | data: jsContent 74 | }); 75 | } 76 | }; 77 | 78 | var templateModeHandler = function (compileStep) { 79 | var result = getCompilerResult(compileStep, false); 80 | var templateName = path.basename(compileStep.inputPath, '.tpl.jade'); 81 | var jsContent; 82 | 83 | if (templateName === "head") { 84 | compileStep.appendDocument({ 85 | section: "head", 86 | data: HTML.toHTML(result) 87 | }); 88 | 89 | } else { 90 | 91 | if (templateName === "body") 92 | jsContent = bodyGen(result); 93 | else 94 | jsContent = templateGen(result, templateName); 95 | 96 | compileStep.addJavaScript({ 97 | path: compileStep.inputPath + '.js', 98 | sourcePath: compileStep.inputPath, 99 | data: jsContent 100 | }); 101 | } 102 | }; 103 | 104 | var pluginOptions = { 105 | isTemplate: true, 106 | archMatching: "web" 107 | }; 108 | 109 | Plugin.registerSourceHandler("jade", pluginOptions, fileModeHandler); 110 | Plugin.registerSourceHandler("tpl.jade", pluginOptions, templateModeHandler); 111 | -------------------------------------------------------------------------------- /packages/jade/tests/body.tpl.jade: -------------------------------------------------------------------------------- 1 | #UnwrappedTemplateUniqueIdentifier 2 | +img_tag_here 3 | -------------------------------------------------------------------------------- /packages/jade/tests/img_tag_here.tpl.jade: -------------------------------------------------------------------------------- 1 | img 2 | -------------------------------------------------------------------------------- /packages/jade/tests/match.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 124 | 125 | 128 | 129 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 166 | 167 | 170 | 171 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /packages/jade/tests/match.jade: -------------------------------------------------------------------------------- 1 | //- HTML tags 2 | 3 | template(name='match-jade-singleTagAttribute') 4 | input(required) 5 | 6 | template(name='match-jade-nonStandardTag') 7 | qr(text="jade rocks") 8 | 9 | template(name='match-jade-tagAttributeWithTextValue-1') 10 | input(type='text') 11 | 12 | template(name='match-jade-tagAttributeWithTextValue-2') 13 | input(type="text") 14 | 15 | template(name='match-jade-emptyTagAttribute') 16 | input(placehover="") 17 | 18 | template(name='match-jade-parentContextInTagAttribute') 19 | a(href="/users/{{../userId}}/") 20 | 21 | template(name='match-jade-helperInTagAttribute') 22 | input(required=isRequired) 23 | 24 | template(name='match-jade-dynamicTagAttributes') 25 | input($dyn=attrs) 26 | 27 | template(name='match-jade-dynamicTagAttributesWithArgs') 28 | input($dyn="{{attrs arg1 arg2}}") 29 | 30 | template(name='match-jade-multipleTagAttributes-1') 31 | input(type="password" required=isRequired autofocus $dyn=attrs) 32 | 33 | template(name='match-jade-multipleTagAttributes-2') 34 | input(type="password", required=isRequired, autofocus, $dyn=attrs) 35 | 36 | template(name='match-jade-charRef') 37 | script(src="//maps.googleapis.com/maps/api/js?language=ru®ion=ru") 38 | 39 | template(name='match-jade-classNames') 40 | div.class1.class2(class="class3") 41 | 42 | template(name='match-jade-classNamesMerging') 43 | .sixteen.wide.mobile.twelve.wide.computer.column 44 | 45 | template(name='match-jade-multipleIdentifiers') 46 | #id1.class1(class="class2" class="class3 class4") 47 | 48 | template(name='match-jade-inlineColonTags') 49 | form: input 50 | 51 | template(name='match-jade-inlineBracketsTags') 52 | div styling #[span #[span tags]] 53 | 54 | template(name='match-jade-iframe') 55 | iframe(width="13" height="37") 56 | 57 | template(name='match-jade-styleTag') 58 | style. 59 | body { 60 | background-color: {{themeColor}}; 61 | } 62 | 63 | template(name='match-jade-empty-1') 64 | 65 | template(name='match-jade-empty-2') 66 | = "" 67 | 68 | template(name='match-jade-justText-1') 69 | | Hello world 70 | 71 | template(name='match-jade-justText-2') 72 | = "Hello world" 73 | 74 | template(name='match-jade-justText-3') 75 | = 'Hello world' 76 | 77 | template(name='match-jade-justText-4') 78 | = 'Hello ' 79 | = 'world' 80 | 81 | template(name='match-jade-textWithEscapedQuote') 82 | = 'Je t\'aime' 83 | 84 | template(name='match-jade-multipleChildren') 85 | p 86 | | Hello 87 | | world 88 | 89 | template(name='match-jade-noSpaceBetweenChildren') 90 | p 91 | | Hello 92 | = "." 93 | 94 | 95 | //- Template helpers 96 | 97 | template(name='match-jade-tagHelper') 98 | textarea= textareaValue 99 | 100 | template(name='match-jade-justHelper') 101 | = helper 102 | 103 | template(name='match-jade-inlineHelper-1') 104 | p Hello #{world} 105 | 106 | template(name='match-jade-inlineHelper-2') 107 | p Hello {{world}} 108 | 109 | template(name='match-jade-unescapedInlineHelper-1') 110 | p Hello !{world} 111 | 112 | template(name='match-jade-unescapedInlineHelper-2') 113 | p Hello {{{world}}} 114 | 115 | template(name='match-jade-mustacheCall') 116 | p {{func arg1 arg2}} 117 | 118 | template(name='match-jade-multipleInlineHelpers') 119 | p Hello #{foo} #{bar} #{baz} 120 | 121 | template(name='match-jade-tagAttributeInterpolation') 122 | button(class="btn #{btnKind}") 123 | 124 | 125 | //- Built-in components 126 | 127 | template(name='match-jade-if-1') 128 | if weAreTheWorld 129 | | hello world 130 | 131 | template(name='match-jade-if-2') 132 | if(weAreTheWorld) 133 | | hello world 134 | 135 | template(name='match-jade-if-3') 136 | +if(weAreTheWorld) 137 | | hello world 138 | 139 | template(name='match-jade-if-4') 140 | +if weAreTheWorld 141 | | hello world 142 | 143 | template(name='match-jade-ifWithHelper-1') 144 | if helper arg1 arg2 145 | | hello world 146 | 147 | template(name='match-jade-ifWithHelper-2') 148 | if(helper arg1 arg2) 149 | | hello world 150 | 151 | template(name='match-jade-ifAndElse') 152 | if weAreTheWorld 153 | | hello world 154 | else 155 | | we are the children 156 | 157 | template(name='match-jade-ifSubscriptionReady') 158 | if Template.subscriptionsReady 159 | | ready 160 | else 161 | | loading 162 | 163 | template(name='match-jade-with') 164 | with user 165 | = name 166 | 167 | template(name='match-jade-each') 168 | each users 169 | = name 170 | 171 | template(name='match-jade-eachIn-1') 172 | each u in users 173 | = u.name 174 | 175 | template(name='match-jade-eachIn-2') 176 | +each(u in users) 177 | = u.name 178 | 179 | template(name='match-jade-eachIndex') 180 | each users 181 | | #{@index} 182 | 183 | template(name='match-jade-eachInIndex') 184 | each u in users 185 | | #{@index} 186 | 187 | template(name='match-jade-eachHelperIndex') 188 | each users 189 | | #{helper @index} 190 | 191 | template(name='match-jade-eachIncludeIndex') 192 | each users 193 | +myTemplate(index=@index) 194 | 195 | template(name='match-jade-eachIfIndex') 196 | each users 197 | if helper(@index) 198 | | #{@index} 199 | 200 | template(name='match-jade-eachAttributeIndex') 201 | each users 202 | input(index=helper(@index)) 203 | 204 | //- else if syntaxic sugar 205 | template(name='match-jade-elseif') 206 | if condA 207 | h1 hello A 208 | else if condB 209 | h2 hello B 210 | else if condC 211 | h3 hello C 212 | else 213 | h4 goodbye 214 | 215 | 216 | //- Components - templates inclusions 217 | 218 | template(name='match-jade-inclusion') 219 | +myTemplate 220 | 221 | template(name='match-jade-inclusionWithContent') 222 | +myTemplate 223 | | this is some {{content}} 224 | 225 | template(name='match-jade-contentsInclusion') 226 | +UI.content 227 | +UI.elseContent 228 | 229 | template(name='match-jade-inclusionWithPositionalArgs-1') 230 | +myTemplate(arg1 arg2) 231 | 232 | template(name='match-jade-inclusionWithPositionalArgs-2') 233 | +myTemplate(arg1 234 | arg2) 235 | 236 | template(name='match-jade-inclusionWithPositionalArgs-3') 237 | +myTemplate( 238 | arg1 239 | arg2 240 | ) 241 | 242 | template(name='match-jade-inclusionWithPositionalArgs-4') 243 | +myTemplate arg1 arg2 244 | 245 | template(name='match-jade-inclusionWithKeywordArgs-1') 246 | +myTemplate(key1=arg1 key2=arg2) 247 | 248 | template(name='match-jade-inclusionWithKeywordArgs-2') 249 | +myTemplate key1=arg1 key2=arg2 250 | 251 | template(name='match-jade-dynamicTemplate') 252 | +Template.dynamic(template=template) 253 | 254 | template(name='match-jade-callableInComponentArg-1') 255 | +Component test="moo()" 256 | 257 | template(name='match-jade-callableInComponentArg-2') 258 | +Component(test="moo()") 259 | 260 | 261 | //- Blocks of text 262 | 263 | template(name='match-jade-blockOfText') 264 | p. 265 | This is a block 266 | of text on multiple 267 | lines. 268 | 269 | template(name='match-jade-textareaContent') 270 | textarea. 271 | Inside a textarea 272 | tags shouldn't be interpreted 273 | 274 | template(name='match-jade-specialMarkdownComponent') 275 | +markdown 276 | This is my email: 277 | 278 | template(name='match-jade-specialMarkdownComponentWithHelper') 279 | +markdown 280 | This is my email: {{email}} 281 | //- end of template 282 | 283 | 284 | //- Added syntax in dalgard:jade 285 | 286 | template(name='match-jade-inclusionWithParenthesisPositionalArg') 287 | +myTemplate(helper(arg1)) 288 | 289 | template(name='match-jade-inclusionWithParenthesisPositionalAndKeywordArgs') 290 | +myTemplate helper(arg1 key1=arg2) 291 | 292 | template(name='match-jade-inclusionWithParenthesisPositionalArgsWithStringParen') 293 | +myTemplate(helper('()')) 294 | 295 | template(name='match-jade-inclusionWithParenthesisPositionalArgsWithStringParenAndKeyword') 296 | +myTemplate helper(arg1 key1=')') 297 | 298 | template(name='match-jade-extrapolationWithSpacesArgs') 299 | | #{helper arg1 key1=arg2} 300 | 301 | template(name='match-jade-extrapolationWithSpacesArgsWithStringBraces') 302 | | #{helper '}' key1=arg1} 303 | 304 | template(name='match-jade-extrapolationWithParenthesisArgs') 305 | | #{helper(arg1 key1=arg2)} 306 | 307 | template(name='match-jade-extrapolationWithParenthesisArgsWithStringBraces') 308 | | #{helper('})' key1=arg1)} 309 | 310 | template(name='match-jade-attributeWithHelperAndParenthesisArgs') 311 | input(type=helper(arg1 key1=arg2)) 312 | 313 | template(name='match-jade-attributeWithHelperAndParenthesisArgsEmptyString') 314 | input(type=helper("")) 315 | 316 | template(name='match-jade-attributeWithDollarHelperAndParenthesisArgs') 317 | input(type=$helper(arg1 key1=arg2)) 318 | 319 | template(name='match-jade-attributeWithParenthesisArgsWithStringParen') 320 | input(type=helper(')' key1="()")) 321 | 322 | template(name='match-jade-attributesWithParenthesisArgsWithStrings') 323 | input(type=helper1(key1='string') placeholder=helper2('string')) 324 | 325 | template(name='match-jade-attributeExtremeString') 326 | input(type='type=helper1(key1="string") placeholder=helper2("string")') 327 | 328 | template(name='match-jade-attributeExtremeStringDouble') 329 | input(type="type=helper1(key1='string') placeholder=helper2('string')") 330 | 331 | template(name='match-jade-attributeExtremeStringEscaped') 332 | input(type='type=helper1(key1=\'string\') placeholder=helper2(\'string\')') 333 | 334 | template(name='match-jade-dynamicAttributeWithDollarSign') 335 | input($attr) 336 | 337 | template(name='match-jade-dynamicAttributeWithDollarSignAndSingleArg') 338 | input($attr=arg) 339 | 340 | template(name='match-jade-dynamicAttributeWithDollarSignAndSingleDollarArg') 341 | input($attr=$arg) 342 | 343 | template(name='match-jade-dynamicAttributeWithDollarSignAndParenthesisArgs') 344 | input($attr(arg key1=')')) 345 | -------------------------------------------------------------------------------- /packages/jade/tests/match.js: -------------------------------------------------------------------------------- 1 | var removeLineComment = function (code) { 2 | var lineBreak = "\n"; 3 | return _.map(code.split(lineBreak), function (line) { 4 | return line.replace(/(.+?)\s*\/\/ [0-9]+/, "$1"); 5 | }).join(lineBreak); 6 | }; 7 | 8 | var tpl2txt = function(tplName) { 9 | var tpl = Template[tplName]; 10 | if (! tpl.renderFunction) 11 | throw Error("The template object doesn't have a render function"); 12 | return removeLineComment(tpl.renderFunction.toString()); 13 | }; 14 | 15 | Tinytest.add('Jade - Compiled template match Spacebars', function (test) { 16 | for (var jadeTplName in Template) { 17 | if (! jadeTplName.match(/^match-jade/)) 18 | continue; 19 | 20 | var parts = jadeTplName.split('-'); 21 | var testName = parts[2]; 22 | var testIndex = parts[3]; 23 | var description = testName + (testIndex ? " (" + testIndex + ")" : ""); 24 | var htmlTplName = ["match", "html", testName].join("-"); 25 | 26 | if (_.has(Template, htmlTplName)) 27 | test.equal(tpl2txt(jadeTplName), tpl2txt(htmlTplName), description); 28 | else 29 | test.fail("Missing template: " + htmlTplName); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /packages/jade/tests/runtime.jade: -------------------------------------------------------------------------------- 1 | head 2 | meta(name="jadetest" content="1337") 3 | 4 | body(data-test="value") 5 | #myUniqueJadeIdentifier 6 | +insert_a_PRE_tag_here 7 | 8 | template(name="insert_a_PRE_tag_here") 9 | pre 10 | -------------------------------------------------------------------------------- /packages/jade/tests/runtime.js: -------------------------------------------------------------------------------- 1 | Tinytest.add('Jade - Head inclusion', function (test) { 2 | test.equal($('meta[name=jadetest]').attr('content'), '1337'); 3 | }); 4 | 5 | Tinytest.add('Jade - Runtime template insertion', function (test) { 6 | test.isNotNull(document.querySelector('#myUniqueJadeIdentifier pre', 7 | "it should insert a pre tag inside the body")); 8 | }); 9 | 10 | Tinytest.add('Jade - Runtime unwrapped template insertion', function (test) { 11 | test.isNotNull(document.querySelector('#UnwrappedTemplateUniqueIdentifier img', 12 | "it should insert an img tag inside the body")); 13 | }); 14 | 15 | Tinytest.add('Jade - Body attributes', function (test) { 16 | test.equal(document.body.dataset.test, 'value'); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/jade/versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | [ 4 | "meteor", 5 | "1.1.3" 6 | ], 7 | [ 8 | "underscore", 9 | "1.0.1" 10 | ] 11 | ], 12 | "pluginDependencies": [ 13 | [ 14 | "compileJade", 15 | { 16 | "spacebars-compiler": "1.0.3", 17 | "mquandalle:jade-compiler": "0.4.1", 18 | "deps": "1.0.5", 19 | "tracker": "1.0.3", 20 | "html-tools": "1.0.2", 21 | "underscore": "1.0.1", 22 | "meteor": "1.1.3", 23 | "blaze-tools": "1.0.1", 24 | "htmljs": "1.0.2", 25 | "minifiers": "1.1.2" 26 | } 27 | ] 28 | ], 29 | "toolVersion": "meteor-tool@1.0.35", 30 | "format": "1.0" 31 | } --------------------------------------------------------------------------------