mirror of
https://github.com/less/less.js.git
synced 2026-04-09 03:00:20 -04:00
Merge branch 'master' of https://github.com/less/less.js
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,4 +1,5 @@
|
||||
*.js text eol=lf
|
||||
*.svg text eol=lf
|
||||
lessc text eol=lf
|
||||
*.less text eol=lf
|
||||
*.css text eol=lf
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,6 +16,7 @@ test/browser/less.js
|
||||
test/sourcemaps/**/*.map
|
||||
test/sourcemaps/*.map
|
||||
test/sourcemaps/*.css
|
||||
test/less-bom
|
||||
|
||||
# grunt
|
||||
.grunt
|
||||
|
||||
73
.jscsrc
Normal file
73
.jscsrc
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"],
|
||||
"disallowKeywords": ["with"],
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowOperatorBeforeLineBreak": ["."],
|
||||
"disallowSpaceAfterKeywords": [
|
||||
"void"//,
|
||||
//"typeof"
|
||||
],
|
||||
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~"],
|
||||
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
|
||||
"disallowSpacesInCallExpression": true,
|
||||
"disallowSpacesInNamedFunctionExpression": {
|
||||
"beforeOpeningRoundBrace": true},
|
||||
"disallowTrailingComma": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"maximumLineLength": 160,
|
||||
"requireCommaBeforeLineBreak": true,
|
||||
"requireCurlyBraces": [ "if",
|
||||
"else",
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"try",
|
||||
"catch"],
|
||||
"requireOperatorBeforeLineBreak": [ "?",
|
||||
"=",
|
||||
"+",
|
||||
"-",
|
||||
"/",
|
||||
"*",
|
||||
"==",
|
||||
"===",
|
||||
"!=",
|
||||
"!==",
|
||||
">",
|
||||
">=",
|
||||
"<",
|
||||
"<="],
|
||||
"requireSpaceAfterBinaryOperators": true,
|
||||
"requireSpaceAfterKeywords": [
|
||||
"else",
|
||||
"case",
|
||||
"try",
|
||||
"typeof",
|
||||
"return",
|
||||
"if",
|
||||
"for",
|
||||
"while",
|
||||
"do"
|
||||
],
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireSpaceBeforeBinaryOperators": [
|
||||
"=",
|
||||
"+",
|
||||
"-",
|
||||
"/",
|
||||
"*",
|
||||
"==",
|
||||
"===",
|
||||
"!=",
|
||||
"!=="
|
||||
],
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireSpaceBetweenArguments": true,
|
||||
"requireSpacesInConditionalExpression": true,
|
||||
"requireSpacesInForStatement": true,
|
||||
"requireSpacesInNamedFunctionExpression": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"validateIndentation": 4
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
{
|
||||
"evil": true,
|
||||
"laxbreak": true,
|
||||
"latedef": true,
|
||||
"node": true,
|
||||
"undef": true,
|
||||
"unused": "vars",
|
||||
"trailing": true,
|
||||
"noarg": true,
|
||||
"eqnull": true,
|
||||
"forin": true,
|
||||
|
||||
@@ -5,3 +5,7 @@ node_js:
|
||||
install:
|
||||
- npm install -g grunt-cli
|
||||
- npm install
|
||||
env:
|
||||
global:
|
||||
- secure: TrNVruWYaUK5ALga1y7wRY+MLjWJECUSCsBmKW5EUmIevOUxqHWu7M89FANKxstEeFRRAGH3QJbloRxnzIgh0U0ah5npE9XA1bYXGO5khoXeIyk7pNRfjIo8aEnJH1Vp8vWA6J6ovxdJ7lCFKEGvGKxGde50knVl7KFVVULlX2U=
|
||||
- secure: Rzh+CEI7YRvvVkOruPE8Z0dkU0s13V6b6cpqbN72vxbJl/Jm5PUZkjTFJdkWJrW3ErhCKX6EC7XdGvrclqEA9WAqKzrecqCJYqTnw4MwqiAj6F9wqE/BqhoWg4xPxm0MK/7eJMvLCgjNpe+gc1CaeFJZkLSNWn6nOFke+vVlf9Q=
|
||||
|
||||
116
CHANGELOG.md
116
CHANGELOG.md
@@ -1,12 +1,120 @@
|
||||
# 2.3.1
|
||||
|
||||
2015-01-28
|
||||
|
||||
- Fix depends option (regression in 2.3.0)
|
||||
- Support parent selector (`&`) used in sub element expression (e.g. `:not(.c_&)`)
|
||||
|
||||
# 2.3.0
|
||||
|
||||
2015-01-27
|
||||
|
||||
- add isruleset function
|
||||
- add optional import option, causing less to not fail if file not found
|
||||
- Fix browsers-side cache.
|
||||
- Many fixes to import reference - support `@support` and keyframe
|
||||
- Selectors now interpolate pseudo selectors (e.g. `:@{hover}`)
|
||||
- Fix comments missed off if they were at the end of the file
|
||||
- Fix !important used with parametric mixins
|
||||
- Emits warnings for extends when the target is not found
|
||||
- include-path now works on data-uri
|
||||
- variables and function calls work for path in data-uri
|
||||
- Fix absolute paths not working on imports sometimes.
|
||||
- Unicode BOM removed again
|
||||
- Misc. bug fixes
|
||||
|
||||
# 2.2.0
|
||||
|
||||
2015-01-04
|
||||
|
||||
- do not apply relative paths to svg-gradient and data-uri functions data-uri output
|
||||
- using import filename interpolation and import inline together now works
|
||||
- deprecate the compression option (still works, but outputs a warning unless silent)
|
||||
- The node version of less now has image-size, image-width, image-height which return the image dimensions of a file
|
||||
- Fixed an issue that could cause the parse to occur more than once and the callback be called multiple times
|
||||
- if you are outputting to the console, lessc defaults to silent so warnings do not end up in output
|
||||
- isunit function supports '' to test if a dimension has no unit
|
||||
- data-uri function now counts characters after base64 encoding instead of bytes before encoding to determine ie8 support
|
||||
- fix bug effecting guards on pseudo class selectors
|
||||
- do not cache on the browser when used with modifyVars
|
||||
- detection if less does not parse last character in file
|
||||
- detection of whether a file is css now requires /css, .css, ?css, &css instead of just css. You can still tell less the type of file using import options.
|
||||
- remove extra new line added to sourcemap entry inline file
|
||||
- support safari extension
|
||||
- less.parse now exposes a way to get the AST. We do not recommend you use this unless you need to.
|
||||
|
||||
# 2.1.2
|
||||
|
||||
2014-12-20
|
||||
|
||||
- Fix for use with requirejs
|
||||
- Fixes for data-uri function
|
||||
|
||||
# 2.1.1
|
||||
|
||||
2014-11-27
|
||||
|
||||
- Improved keyword and anonymous usage with the replace function
|
||||
- Added getCSSAppendage to sourcemap builder to avoid duplication in plugins
|
||||
- Fix problem with plugins when used with the promises version of render
|
||||
- If the render callback throws an exception it now propogates instead of calling the callback again with an error
|
||||
|
||||
# 2.1.0
|
||||
|
||||
2014-11-23
|
||||
|
||||
- Fixed isSync option, it was using sync file operations but promises are guaranteed to call back async. We now support promises as a feature rather than the 1st class way of doing things.
|
||||
- Browser code is now synchronous again, like in v1, meaning it blocks the site until less is compiled
|
||||
- Some fixes for variable imports which affected filemanagers when synchronous
|
||||
- Fixed lessc makefile dependencies option
|
||||
- output now reports back a imports field with an array of imported files
|
||||
- relative path test for drive names (so windows only) is now case insensitive
|
||||
- Fix for IE7 - use getChar instead of indexing array
|
||||
- variables using !important now output !important, which bubbles up to affect the rule
|
||||
- livereload cache buster is now treated specially
|
||||
- upgrade dependencies
|
||||
|
||||
# 2.0.0
|
||||
|
||||
2014-11-09
|
||||
|
||||
- Fixed multiplication in non strict units mode to take the left operand unit, in the case that the unit cannot be resolved
|
||||
- Some fixes for browser cross-compatibility
|
||||
- browser tests now pass in IE 8-11 and FF
|
||||
- added index.js and browser.js in root as shortcuts
|
||||
- fixed some local variable spellings
|
||||
- support for @counter-style directive
|
||||
|
||||
# 2.0.0-b3
|
||||
|
||||
2014-11-01
|
||||
|
||||
- some refactoring of browser structure to allow use of api vs normal browser bundle
|
||||
- browser bundle no longer leaks require
|
||||
- browser can now be scoped with just window
|
||||
- browser useFileCache defaults to true, but file cache is now cleared when refreshing or in watch mode
|
||||
|
||||
# 2.0.0-b2
|
||||
|
||||
2014-10-26
|
||||
|
||||
- Imports are now sequenced and so are consistent (previously some complex projects might end up with occasional different orderings)
|
||||
- Imports with variables are better supported - variables can be specified in sub imports
|
||||
- Support for rebeccapurple
|
||||
- Browser can now accept options as attributes on the script tag and the link tags e.g. `<script data-verbose="false" src="less.js"...`
|
||||
- adding a .less file extension is done in the abstract file manager so it the behaviour can be overridden by certain file managers
|
||||
- Fixed a bug where unquoted urls beginning `//` e.g. `url(//file/file.less)` would be incorrectly interpreted (bug introduced in b-1)
|
||||
- lessc plugins can be a function, used as a constructor as well as an object - this to allow the plugin more flexibility to be used programattically
|
||||
|
||||
# 2.0.0-b1
|
||||
|
||||
2014-10-19
|
||||
|
||||
- Public Beta / Release Candidate - Feedback welcome
|
||||
For a guide to breaking changes see [the v2 upgrade guide](lesscss.org/usage/#v2-upgrade-guide)
|
||||
For a guide to breaking changes see [the v2 upgrade guide](http://lesscss.org/usage/#v2-upgrade-guide)
|
||||
- no longer including old versions of less in the repo or npm
|
||||
- not including test less and gradle files in npm
|
||||
- colours now output in the format they are added unless compressing, so yellow will output yellow, not its hex counterpart
|
||||
- colours now output in the format they are added, so yellow will output yellow, not its hex counterpart
|
||||
- better parsing - better comment support and comments in brackets can now contain comments including quotes.
|
||||
- Removal of dependency on clean-css - install less-plugin-clean-css and use --clean-css to reference plugin
|
||||
- Environment Support - less is now separate from its node and browser environment implementations and adding support for another javascript environment should be straight forward.
|
||||
@@ -175,7 +283,7 @@
|
||||
- support for guards on selectors (currently only if you have a single selector)
|
||||
- allow property merging through the +: syntax
|
||||
- Added min/max functions
|
||||
- Added length function and improved extract to work with comma seperated values
|
||||
- Added length function and improved extract to work with comma separated values
|
||||
- when using import multiple, sub imports are imported multiple times into final output
|
||||
- fix bad spaces between namespace operators
|
||||
- do not compress comment if it begins with an exclamation mark
|
||||
@@ -272,7 +380,7 @@
|
||||
- browser and server url re-writing is now aligned to not re-write (previous lessc behaviour)
|
||||
- url-rewriting can be made to re-write to be relative to the entry file using the relative-urls option (less.relativeUrls option)
|
||||
- rootpath option can be used to add a base path to every url
|
||||
- Support mixin argument seperator of ';' so you can pass comma seperated values. e.g. `.mixin(23px, 12px;);`
|
||||
- Support mixin argument separator of ';' so you can pass comma separated values. e.g. `.mixin(23px, 12px;);`
|
||||
- Fix lots of problems with named arguments in corner cases, not behaving as expected
|
||||
- hsv, hsva, unit functions
|
||||
- fixed lots more bad error messages
|
||||
|
||||
149
Gruntfile.js
149
Gruntfile.js
@@ -6,6 +6,8 @@ module.exports = function (grunt) {
|
||||
// Report the elapsed execution time of tasks.
|
||||
require('time-grunt')(grunt);
|
||||
|
||||
var COMPRESS_FOR_TESTS = true;
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
|
||||
@@ -46,9 +48,13 @@ module.exports = function (grunt) {
|
||||
|
||||
browserify: {
|
||||
browser: {
|
||||
src: ['./lib/less-browser/index.js'],
|
||||
src: ['./lib/less-browser/bootstrap.js'],
|
||||
options: {
|
||||
alias: ["promise/polyfill.js:promise"]
|
||||
exclude: ["promise"],
|
||||
require: ["promise/polyfill.js"],
|
||||
browserifyOptions: {
|
||||
standalone: 'less'
|
||||
}
|
||||
},
|
||||
dest: 'tmp/less.js'
|
||||
}
|
||||
@@ -59,7 +65,7 @@ module.exports = function (grunt) {
|
||||
banner: '<%= meta.banner %>'
|
||||
},
|
||||
browsertest: {
|
||||
src: '<%= browserify.browser.dest %>',
|
||||
src: COMPRESS_FOR_TESTS ? '<%= uglify.test.dest %>' : '<%= browserify.browser.dest %>',
|
||||
dest: 'test/browser/less.js'
|
||||
},
|
||||
dist: {
|
||||
@@ -89,11 +95,18 @@ module.exports = function (grunt) {
|
||||
uglify: {
|
||||
options: {
|
||||
banner: '<%= meta.banner %>',
|
||||
mangle: true
|
||||
mangle: true,
|
||||
compress: {
|
||||
pure_getters: true
|
||||
}
|
||||
},
|
||||
dist: {
|
||||
src: ['<%= concat.dist.dest %>'],
|
||||
dest: 'dist/less.min.js'
|
||||
},
|
||||
test: {
|
||||
src: '<%= browserify.browser.dest %>',
|
||||
dest: 'tmp/less.min.js'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -111,6 +124,13 @@ module.exports = function (grunt) {
|
||||
}
|
||||
},
|
||||
|
||||
jscs: {
|
||||
src: ["test/**/*.js", "lib/less*/**/*.js", "bin/lessc"],
|
||||
options: {
|
||||
config: ".jscsrc"
|
||||
}
|
||||
},
|
||||
|
||||
connect: {
|
||||
server: {
|
||||
options: {
|
||||
@@ -121,10 +141,9 @@ module.exports = function (grunt) {
|
||||
|
||||
jasmine: {
|
||||
options: {
|
||||
// version: '2.0.0-rc2',
|
||||
keepRunner: true,
|
||||
host: 'http://localhost:8081/',
|
||||
vendor: ['test/browser/common.js', 'test/browser/less.js'],
|
||||
vendor: ['test/browser/jasmine-jsreporter.js', 'test/browser/common.js', 'test/browser/less.js'],
|
||||
template: 'test/browser/test-runner-template.tmpl'
|
||||
},
|
||||
main: {
|
||||
@@ -144,6 +163,14 @@ module.exports = function (grunt) {
|
||||
outfile: 'tmp/browser/test-runner-legacy.html'
|
||||
}
|
||||
},
|
||||
strictUnits: {
|
||||
src: ['test/less/strict-units/*.less'],
|
||||
options: {
|
||||
helpers: 'test/browser/runner-strict-units-options.js',
|
||||
specs: 'test/browser/runner-strict-units-spec.js',
|
||||
outfile: 'tmp/browser/test-runner-strict-units.html'
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
src: ['test/less/errors/*.less', '!test/less/errors/javascript-error.less'],
|
||||
options: {
|
||||
@@ -222,15 +249,73 @@ module.exports = function (grunt) {
|
||||
options: {
|
||||
helpers: 'test/browser/runner-postProcessor-options.js',
|
||||
specs: 'test/browser/runner-postProcessor.js',
|
||||
outfile: 'tmp/browser/test-postProcessor.html'
|
||||
outfile: 'tmp/browser/test-runner-post-processor.html'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'saucelabs-jasmine': {
|
||||
all: {
|
||||
options: {
|
||||
urls: ["post-processor", "global-vars", "modify-vars", "production", "rootpath-relative",
|
||||
"rootpath", "relative-urls", "browser", "no-js-errors", "legacy", "strict-units"
|
||||
].map(function(testName) {
|
||||
return "http://localhost:8081/tmp/browser/test-runner-" + testName + ".html";
|
||||
}),
|
||||
testname: 'Sauce Unit Test for less.js',
|
||||
browsers: [{
|
||||
browserName: "chrome",
|
||||
version: '',
|
||||
platform: 'Windows 8'
|
||||
},
|
||||
{
|
||||
browserName: "firefox",
|
||||
version: '33',
|
||||
platform: 'Linux'
|
||||
},
|
||||
{
|
||||
browserName: "iPad",
|
||||
version: '8.0',
|
||||
platform: 'OS X 10.9',
|
||||
'device-orientation': 'portrait'
|
||||
},
|
||||
{
|
||||
browserName: "internet explorer",
|
||||
version: '8',
|
||||
platform: 'Windows XP'
|
||||
},
|
||||
{
|
||||
browserName: "internet explorer",
|
||||
version: '9',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
{
|
||||
browserName: "internet explorer",
|
||||
version: '10',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
{
|
||||
browserName: "internet explorer",
|
||||
version: '11',
|
||||
platform: 'Windows 8.1'
|
||||
}],
|
||||
sauceConfig: {
|
||||
'record-video': process.env.TRAVIS_BRANCH !== "master",
|
||||
'record-screenshots': process.env.TRAVIS_BRANCH !== "master",
|
||||
'idle-timeout': 100, 'max-duration': 120,
|
||||
build: process.env.TRAVIS_BRANCH === "master" ? process.env.TRAVIS_JOB_ID : undefined,
|
||||
tags: [process.env.TRAVIS_BUILD_NUMBER, process.env.TRAVIS_PULL_REQUEST, process.env.TRAVIS_BRANCH]
|
||||
},
|
||||
throttled: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Clean the version of less built for the tests
|
||||
clean: {
|
||||
test: ['test/browser/less.js', 'tmp'],
|
||||
"sourcemap-test": ['test/sourcemaps/*.css', 'test/sourcemaps/*.map']
|
||||
test: ['test/browser/less.js', 'tmp', 'test/less-bom'],
|
||||
"sourcemap-test": ['test/sourcemaps/*.css', 'test/sourcemaps/*.map'],
|
||||
sauce_log: ["sc_*.log"]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -269,6 +354,7 @@ module.exports = function (grunt) {
|
||||
// Create the browser version of less.js
|
||||
grunt.registerTask('browsertest-lessjs', [
|
||||
'browserify:browser',
|
||||
'uglify:test',
|
||||
'concat:browsertest'
|
||||
]);
|
||||
|
||||
@@ -282,16 +368,55 @@ module.exports = function (grunt) {
|
||||
// setup a web server to run the browser tests in a browser rather than phantom
|
||||
grunt.registerTask('browsertest-server', [
|
||||
'browsertest-lessjs',
|
||||
'jasmine::build',
|
||||
'connect::keepalive'
|
||||
]);
|
||||
|
||||
// Run all tests
|
||||
grunt.registerTask('test', [
|
||||
var previous_force_state = grunt.option("force");
|
||||
|
||||
grunt.registerTask("force",function(set) {
|
||||
if (set === "on") {
|
||||
grunt.option("force",true);
|
||||
}
|
||||
else if (set === "off") {
|
||||
grunt.option("force",false);
|
||||
}
|
||||
else if (set === "restore") {
|
||||
grunt.option("force",previous_force_state);
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('sauce', [
|
||||
'browsertest-lessjs',
|
||||
'jasmine::build',
|
||||
'connect',
|
||||
'sauce-after-setup'
|
||||
]);
|
||||
|
||||
// setup a web server to run the browser tests in a browser rather than phantom
|
||||
grunt.registerTask('sauce-after-setup', [
|
||||
'saucelabs-jasmine',
|
||||
'clean:sauce_log'
|
||||
]);
|
||||
|
||||
var testTasks = [
|
||||
'clean',
|
||||
'jshint',
|
||||
'jscs',
|
||||
'shell:test',
|
||||
'browsertest'
|
||||
]);
|
||||
];
|
||||
|
||||
if (isNaN(Number(process.env.TRAVIS_PULL_REQUEST, 10)) &&
|
||||
Number(process.env.TRAVIS_NODE_VERSION) === 0.11 &&
|
||||
(process.env.TRAVIS_BRANCH === "master" || process.env.TRAVIS_BRANCH === "sauce")) {
|
||||
testTasks.push("force:on");
|
||||
testTasks.push("sauce-after-setup");
|
||||
testTasks.push("force:off");
|
||||
}
|
||||
|
||||
// Run all tests
|
||||
grunt.registerTask('test', testTasks);
|
||||
|
||||
// generate a good test environment for testing sourcemaps
|
||||
grunt.registerTask('sourcemap-test', [
|
||||
|
||||
13
README.md
13
README.md
@@ -1,5 +1,6 @@
|
||||
[](http://badge.fury.io/js/less) [](https://travis-ci.org/less/less.js)
|
||||
[](http://badge.fury.io/js/less) [](https://travis-ci.org/less/less.js)
|
||||
[](https://david-dm.org/less/less.js) [](https://david-dm.org/less/less.js#info=devDependencies) [](https://david-dm.org/less/less.js#info=optionalDependencies)
|
||||
[](https://saucelabs.com/u/less) [](https://ci.appveyor.com/project/lukeapage/less-js/branch/master)
|
||||
|
||||
# [Less.js](http://lesscss.org)
|
||||
|
||||
@@ -12,9 +13,9 @@ This is the JavaScript, official, stable version of Less.
|
||||
|
||||
Options for adding Less.js to your project:
|
||||
|
||||
* Install with [NPM](https://npmjs.org/): `npm install less`
|
||||
* Install with [npm](https://npmjs.org): `npm install less`
|
||||
* [Download the latest release][download]
|
||||
* Clone the repo: `git clone git://github.com/less/less.js.git`
|
||||
* Clone the repo: `git clone https://github.com/less/less.js.git`
|
||||
|
||||
## More information
|
||||
|
||||
@@ -27,11 +28,11 @@ Here are other resources for using Less.js:
|
||||
|
||||
|
||||
## Contributing
|
||||
Please read [CONTRIBUTING.md](./CONTRIBUTING.md). Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
|
||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md). Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com).
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
Before opening any issue, please search for existing issues and read the [Issue Guidelines](https://github.com/necolas/issue-guidelines), written by [Nicolas Gallagher](https://github.com/necolas/). After that if you find a bug or would like to make feature request, [please open a new issue][issues].
|
||||
Before opening any issue, please search for existing issues and read the [Issue Guidelines](https://github.com/necolas/issue-guidelines), written by [Nicolas Gallagher](https://github.com/necolas). After that if you find a bug or would like to make feature request, [please open a new issue][issues].
|
||||
|
||||
Please report documentation issues in [the documentation project](https://github.com/less/less-docs).
|
||||
|
||||
@@ -44,7 +45,7 @@ See the [changelog](CHANGELOG.md)
|
||||
|
||||
## [License](LICENSE)
|
||||
|
||||
Copyright (c) 2009-2014 [Alexis Sellier](http://cloudhead.io/) & The Core Less Team
|
||||
Copyright (c) 2009-2014 [Alexis Sellier](http://cloudhead.io) & The Core Less Team
|
||||
Licensed under the [Apache License](LICENSE).
|
||||
|
||||
|
||||
|
||||
33
appveyor.yml
Normal file
33
appveyor.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Test against these versions of Node.js.
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "0.10"
|
||||
- nodejs_version: "0.11"
|
||||
|
||||
# Allow failing jobs for bleeding-edge Node.js versions.
|
||||
matrix:
|
||||
allow_failures:
|
||||
- nodejs_version: "0.11"
|
||||
|
||||
# Install scripts. (runs after repo cloning)
|
||||
install:
|
||||
# Get the latest stable version of Node 0.STABLE.latest
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
# Typical npm stuff.
|
||||
- npm install
|
||||
# Grunt-specific stuff.
|
||||
- npm install -g grunt-cli
|
||||
|
||||
# Post-install test scripts.
|
||||
test_script:
|
||||
# Output useful info for debugging.
|
||||
- node --version
|
||||
- npm --version
|
||||
# Run test
|
||||
- grunt test
|
||||
|
||||
# Don't actually build.
|
||||
build: off
|
||||
|
||||
# Set build version format here instead of in the admin panel.
|
||||
version: "{build}"
|
||||
726
bin/lessc
726
bin/lessc
@@ -3,8 +3,15 @@
|
||||
var path = require('path'),
|
||||
fs = require('../lib/less-node/fs'),
|
||||
os = require('os'),
|
||||
errno,
|
||||
mkdirp;
|
||||
|
||||
try {
|
||||
errno = require('errno');
|
||||
} catch (err) {
|
||||
errno = null;
|
||||
}
|
||||
|
||||
var less = require('../lib/less-node'),
|
||||
pluginLoader = new less.PluginLoader(less),
|
||||
plugin,
|
||||
@@ -27,8 +34,8 @@ var silent = false,
|
||||
ieCompat: true,
|
||||
strictMath: false,
|
||||
strictUnits: false,
|
||||
globalVariables: '',
|
||||
modifyVariables: '',
|
||||
globalVars: null,
|
||||
modifyVars: null,
|
||||
urlArgs: '',
|
||||
plugins: plugins
|
||||
};
|
||||
@@ -52,16 +59,16 @@ var checkArgFunc = function(arg, option) {
|
||||
var checkBooleanArg = function(arg) {
|
||||
var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg);
|
||||
if (!onOff) {
|
||||
console.log(" unable to parse "+arg+" as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
|
||||
console.log(" unable to parse " + arg + " as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
|
||||
continueProcessing = false;
|
||||
return false;
|
||||
}
|
||||
return Boolean(onOff[2]);
|
||||
};
|
||||
|
||||
var parseVariableOption = function(option) {
|
||||
var parseVariableOption = function(option, variables) {
|
||||
var parts = option.split('=', 2);
|
||||
return '@' + parts[0] + ': ' + parts[1] + ';\n';
|
||||
variables[parts[0]] = parts[1];
|
||||
};
|
||||
|
||||
var warningMessages = "";
|
||||
@@ -73,362 +80,411 @@ function printUsage() {
|
||||
continueProcessing = false;
|
||||
}
|
||||
|
||||
args = args.filter(function (arg) {
|
||||
var match;
|
||||
// self executing function so we can return
|
||||
(function() {
|
||||
args = args.filter(function (arg) {
|
||||
var match;
|
||||
|
||||
match = arg.match(/^-I(.+)$/);
|
||||
if (match) {
|
||||
options.paths.push(match[1]);
|
||||
return false;
|
||||
}
|
||||
match = arg.match(/^-I(.+)$/);
|
||||
if (match) {
|
||||
options.paths.push(match[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
|
||||
if (match) { arg = match[1]; }
|
||||
else { return arg; }
|
||||
match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
|
||||
if (match) {
|
||||
arg = match[1];
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
|
||||
switch (arg) {
|
||||
case 'v':
|
||||
case 'version':
|
||||
console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]");
|
||||
continueProcessing = false;
|
||||
break;
|
||||
case 'verbose':
|
||||
verbose = true;
|
||||
break;
|
||||
case 's':
|
||||
case 'silent':
|
||||
silent = true;
|
||||
break;
|
||||
case 'l':
|
||||
case 'lint':
|
||||
options.lint = true;
|
||||
break;
|
||||
case 'strict-imports':
|
||||
options.strictImports = true;
|
||||
break;
|
||||
case 'h':
|
||||
case 'help':
|
||||
printUsage();
|
||||
break;
|
||||
case 'x':
|
||||
case 'compress':
|
||||
options.compress = true;
|
||||
break;
|
||||
case 'insecure':
|
||||
options.insecure = true;
|
||||
break;
|
||||
case 'M':
|
||||
case 'depends':
|
||||
options.depends = true;
|
||||
break;
|
||||
case 'max-line-len':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.maxLineLen = parseInt(match[2], 10);
|
||||
if (options.maxLineLen <= 0) {
|
||||
options.maxLineLen = -1;
|
||||
switch (arg) {
|
||||
case 'v':
|
||||
case 'version':
|
||||
console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]");
|
||||
continueProcessing = false;
|
||||
break;
|
||||
case 'verbose':
|
||||
verbose = true;
|
||||
break;
|
||||
case 's':
|
||||
case 'silent':
|
||||
silent = true;
|
||||
break;
|
||||
case 'l':
|
||||
case 'lint':
|
||||
options.lint = true;
|
||||
break;
|
||||
case 'strict-imports':
|
||||
options.strictImports = true;
|
||||
break;
|
||||
case 'h':
|
||||
case 'help':
|
||||
printUsage();
|
||||
break;
|
||||
case 'x':
|
||||
case 'compress':
|
||||
options.compress = true;
|
||||
break;
|
||||
case 'insecure':
|
||||
options.insecure = true;
|
||||
break;
|
||||
case 'M':
|
||||
case 'depends':
|
||||
options.depends = true;
|
||||
break;
|
||||
case 'max-line-len':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.maxLineLen = parseInt(match[2], 10);
|
||||
if (options.maxLineLen <= 0) {
|
||||
options.maxLineLen = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'no-color':
|
||||
options.color = false;
|
||||
break;
|
||||
case 'no-ie-compat':
|
||||
options.ieCompat = false;
|
||||
break;
|
||||
case 'no-js':
|
||||
options.javascriptEnabled = false;
|
||||
break;
|
||||
case 'include-path':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.paths = match[2].split(os.type().match(/Windows/) ? ';' : ':')
|
||||
.map(function(p) {
|
||||
if (p) {
|
||||
return path.resolve(process.cwd(), p);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'line-numbers':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.dumpLineNumbers = match[2];
|
||||
}
|
||||
break;
|
||||
case 'source-map':
|
||||
options.sourceMap = true;
|
||||
if (match[2]) {
|
||||
sourceMapOptions.sourceMapFullFilename = match[2];
|
||||
}
|
||||
break;
|
||||
case 'source-map-rootpath':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
sourceMapOptions.sourceMapRootpath = match[2];
|
||||
}
|
||||
break;
|
||||
case 'source-map-basepath':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
sourceMapOptions.sourceMapBasepath = match[2];
|
||||
}
|
||||
break;
|
||||
case 'source-map-map-inline':
|
||||
sourceMapFileInline = true;
|
||||
options.sourceMap = true;
|
||||
break;
|
||||
case 'source-map-less-inline':
|
||||
sourceMapOptions.outputSourceFiles = true;
|
||||
break;
|
||||
case 'source-map-url':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
sourceMapOptions.sourceMapURL = match[2];
|
||||
}
|
||||
break;
|
||||
case 'rp':
|
||||
case 'rootpath':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.rootpath = match[2].replace(/\\/g, '/');
|
||||
}
|
||||
break;
|
||||
case "ru":
|
||||
case "relative-urls":
|
||||
options.relativeUrls = true;
|
||||
break;
|
||||
case "sm":
|
||||
case "strict-math":
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.strictMath = checkBooleanArg(match[2]);
|
||||
}
|
||||
break;
|
||||
case "su":
|
||||
case "strict-units":
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.strictUnits = checkBooleanArg(match[2]);
|
||||
}
|
||||
break;
|
||||
case "global-var":
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.globalVariables += parseVariableOption(match[2]);
|
||||
}
|
||||
break;
|
||||
case "modify-var":
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.modifyVariables += parseVariableOption(match[2]);
|
||||
}
|
||||
break;
|
||||
case 'url-args':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.urlArgs = match[2];
|
||||
}
|
||||
break;
|
||||
case 'plugin':
|
||||
var splitupArg = match[2].match(/^([^=]+)(=(.*))?/),
|
||||
name = splitupArg[1],
|
||||
pluginOptions = splitupArg[3];
|
||||
break;
|
||||
case 'no-color':
|
||||
options.color = false;
|
||||
break;
|
||||
case 'no-ie-compat':
|
||||
options.ieCompat = false;
|
||||
break;
|
||||
case 'no-js':
|
||||
options.javascriptEnabled = false;
|
||||
break;
|
||||
case 'include-path':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.paths = match[2].split(os.type().match(/Windows/) ? ';' : ':')
|
||||
.map(function(p) {
|
||||
if (p) {
|
||||
return path.resolve(process.cwd(), p);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'line-numbers':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.dumpLineNumbers = match[2];
|
||||
}
|
||||
break;
|
||||
case 'source-map':
|
||||
options.sourceMap = true;
|
||||
if (match[2]) {
|
||||
sourceMapOptions.sourceMapFullFilename = match[2];
|
||||
}
|
||||
break;
|
||||
case 'source-map-rootpath':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
sourceMapOptions.sourceMapRootpath = match[2];
|
||||
}
|
||||
break;
|
||||
case 'source-map-basepath':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
sourceMapOptions.sourceMapBasepath = match[2];
|
||||
}
|
||||
break;
|
||||
case 'source-map-map-inline':
|
||||
sourceMapFileInline = true;
|
||||
options.sourceMap = true;
|
||||
break;
|
||||
case 'source-map-less-inline':
|
||||
sourceMapOptions.outputSourceFiles = true;
|
||||
break;
|
||||
case 'source-map-url':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
sourceMapOptions.sourceMapURL = match[2];
|
||||
}
|
||||
break;
|
||||
case 'rp':
|
||||
case 'rootpath':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.rootpath = match[2].replace(/\\/g, '/');
|
||||
}
|
||||
break;
|
||||
case "ru":
|
||||
case "relative-urls":
|
||||
options.relativeUrls = true;
|
||||
break;
|
||||
case "sm":
|
||||
case "strict-math":
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.strictMath = checkBooleanArg(match[2]);
|
||||
}
|
||||
break;
|
||||
case "su":
|
||||
case "strict-units":
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.strictUnits = checkBooleanArg(match[2]);
|
||||
}
|
||||
break;
|
||||
case "global-var":
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
if (!options.globalVars) {
|
||||
options.globalVars = {};
|
||||
}
|
||||
parseVariableOption(match[2], options.globalVars);
|
||||
}
|
||||
break;
|
||||
case "modify-var":
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
if (!options.modifyVars) {
|
||||
options.modifyVars = {};
|
||||
}
|
||||
|
||||
plugin = pluginLoader.tryLoadPlugin(name, pluginOptions);
|
||||
if (plugin) {
|
||||
plugins.push(plugin);
|
||||
} else {
|
||||
console.log("Unable to load plugin " + name + " please make sure that it is installed under or at the same level as less");
|
||||
console.log();
|
||||
printUsage();
|
||||
currentErrorcode = 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
plugin = pluginLoader.tryLoadPlugin("less-plugin-" + arg, match[2]);
|
||||
if (plugin) {
|
||||
plugins.push(plugin);
|
||||
} else {
|
||||
console.log("Unable to interpret argument " + arg + " - if it is a plugin (less-plugin-" + arg + "), make sure that it is installed under or at the same level as less");
|
||||
console.log();
|
||||
printUsage();
|
||||
currentErrorcode = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
parseVariableOption(match[2], options.modifyVars);
|
||||
}
|
||||
break;
|
||||
case 'url-args':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.urlArgs = match[2];
|
||||
}
|
||||
break;
|
||||
case 'plugin':
|
||||
var splitupArg = match[2].match(/^([^=]+)(=(.*))?/),
|
||||
name = splitupArg[1],
|
||||
pluginOptions = splitupArg[3];
|
||||
|
||||
if (!continueProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
var input = args[1];
|
||||
if (input && input != '-') {
|
||||
input = path.resolve(process.cwd(), input);
|
||||
}
|
||||
var output = args[2];
|
||||
var outputbase = args[2];
|
||||
if (output) {
|
||||
output = path.resolve(process.cwd(), output);
|
||||
if (warningMessages) {
|
||||
console.log(warningMessages);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.sourceMap) {
|
||||
|
||||
sourceMapOptions.sourceMapInputFilename = input;
|
||||
if (!sourceMapOptions.sourceMapFullFilename) {
|
||||
if (!output && !sourceMapFileInline) {
|
||||
console.log("the sourcemap option only has an optional filename if the css filename is given");
|
||||
console.log("consider adding --source-map-map-inline which embeds the sourcemap into the css");
|
||||
return;
|
||||
plugin = pluginLoader.tryLoadPlugin(name, pluginOptions);
|
||||
if (plugin) {
|
||||
plugins.push(plugin);
|
||||
} else {
|
||||
console.log("Unable to load plugin " + name +
|
||||
" please make sure that it is installed under or at the same level as less");
|
||||
console.log();
|
||||
printUsage();
|
||||
currentErrorcode = 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
plugin = pluginLoader.tryLoadPlugin("less-plugin-" + arg, match[2]);
|
||||
if (plugin) {
|
||||
plugins.push(plugin);
|
||||
} else {
|
||||
console.log("Unable to interpret argument " + arg +
|
||||
" - if it is a plugin (less-plugin-" + arg + "), make sure that it is installed under or at" +
|
||||
" the same level as less");
|
||||
console.log();
|
||||
printUsage();
|
||||
currentErrorcode = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// its in the same directory, so always just the basename
|
||||
sourceMapOptions.sourceMapOutputFilename = path.basename(output);
|
||||
sourceMapOptions.sourceMapFullFilename = output + ".map";
|
||||
// its in the same directory, so always just the basename
|
||||
sourceMapOptions.sourceMapFilename = path.basename(sourceMapOptions.sourceMapFullFilename);
|
||||
} else if (options.sourceMap && !sourceMapFileInline) {
|
||||
var mapFilename = path.resolve(process.cwd(), sourceMapOptions.sourceMapFullFilename),
|
||||
mapDir = path.dirname(mapFilename),
|
||||
outputDir = path.dirname(output);
|
||||
// find the path from the map to the output file
|
||||
sourceMapOptions.sourceMapOutputFilename = path.join(
|
||||
path.relative(mapDir, outputDir), path.basename(output));
|
||||
});
|
||||
|
||||
// make the sourcemap filename point to the sourcemap relative to the css file output directory
|
||||
sourceMapOptions.sourceMapFilename = path.join(
|
||||
path.relative(outputDir, mapDir), path.basename(sourceMapOptions.sourceMapFullFilename));
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceMapOptions.sourceMapBasepath === undefined) {
|
||||
sourceMapOptions.sourceMapBasepath = input ? path.dirname(input) : process.cwd();
|
||||
}
|
||||
|
||||
if (sourceMapOptions.sourceMapRootpath === undefined) {
|
||||
var pathToMap = path.dirname(sourceMapFileInline ? output : sourceMapOptions.sourceMapFullFilename),
|
||||
pathToInput = path.dirname(sourceMapOptions.sourceMapInputFilename);
|
||||
sourceMapOptions.sourceMapRootpath = path.relative(pathToMap, pathToInput);
|
||||
}
|
||||
|
||||
|
||||
if (! input) {
|
||||
console.log("lessc: no input files");
|
||||
console.log("");
|
||||
printUsage();
|
||||
currentErrorcode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
var ensureDirectory = function (filepath) {
|
||||
var dir = path.dirname(filepath),
|
||||
cmd,
|
||||
existsSync = fs.existsSync || path.existsSync;
|
||||
if (!existsSync(dir)) {
|
||||
if (mkdirp === undefined) {
|
||||
try {mkdirp = require('mkdirp');}
|
||||
catch(e) { mkdirp = null; }
|
||||
}
|
||||
cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
|
||||
cmd(dir);
|
||||
}
|
||||
};
|
||||
|
||||
if (options.depends) {
|
||||
if (!outputbase) {
|
||||
console.log("option --depends requires an output path to be specified");
|
||||
if (!continueProcessing) {
|
||||
return;
|
||||
}
|
||||
process.stdout.write(outputbase + ": ");
|
||||
}
|
||||
|
||||
if (!sourceMapFileInline) {
|
||||
var writeSourceMap = function(output) {
|
||||
var filename = sourceMapOptions.sourceMapFullFilename;
|
||||
ensureDirectory(filename);
|
||||
fs.writeFileSync(filename, output, 'utf8');
|
||||
};
|
||||
}
|
||||
var input = args[1];
|
||||
if (input && input != '-') {
|
||||
input = path.resolve(process.cwd(), input);
|
||||
}
|
||||
var output = args[2];
|
||||
var outputbase = args[2];
|
||||
if (output) {
|
||||
output = path.resolve(process.cwd(), output);
|
||||
if (warningMessages) {
|
||||
console.log(warningMessages);
|
||||
}
|
||||
}
|
||||
|
||||
var parseLessFile = function (e, data) {
|
||||
if (e) {
|
||||
console.log("lessc: " + e.message);
|
||||
if (options.sourceMap) {
|
||||
|
||||
sourceMapOptions.sourceMapInputFilename = input;
|
||||
if (!sourceMapOptions.sourceMapFullFilename) {
|
||||
if (!output && !sourceMapFileInline) {
|
||||
console.log("the sourcemap option only has an optional filename if the css filename is given");
|
||||
console.log("consider adding --source-map-map-inline which embeds the sourcemap into the css");
|
||||
return;
|
||||
}
|
||||
// its in the same directory, so always just the basename
|
||||
sourceMapOptions.sourceMapOutputFilename = path.basename(output);
|
||||
sourceMapOptions.sourceMapFullFilename = output + ".map";
|
||||
// its in the same directory, so always just the basename
|
||||
sourceMapOptions.sourceMapFilename = path.basename(sourceMapOptions.sourceMapFullFilename);
|
||||
} else if (options.sourceMap && !sourceMapFileInline) {
|
||||
var mapFilename = path.resolve(process.cwd(), sourceMapOptions.sourceMapFullFilename),
|
||||
mapDir = path.dirname(mapFilename),
|
||||
outputDir = path.dirname(output);
|
||||
// find the path from the map to the output file
|
||||
sourceMapOptions.sourceMapOutputFilename = path.join(
|
||||
path.relative(mapDir, outputDir), path.basename(output));
|
||||
|
||||
// make the sourcemap filename point to the sourcemap relative to the css file output directory
|
||||
sourceMapOptions.sourceMapFilename = path.join(
|
||||
path.relative(outputDir, mapDir), path.basename(sourceMapOptions.sourceMapFullFilename));
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceMapOptions.sourceMapBasepath === undefined) {
|
||||
sourceMapOptions.sourceMapBasepath = input ? path.dirname(input) : process.cwd();
|
||||
}
|
||||
|
||||
if (sourceMapOptions.sourceMapRootpath === undefined) {
|
||||
var pathToMap = path.dirname(sourceMapFileInline ? output : sourceMapOptions.sourceMapFullFilename),
|
||||
pathToInput = path.dirname(sourceMapOptions.sourceMapInputFilename);
|
||||
sourceMapOptions.sourceMapRootpath = path.relative(pathToMap, pathToInput);
|
||||
}
|
||||
|
||||
if (! input) {
|
||||
console.log("lessc: no input files");
|
||||
console.log("");
|
||||
printUsage();
|
||||
currentErrorcode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
data = options.globalVariables + data + '\n' + options.modifyVariables;
|
||||
|
||||
options.paths = [path.dirname(input)].concat(options.paths);
|
||||
options.filename = input;
|
||||
var ensureDirectory = function (filepath) {
|
||||
var dir = path.dirname(filepath),
|
||||
cmd,
|
||||
existsSync = fs.existsSync || path.existsSync;
|
||||
if (!existsSync(dir)) {
|
||||
if (mkdirp === undefined) {
|
||||
try {mkdirp = require('mkdirp');}
|
||||
catch(e) { mkdirp = null; }
|
||||
}
|
||||
cmd = mkdirp && mkdirp.sync || fs.mkdirSync;
|
||||
cmd(dir);
|
||||
}
|
||||
};
|
||||
|
||||
if (options.depends) {
|
||||
var parser = new(less.Parser)(options);
|
||||
parser.parse(data, function (err, tree) {
|
||||
if (err) {
|
||||
less.writeError(err, options);
|
||||
currentErrorcode = 1;
|
||||
return;
|
||||
}
|
||||
for(var file in parser.imports.files) {
|
||||
if (parser.imports.files.hasOwnProperty(file)) {
|
||||
process.stdout.write(file + " ");
|
||||
if (!outputbase) {
|
||||
console.log("option --depends requires an output path to be specified");
|
||||
return;
|
||||
}
|
||||
process.stdout.write(outputbase + ": ");
|
||||
}
|
||||
|
||||
if (!sourceMapFileInline) {
|
||||
var writeSourceMap = function(output, onDone) {
|
||||
var filename = sourceMapOptions.sourceMapFullFilename;
|
||||
ensureDirectory(filename);
|
||||
fs.writeFile(filename, output, 'utf8', function (err) {
|
||||
if (err) {
|
||||
var description = "Error: ";
|
||||
if (errno && errno.errno[err.errno]) {
|
||||
description += errno.errno[err.errno].description;
|
||||
} else {
|
||||
description += err.code + " " + err.message;
|
||||
}
|
||||
less.logger.error('lessc: failed to create file ' + filename);
|
||||
less.logger.error(description);
|
||||
} else {
|
||||
less.logger.info('lessc: wrote ' + filename);
|
||||
}
|
||||
onDone();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var writeSourceMapIfNeeded = function(output, onDone) {
|
||||
if (options.sourceMap && !sourceMapFileInline) {
|
||||
writeSourceMap(output, onDone);
|
||||
} else {
|
||||
onDone();
|
||||
}
|
||||
};
|
||||
|
||||
var writeOutput = function(output, result, onSuccess) {
|
||||
if (output) {
|
||||
ensureDirectory(output);
|
||||
fs.writeFile(output, result.css, {encoding: 'utf8'}, function (err) {
|
||||
if (err) {
|
||||
var description = "Error: ";
|
||||
if (errno && errno.errno[err.errno]) {
|
||||
description += errno.errno[err.errno].description;
|
||||
} else {
|
||||
description += err.code + " " + err.message;
|
||||
}
|
||||
less.logger.error('lessc: failed to create file ' + output);
|
||||
less.logger.error(description);
|
||||
} else {
|
||||
less.logger.info('lessc: wrote ' + output);
|
||||
onSuccess();
|
||||
}
|
||||
});
|
||||
} else if (!options.depends) {
|
||||
process.stdout.write(result.css);
|
||||
onSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
var logDependencies = function(options, result) {
|
||||
if (options.depends) {
|
||||
var depends = "";
|
||||
for (var i = 0; i < result.imports.length; i++) {
|
||||
depends += result.imports[i] + " ";
|
||||
}
|
||||
console.log("");
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(depends);
|
||||
}
|
||||
};
|
||||
|
||||
if (options.lint) {
|
||||
options.sourceMap = false;
|
||||
}
|
||||
sourceMapOptions.sourceMapFileInline = sourceMapFileInline;
|
||||
var parseLessFile = function (e, data) {
|
||||
if (e) {
|
||||
console.log("lessc: " + e.message);
|
||||
currentErrorcode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.sourceMap) {
|
||||
options.sourceMap = sourceMapOptions;
|
||||
}
|
||||
data = data.replace(/^\uFEFF/, '');
|
||||
|
||||
less.logger.addListener({
|
||||
info: function(msg) {
|
||||
if (verbose) {
|
||||
options.paths = [path.dirname(input)].concat(options.paths);
|
||||
options.filename = input;
|
||||
|
||||
if (options.lint) {
|
||||
options.sourceMap = false;
|
||||
}
|
||||
sourceMapOptions.sourceMapFileInline = sourceMapFileInline;
|
||||
|
||||
if (options.sourceMap) {
|
||||
options.sourceMap = sourceMapOptions;
|
||||
}
|
||||
|
||||
less.logger.addListener({
|
||||
info: function(msg) {
|
||||
if (verbose) {
|
||||
console.log(msg);
|
||||
}
|
||||
},
|
||||
warn: function(msg) {
|
||||
// do not show warning if outputting css to the console or the silent option is used
|
||||
if (!silent && output) {
|
||||
console.warn(msg);
|
||||
}
|
||||
},
|
||||
error: function(msg) {
|
||||
console.log(msg);
|
||||
}
|
||||
},
|
||||
warn: function(msg) {
|
||||
if (!silent) {
|
||||
console.warn(msg);
|
||||
}
|
||||
},
|
||||
error: function(msg) {
|
||||
console.log(msg);
|
||||
}
|
||||
});
|
||||
|
||||
less.render(data, options)
|
||||
.then(function(result) {
|
||||
if(!options.lint) {
|
||||
if (output) {
|
||||
ensureDirectory(output);
|
||||
fs.writeFileSync(output, result.css, 'utf8');
|
||||
less.logger.info('lessc: wrote ' + output);
|
||||
} else {
|
||||
process.stdout.write(result.css);
|
||||
}
|
||||
if (options.sourceMap && !sourceMapFileInline) {
|
||||
writeSourceMap(result.map);
|
||||
}
|
||||
}
|
||||
},
|
||||
function(err) {
|
||||
less.writeError(err, options);
|
||||
currentErrorcode = 1;
|
||||
});
|
||||
};
|
||||
|
||||
if (input != '-') {
|
||||
fs.readFile(input, 'utf8', parseLessFile);
|
||||
} else {
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding('utf8');
|
||||
less.render(data, options)
|
||||
.then(function(result) {
|
||||
if (!options.lint) {
|
||||
writeOutput(output, result, function() {
|
||||
writeSourceMapIfNeeded(result.map, function() {
|
||||
logDependencies(options, result);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
function(err) {
|
||||
less.writeError(err, options);
|
||||
currentErrorcode = 1;
|
||||
});
|
||||
};
|
||||
|
||||
var buffer = '';
|
||||
process.stdin.on('data', function(data) {
|
||||
buffer += data;
|
||||
});
|
||||
if (input != '-') {
|
||||
fs.readFile(input, 'utf8', parseLessFile);
|
||||
} else {
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('end', function() {
|
||||
parseLessFile(false, buffer);
|
||||
});
|
||||
}
|
||||
var buffer = '';
|
||||
process.stdin.on('data', function(data) {
|
||||
buffer += data;
|
||||
});
|
||||
|
||||
process.stdin.on('end', function() {
|
||||
parseLessFile(false, buffer);
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "less",
|
||||
"version": "2.0.0-b1",
|
||||
"main": "./dist/less.js",
|
||||
"version": "2.3.1",
|
||||
"main": "dist/less.js",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"benchmark",
|
||||
@@ -15,6 +15,7 @@
|
||||
"Gruntfile.js",
|
||||
"*.json",
|
||||
"*.yml",
|
||||
"build.gradle",
|
||||
"gradlew",
|
||||
"gradlew.bat",
|
||||
".gitattributes",
|
||||
|
||||
1
browser.js
Normal file
1
browser.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('./lib/less-browser');
|
||||
@@ -1,4 +1,4 @@
|
||||
if (typeof(window) === 'undefined') { less = {} }
|
||||
if (typeof window === 'undefined') { less = {} }
|
||||
else { less = window.less = {} }
|
||||
tree = less.tree = {};
|
||||
less.mode = 'rhino';
|
||||
|
||||
2204
dist/less.js
vendored
2204
dist/less.js
vendored
File diff suppressed because it is too large
Load Diff
13
dist/less.min.js
vendored
13
dist/less.min.js
vendored
File diff suppressed because one or more lines are too long
46
lib/less-browser/add-default-options.js
Normal file
46
lib/less-browser/add-default-options.js
Normal file
@@ -0,0 +1,46 @@
|
||||
var addDataAttr = require("./utils").addDataAttr,
|
||||
browser = require("./browser");
|
||||
|
||||
module.exports = function(window, options) {
|
||||
|
||||
// use options from the current script tag data attribues
|
||||
addDataAttr(options, browser.currentScript(window));
|
||||
|
||||
if (options.isFileProtocol === undefined) {
|
||||
options.isFileProtocol = /^(file|(chrome|safari)(-extension)?|resource|qrc|app):/.test(window.location.protocol);
|
||||
}
|
||||
|
||||
// Load styles asynchronously (default: false)
|
||||
//
|
||||
// This is set to `false` by default, so that the body
|
||||
// doesn't start loading before the stylesheets are parsed.
|
||||
// Setting this to `true` can result in flickering.
|
||||
//
|
||||
options.async = options.async || false;
|
||||
options.fileAsync = options.fileAsync || false;
|
||||
|
||||
// Interval between watch polls
|
||||
options.poll = options.poll || (options.isFileProtocol ? 1000 : 1500);
|
||||
|
||||
options.env = options.env || (window.location.hostname == '127.0.0.1' ||
|
||||
window.location.hostname == '0.0.0.0' ||
|
||||
window.location.hostname == 'localhost' ||
|
||||
(window.location.port &&
|
||||
window.location.port.length > 0) ||
|
||||
options.isFileProtocol ? 'development'
|
||||
: 'production');
|
||||
|
||||
var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(window.location.hash);
|
||||
if (dumpLineNumbers) {
|
||||
options.dumpLineNumbers = dumpLineNumbers[1];
|
||||
}
|
||||
|
||||
if (options.useFileCache === undefined) {
|
||||
options.useFileCache = true;
|
||||
}
|
||||
|
||||
if (options.onReady === undefined) {
|
||||
options.onReady = true;
|
||||
}
|
||||
|
||||
};
|
||||
26
lib/less-browser/bootstrap.js
vendored
Normal file
26
lib/less-browser/bootstrap.js
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Kicks off less and compiles any stylesheets
|
||||
* used in the browser distributed version of less
|
||||
* to kick-start less using the browser api
|
||||
*/
|
||||
/*global window */
|
||||
|
||||
// shim Promise if required
|
||||
require('promise/polyfill.js');
|
||||
|
||||
var options = window.less || {};
|
||||
require("./add-default-options")(window, options);
|
||||
|
||||
var less = module.exports = require("./index")(window, options);
|
||||
|
||||
if (options.onReady) {
|
||||
if (/!watch/.test(window.location.hash)) {
|
||||
less.watch();
|
||||
}
|
||||
|
||||
less.pageLoadFinished = less.registerStylesheets().then(
|
||||
function () {
|
||||
return less.refresh(less.env === 'development');
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -8,50 +8,57 @@ module.exports = {
|
||||
var id = 'less:' + (sheet.title || utils.extractId(href));
|
||||
|
||||
// If this has already been inserted into the DOM, we may need to replace it
|
||||
var oldCss = document.getElementById(id);
|
||||
var keepOldCss = false;
|
||||
var oldStyleNode = document.getElementById(id);
|
||||
var keepOldStyleNode = false;
|
||||
|
||||
// Create a new stylesheet node for insertion or (if necessary) replacement
|
||||
var css = document.createElement('style');
|
||||
css.setAttribute('type', 'text/css');
|
||||
var styleNode = document.createElement('style');
|
||||
styleNode.setAttribute('type', 'text/css');
|
||||
if (sheet.media) {
|
||||
css.setAttribute('media', sheet.media);
|
||||
styleNode.setAttribute('media', sheet.media);
|
||||
}
|
||||
css.id = id;
|
||||
styleNode.id = id;
|
||||
|
||||
if (!css.styleSheet) {
|
||||
css.appendChild(document.createTextNode(styles));
|
||||
if (!styleNode.styleSheet) {
|
||||
styleNode.appendChild(document.createTextNode(styles));
|
||||
|
||||
// If new contents match contents of oldCss, don't replace oldCss
|
||||
keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 &&
|
||||
oldCss.firstChild.nodeValue === css.firstChild.nodeValue);
|
||||
// If new contents match contents of oldStyleNode, don't replace oldStyleNode
|
||||
keepOldStyleNode = (oldStyleNode !== null && oldStyleNode.childNodes.length > 0 && styleNode.childNodes.length > 0 &&
|
||||
oldStyleNode.firstChild.nodeValue === styleNode.firstChild.nodeValue);
|
||||
}
|
||||
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
|
||||
// If there is no oldCss, just append; otherwise, only append if we need
|
||||
// to replace oldCss with an updated stylesheet
|
||||
if (oldCss === null || keepOldCss === false) {
|
||||
// If there is no oldStyleNode, just append; otherwise, only append if we need
|
||||
// to replace oldStyleNode with an updated stylesheet
|
||||
if (oldStyleNode === null || keepOldStyleNode === false) {
|
||||
var nextEl = sheet && sheet.nextSibling || null;
|
||||
if (nextEl) {
|
||||
nextEl.parentNode.insertBefore(css, nextEl);
|
||||
nextEl.parentNode.insertBefore(styleNode, nextEl);
|
||||
} else {
|
||||
head.appendChild(css);
|
||||
head.appendChild(styleNode);
|
||||
}
|
||||
}
|
||||
if (oldCss && keepOldCss === false) {
|
||||
oldCss.parentNode.removeChild(oldCss);
|
||||
if (oldStyleNode && keepOldStyleNode === false) {
|
||||
oldStyleNode.parentNode.removeChild(oldStyleNode);
|
||||
}
|
||||
|
||||
// For IE.
|
||||
// This needs to happen *after* the style element is added to the DOM, otherwise IE 7 and 8 may crash.
|
||||
// See http://social.msdn.microsoft.com/Forums/en-US/7e081b65-878a-4c22-8e68-c10d39c2ed32/internet-explorer-crashes-appending-style-element-to-head
|
||||
if (css.styleSheet) {
|
||||
if (styleNode.styleSheet) {
|
||||
try {
|
||||
css.styleSheet.cssText = styles;
|
||||
styleNode.styleSheet.cssText = styles;
|
||||
} catch (e) {
|
||||
throw new Error("Couldn't reassign styleSheet.cssText.");
|
||||
}
|
||||
}
|
||||
},
|
||||
currentScript: function(window) {
|
||||
var document = window.document;
|
||||
return document.currentScript || (function() {
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
return scripts[scripts.length - 1];
|
||||
})();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,19 +4,19 @@ module.exports = function(window, options, logger) {
|
||||
var cache = null;
|
||||
if (options.env !== 'development') {
|
||||
try {
|
||||
cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
|
||||
cache = (typeof window.localStorage === 'undefined') ? null : window.localStorage;
|
||||
} catch (_) {}
|
||||
}
|
||||
return {
|
||||
setCSS: function(path, lastModified, styles) {
|
||||
if (cache) {
|
||||
logger.info('saving ' + path+ ' to cache.');
|
||||
logger.info('saving ' + path + ' to cache.');
|
||||
try {
|
||||
cache.setItem(path, styles);
|
||||
cache.setItem(path + ':timestamp', lastModified);
|
||||
} catch(e) {
|
||||
//TODO - could do with adding more robust error handling
|
||||
logger.error('failed to save');
|
||||
logger.error('failed to save "' + path + '" to local storage for caching.');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,87 +1,88 @@
|
||||
/*global window, XMLHttpRequest */
|
||||
|
||||
module.exports = function(options, isFileProtocol, logger) {
|
||||
module.exports = function(options, logger) {
|
||||
|
||||
var PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
|
||||
AbstractFileManager = require("../less/environment/abstract-file-manager.js");
|
||||
var AbstractFileManager = require("../less/environment/abstract-file-manager.js");
|
||||
|
||||
var fileCache = {};
|
||||
var fileCache = {};
|
||||
|
||||
//TODOS - move log somewhere. pathDiff and doing something similar in node. use pathDiff in the other browser file for the initial load
|
||||
// isFileProtocol is global
|
||||
//TODOS - move log somewhere. pathDiff and doing something similar in node. use pathDiff in the other browser file for the initial load
|
||||
|
||||
function getXMLHttpRequest() {
|
||||
if (window.XMLHttpRequest && (window.location.protocol !== "file:" || !("ActiveXObject" in window))) {
|
||||
return new XMLHttpRequest();
|
||||
} else {
|
||||
try {
|
||||
/*global ActiveXObject */
|
||||
return new ActiveXObject("Microsoft.XMLHTTP");
|
||||
} catch (e) {
|
||||
logger.error("browser doesn't support AJAX.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var FileManager = function() {
|
||||
};
|
||||
|
||||
FileManager.prototype = new AbstractFileManager();
|
||||
|
||||
FileManager.prototype.alwaysMakePathsAbsolute = function alwaysMakePathsAbsolute() {
|
||||
return true;
|
||||
};
|
||||
FileManager.prototype.join = function join(basePath, laterPath) {
|
||||
if (!basePath) {
|
||||
return laterPath;
|
||||
}
|
||||
return this.extractUrlParts(laterPath, basePath).path;
|
||||
};
|
||||
FileManager.prototype.doXHR = function doXHR(url, type, callback, errback) {
|
||||
|
||||
var xhr = getXMLHttpRequest();
|
||||
var async = isFileProtocol ? options.fileAsync : options.async;
|
||||
|
||||
if (typeof(xhr.overrideMimeType) === 'function') {
|
||||
xhr.overrideMimeType('text/css');
|
||||
}
|
||||
logger.debug("XHR: Getting '" + url + "'");
|
||||
xhr.open('GET', url, async);
|
||||
xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
|
||||
xhr.send(null);
|
||||
|
||||
function handleResponse(xhr, callback, errback) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
callback(xhr.responseText,
|
||||
xhr.getResponseHeader("Last-Modified"));
|
||||
} else if (typeof(errback) === 'function') {
|
||||
errback(xhr.status, url);
|
||||
}
|
||||
}
|
||||
|
||||
if (isFileProtocol && !options.fileAsync) {
|
||||
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
|
||||
callback(xhr.responseText);
|
||||
function getXMLHttpRequest() {
|
||||
if (window.XMLHttpRequest && (window.location.protocol !== "file:" || !("ActiveXObject" in window))) {
|
||||
return new XMLHttpRequest();
|
||||
} else {
|
||||
errback(xhr.status, url);
|
||||
}
|
||||
} else if (async) {
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4) {
|
||||
handleResponse(xhr, callback, errback);
|
||||
try {
|
||||
/*global ActiveXObject */
|
||||
return new ActiveXObject("Microsoft.XMLHTTP");
|
||||
} catch (e) {
|
||||
logger.error("browser doesn't support AJAX.");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
handleResponse(xhr, callback, errback);
|
||||
}
|
||||
}
|
||||
};
|
||||
FileManager.prototype.supports = function(filename, currentDirectory, options, environment) {
|
||||
return true;
|
||||
};
|
||||
|
||||
FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, options, environment) {
|
||||
return new PromiseConstructor(function(fullfill, reject) {
|
||||
var FileManager = function() {
|
||||
};
|
||||
|
||||
FileManager.prototype = new AbstractFileManager();
|
||||
|
||||
FileManager.prototype.alwaysMakePathsAbsolute = function alwaysMakePathsAbsolute() {
|
||||
return true;
|
||||
};
|
||||
FileManager.prototype.join = function join(basePath, laterPath) {
|
||||
if (!basePath) {
|
||||
return laterPath;
|
||||
}
|
||||
return this.extractUrlParts(laterPath, basePath).path;
|
||||
};
|
||||
FileManager.prototype.doXHR = function doXHR(url, type, callback, errback) {
|
||||
|
||||
var xhr = getXMLHttpRequest();
|
||||
var async = options.isFileProtocol ? options.fileAsync : options.async;
|
||||
|
||||
if (typeof xhr.overrideMimeType === 'function') {
|
||||
xhr.overrideMimeType('text/css');
|
||||
}
|
||||
logger.debug("XHR: Getting '" + url + "'");
|
||||
xhr.open('GET', url, async);
|
||||
xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
|
||||
xhr.send(null);
|
||||
|
||||
function handleResponse(xhr, callback, errback) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
callback(xhr.responseText,
|
||||
xhr.getResponseHeader("Last-Modified"));
|
||||
} else if (typeof errback === 'function') {
|
||||
errback(xhr.status, url);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.isFileProtocol && !options.fileAsync) {
|
||||
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
|
||||
callback(xhr.responseText);
|
||||
} else {
|
||||
errback(xhr.status, url);
|
||||
}
|
||||
} else if (async) {
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4) {
|
||||
handleResponse(xhr, callback, errback);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
handleResponse(xhr, callback, errback);
|
||||
}
|
||||
};
|
||||
FileManager.prototype.supports = function(filename, currentDirectory, options, environment) {
|
||||
return true;
|
||||
};
|
||||
|
||||
FileManager.prototype.clearFileCache = function() {
|
||||
fileCache = {};
|
||||
};
|
||||
|
||||
FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, options, environment, callback) {
|
||||
if (currentDirectory && !this.isPathAbsolute(filename)) {
|
||||
filename = currentDirectory + filename;
|
||||
}
|
||||
@@ -96,9 +97,9 @@ FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, o
|
||||
if (options.useFileCache && fileCache[href]) {
|
||||
try {
|
||||
var lessText = fileCache[href];
|
||||
fullfill({ contents: lessText, filename: href, webInfo: { lastModified: new Date() }});
|
||||
callback(null, { contents: lessText, filename: href, webInfo: { lastModified: new Date() }});
|
||||
} catch (e) {
|
||||
reject({filename: href, message: "Error loading file " + href + " error was " + e.message});
|
||||
callback({filename: href, message: "Error loading file " + href + " error was " + e.message});
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -108,12 +109,11 @@ FileManager.prototype.loadFile = function loadFile(filename, currentDirectory, o
|
||||
fileCache[href] = data;
|
||||
|
||||
// Use remote copy (re-parse)
|
||||
fullfill({ contents: data, filename: href, webInfo: { lastModified: lastModified }});
|
||||
callback(null, { contents: data, filename: href, webInfo: { lastModified: lastModified }});
|
||||
}, function doXHRError(status, url) {
|
||||
reject({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")", href: href });
|
||||
callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")", href: href });
|
||||
});
|
||||
}.bind(this));
|
||||
};
|
||||
};
|
||||
|
||||
return FileManager;
|
||||
return FileManager;
|
||||
};
|
||||
|
||||
@@ -1,293 +1,256 @@
|
||||
//
|
||||
// browser.js - client-side engine
|
||||
// index.js
|
||||
// Should expose the additional browser functions on to the less object
|
||||
//
|
||||
/*global window, document, location */
|
||||
var addDataAttr = require("./utils").addDataAttr,
|
||||
browser = require("./browser");
|
||||
|
||||
var less;
|
||||
module.exports = function(window, options) {
|
||||
var document = window.document;
|
||||
var less = require('../less')();
|
||||
//module.exports = less;
|
||||
less.options = options;
|
||||
var environment = less.environment,
|
||||
FileManager = require("./file-manager")(options, less.logger),
|
||||
fileManager = new FileManager();
|
||||
environment.addFileManager(fileManager);
|
||||
less.FileManager = FileManager;
|
||||
|
||||
/*
|
||||
TODO - options is now hidden - we should expose it on the less object, but not have it "as" the less object
|
||||
less = { fileAsync: true }
|
||||
then access as less.environment.options.fileAsync ?
|
||||
*/
|
||||
require("./log-listener")(less, options);
|
||||
var errors = require("./error-reporting")(window, less, options);
|
||||
var cache = less.cache = options.cache || require("./cache")(window, options, less.logger);
|
||||
|
||||
var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(window.location.protocol),
|
||||
options = window.less || {};
|
||||
|
||||
// shim Promise if required
|
||||
require('promise/polyfill.js');
|
||||
|
||||
window.less = less = require('../less')();
|
||||
var environment = less.environment,
|
||||
FileManager = require("./file-manager")(options, isFileProtocol, less.logger),
|
||||
fileManager = new FileManager();
|
||||
environment.addFileManager(fileManager);
|
||||
less.FileManager = FileManager;
|
||||
|
||||
require("./log-listener")(less, options);
|
||||
var errors = require("./error-reporting")(window, less, options);
|
||||
var browser = require("./browser");
|
||||
var cache = less.cache = options.cache || require("./cache")(window, options, less.logger);
|
||||
|
||||
options.env = options.env || (window.location.hostname == '127.0.0.1' ||
|
||||
window.location.hostname == '0.0.0.0' ||
|
||||
window.location.hostname == 'localhost' ||
|
||||
(window.location.port &&
|
||||
window.location.port.length > 0) ||
|
||||
isFileProtocol ? 'development'
|
||||
: 'production');
|
||||
|
||||
// Load styles asynchronously (default: false)
|
||||
//
|
||||
// This is set to `false` by default, so that the body
|
||||
// doesn't start loading before the stylesheets are parsed.
|
||||
// Setting this to `true` can result in flickering.
|
||||
//
|
||||
options.async = options.async || false;
|
||||
options.fileAsync = options.fileAsync || false;
|
||||
|
||||
// Interval between watch polls
|
||||
options.poll = options.poll || (isFileProtocol ? 1000 : 1500);
|
||||
|
||||
//Setup user functions
|
||||
if (options.functions) {
|
||||
less.functions.functionRegistry.addMultiple(options.functions);
|
||||
}
|
||||
|
||||
var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);
|
||||
if (dumpLineNumbers) {
|
||||
options.dumpLineNumbers = dumpLineNumbers[1];
|
||||
}
|
||||
|
||||
var typePattern = /^text\/(x-)?less$/;
|
||||
|
||||
function postProcessCSS(styles) {
|
||||
if (options.postProcessor && typeof options.postProcessor === 'function') {
|
||||
styles = options.postProcessor.call(styles, styles) || styles;
|
||||
//Setup user functions
|
||||
if (options.functions) {
|
||||
less.functions.functionRegistry.addMultiple(options.functions);
|
||||
}
|
||||
return styles;
|
||||
}
|
||||
|
||||
function clone(obj) {
|
||||
var cloned = {};
|
||||
for(var prop in obj) {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
cloned[prop] = obj[prop];
|
||||
var typePattern = /^text\/(x-)?less$/;
|
||||
|
||||
function postProcessCSS(styles) {
|
||||
if (options.postProcessor && typeof options.postProcessor === 'function') {
|
||||
styles = options.postProcessor.call(styles, styles) || styles;
|
||||
}
|
||||
return styles;
|
||||
}
|
||||
|
||||
function clone(obj) {
|
||||
var cloned = {};
|
||||
for (var prop in obj) {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
cloned[prop] = obj[prop];
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
// only really needed for phantom
|
||||
function bind(func, thisArg) {
|
||||
var curryArgs = Array.prototype.slice.call(arguments, 2);
|
||||
return function() {
|
||||
var args = curryArgs.concat(Array.prototype.slice.call(arguments, 0));
|
||||
return func.apply(thisArg, args);
|
||||
};
|
||||
}
|
||||
|
||||
function loadStyles(modifyVars) {
|
||||
var styles = document.getElementsByTagName('style'),
|
||||
style;
|
||||
|
||||
for (var i = 0; i < styles.length; i++) {
|
||||
style = styles[i];
|
||||
if (style.type.match(typePattern)) {
|
||||
var instanceOptions = clone(options);
|
||||
instanceOptions.modifyVars = modifyVars;
|
||||
var lessText = style.innerHTML || '';
|
||||
instanceOptions.filename = document.location.href.replace(/#.*$/, '');
|
||||
|
||||
/*jshint loopfunc:true */
|
||||
// use closure to store current style
|
||||
less.render(lessText, instanceOptions,
|
||||
bind(function(style, e, result) {
|
||||
if (e) {
|
||||
errors.add(e, "inline");
|
||||
} else {
|
||||
style.type = 'text/css';
|
||||
if (style.styleSheet) {
|
||||
style.styleSheet.cssText = result.css;
|
||||
} else {
|
||||
style.innerHTML = result.css;
|
||||
}
|
||||
}
|
||||
}, null, style));
|
||||
}
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
// only really needed for phantom
|
||||
function bind(func, thisArg) {
|
||||
var curryArgs = Array.prototype.slice.call(arguments, 2);
|
||||
return function() {
|
||||
var args = curryArgs.concat(Array.prototype.slice.call(arguments, 0));
|
||||
return func.apply(thisArg, args);
|
||||
};
|
||||
}
|
||||
function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
|
||||
|
||||
function loadStyles(modifyVars) {
|
||||
var styles = document.getElementsByTagName('style'),
|
||||
style;
|
||||
var instanceOptions = clone(options);
|
||||
addDataAttr(instanceOptions, sheet);
|
||||
instanceOptions.mime = sheet.type;
|
||||
|
||||
for (var i = 0; i < styles.length; i++) {
|
||||
style = styles[i];
|
||||
if (style.type.match(typePattern)) {
|
||||
var instanceOptions = clone(options);
|
||||
if (modifyVars) {
|
||||
instanceOptions.modifyVars = modifyVars;
|
||||
var lessText = style.innerHTML || '';
|
||||
instanceOptions.filename = document.location.href.replace(/#.*$/, '');
|
||||
}
|
||||
|
||||
if (modifyVars || instanceOptions.globalVars) {
|
||||
instanceOptions.useFileCache = true;
|
||||
function loadInitialFileCallback(loadedFile) {
|
||||
|
||||
var data = loadedFile.contents,
|
||||
path = loadedFile.filename,
|
||||
webInfo = loadedFile.webInfo;
|
||||
|
||||
var newFileInfo = {
|
||||
currentDirectory: fileManager.getPath(path),
|
||||
filename: path,
|
||||
rootFilename: path,
|
||||
relativeUrls: instanceOptions.relativeUrls};
|
||||
|
||||
newFileInfo.entryPath = newFileInfo.currentDirectory;
|
||||
newFileInfo.rootpath = instanceOptions.rootpath || newFileInfo.currentDirectory;
|
||||
|
||||
if (webInfo) {
|
||||
webInfo.remaining = remaining;
|
||||
|
||||
if (!instanceOptions.modifyVars) {
|
||||
var css = cache.getCSS(path, webInfo);
|
||||
if (!reload && css) {
|
||||
webInfo.local = true;
|
||||
callback(null, css, data, sheet, webInfo, path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*jshint loopfunc:true */
|
||||
// use closure to store current style
|
||||
less.render(lessText, instanceOptions)
|
||||
.then(bind(function(style, result) {
|
||||
style.type = 'text/css';
|
||||
if (style.styleSheet) {
|
||||
style.styleSheet.cssText = result.css;
|
||||
} else {
|
||||
style.innerHTML = result.css;
|
||||
//TODO add tests around how this behaves when reloading
|
||||
errors.remove(path);
|
||||
|
||||
instanceOptions.rootFileInfo = newFileInfo;
|
||||
less.render(data, instanceOptions, function(e, result) {
|
||||
if (e) {
|
||||
e.href = path;
|
||||
callback(e);
|
||||
} else {
|
||||
result.css = postProcessCSS(result.css);
|
||||
if (!instanceOptions.modifyVars) {
|
||||
cache.setCSS(sheet.href, webInfo.lastModified, result.css);
|
||||
}
|
||||
}, null, style),
|
||||
function(e) {
|
||||
errors.add(e, "inline");
|
||||
});
|
||||
callback(null, result.css, data, sheet, webInfo, path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
|
||||
|
||||
var instanceOptions = clone(options);
|
||||
instanceOptions.mime = sheet.type;
|
||||
|
||||
if (modifyVars) {
|
||||
instanceOptions.modifyVars = modifyVars;
|
||||
}
|
||||
if (modifyVars || options.globalVars) {
|
||||
instanceOptions.useFileCache = true;
|
||||
}
|
||||
|
||||
fileManager.loadFile(sheet.href, null, instanceOptions, environment)
|
||||
.then(function loadInitialFileCallback(loadedFile) {
|
||||
|
||||
var data = loadedFile.contents,
|
||||
path = loadedFile.filename,
|
||||
webInfo = loadedFile.webInfo;
|
||||
|
||||
var newFileInfo = {
|
||||
currentDirectory: fileManager.getPath(path),
|
||||
filename: path,
|
||||
rootFilename: path,
|
||||
relativeUrls: instanceOptions.relativeUrls};
|
||||
|
||||
newFileInfo.entryPath = newFileInfo.currentDirectory;
|
||||
newFileInfo.rootpath = instanceOptions.rootpath || newFileInfo.currentDirectory;
|
||||
|
||||
if (webInfo) {
|
||||
webInfo.remaining = remaining;
|
||||
|
||||
var css = cache.getCSS(path, webInfo);
|
||||
if (!reload && css) {
|
||||
browser.createCSS(window.document, css, sheet);
|
||||
webInfo.local = true;
|
||||
callback(null, null, data, sheet, webInfo, path);
|
||||
fileManager.loadFile(sheet.href, null, instanceOptions, environment, function(e, loadedFile) {
|
||||
if (e) {
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
loadInitialFileCallback(loadedFile);
|
||||
});
|
||||
}
|
||||
|
||||
function loadStyleSheets(callback, reload, modifyVars) {
|
||||
for (var i = 0; i < less.sheets.length; i++) {
|
||||
loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars);
|
||||
}
|
||||
|
||||
//TODO add tests around how this behaves when reloading
|
||||
errors.remove(path);
|
||||
|
||||
instanceOptions.rootFileInfo = newFileInfo;
|
||||
less.render(data, instanceOptions)
|
||||
.then(function(result) {
|
||||
callback(null, result.css, data, sheet, webInfo, path);
|
||||
},
|
||||
function(e) {
|
||||
e.href = path;
|
||||
callback(e);
|
||||
});
|
||||
},
|
||||
function(e) {
|
||||
callback(e);
|
||||
});
|
||||
}
|
||||
|
||||
function loadStyleSheets(callback, reload, modifyVars) {
|
||||
for (var i = 0; i < less.sheets.length; i++) {
|
||||
loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars);
|
||||
}
|
||||
}
|
||||
|
||||
function initRunningMode(){
|
||||
if (less.env === 'development') {
|
||||
less.watchTimer = setInterval(function () {
|
||||
if (less.watchMode) {
|
||||
loadStyleSheets(function (e, css, _, sheet, context) {
|
||||
if (e) {
|
||||
errors.add(e, e.href || sheet.href);
|
||||
} else if (css) {
|
||||
css = postProcessCSS(css);
|
||||
browser.createCSS(window.document, css, sheet);
|
||||
cache.setCSS(sheet.href, context.lastModified, css);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, options.poll);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Watch mode
|
||||
//
|
||||
less.watch = function () {
|
||||
if (!less.watchMode ){
|
||||
less.env = 'development';
|
||||
initRunningMode();
|
||||
}
|
||||
this.watchMode = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; };
|
||||
|
||||
if (/!watch/.test(location.hash)) {
|
||||
less.watch();
|
||||
}
|
||||
|
||||
//
|
||||
// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
|
||||
//
|
||||
less.registerStylesheets = function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var links = document.getElementsByTagName('link');
|
||||
less.sheets = [];
|
||||
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
|
||||
(links[i].type.match(typePattern)))) {
|
||||
less.sheets.push(links[i]);
|
||||
}
|
||||
function initRunningMode() {
|
||||
if (less.env === 'development') {
|
||||
less.watchTimer = setInterval(function () {
|
||||
if (less.watchMode) {
|
||||
fileManager.clearFileCache();
|
||||
loadStyleSheets(function (e, css, _, sheet, webInfo) {
|
||||
if (e) {
|
||||
errors.add(e, e.href || sheet.href);
|
||||
} else if (css) {
|
||||
browser.createCSS(window.document, css, sheet);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, options.poll);
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
//
|
||||
// Watch mode
|
||||
//
|
||||
less.watch = function () {
|
||||
if (!less.watchMode ) {
|
||||
less.env = 'development';
|
||||
initRunningMode();
|
||||
}
|
||||
this.watchMode = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
//
|
||||
// With this function, it's possible to alter variables and re-render
|
||||
// CSS without reloading less-files
|
||||
//
|
||||
less.modifyVars = function(record) {
|
||||
return less.refresh(false, record);
|
||||
};
|
||||
less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; };
|
||||
|
||||
less.refresh = function (reload, modifyVars) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var startTime, endTime, totalMilliseconds;
|
||||
startTime = endTime = new Date();
|
||||
//
|
||||
// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
|
||||
//
|
||||
less.registerStylesheets = function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var links = document.getElementsByTagName('link');
|
||||
less.sheets = [];
|
||||
|
||||
loadStyleSheets(function (e, css, _, sheet, webInfo) {
|
||||
if (e) {
|
||||
errors.add(e, e.href || sheet.href);
|
||||
reject(e);
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
|
||||
(links[i].type.match(typePattern)))) {
|
||||
less.sheets.push(links[i]);
|
||||
}
|
||||
}
|
||||
if (webInfo.local) {
|
||||
less.logger.info("loading " + sheet.href + " from cache.");
|
||||
} else {
|
||||
less.logger.info("rendered " + sheet.href + " successfully.");
|
||||
css = postProcessCSS(css);
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// With this function, it's possible to alter variables and re-render
|
||||
// CSS without reloading less-files
|
||||
//
|
||||
less.modifyVars = function(record) {
|
||||
return less.refresh(true, record, false);
|
||||
};
|
||||
|
||||
less.refresh = function (reload, modifyVars, clearFileCache) {
|
||||
if ((reload || clearFileCache) && clearFileCache !== false) {
|
||||
fileManager.clearFileCache();
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
var startTime, endTime, totalMilliseconds;
|
||||
startTime = endTime = new Date();
|
||||
|
||||
loadStyleSheets(function (e, css, _, sheet, webInfo) {
|
||||
if (e) {
|
||||
errors.add(e, e.href || sheet.href);
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
if (webInfo.local) {
|
||||
less.logger.info("loading " + sheet.href + " from cache.");
|
||||
} else {
|
||||
less.logger.info("rendered " + sheet.href + " successfully.");
|
||||
}
|
||||
browser.createCSS(window.document, css, sheet);
|
||||
cache.setCSS(sheet.href, webInfo.lastModified, css);
|
||||
}
|
||||
less.logger.info("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms');
|
||||
if (webInfo.remaining === 0) {
|
||||
totalMilliseconds = new Date() - startTime;
|
||||
less.logger.info("less has finished. css generated in " + totalMilliseconds + 'ms');
|
||||
resolve({
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
totalMilliseconds: totalMilliseconds,
|
||||
sheets: less.sheets.length
|
||||
});
|
||||
}
|
||||
endTime = new Date();
|
||||
}, reload, modifyVars);
|
||||
less.logger.info("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms');
|
||||
if (webInfo.remaining === 0) {
|
||||
totalMilliseconds = new Date() - startTime;
|
||||
less.logger.info("less has finished. css generated in " + totalMilliseconds + 'ms');
|
||||
resolve({
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
totalMilliseconds: totalMilliseconds,
|
||||
sheets: less.sheets.length
|
||||
});
|
||||
}
|
||||
endTime = new Date();
|
||||
}, reload, modifyVars);
|
||||
|
||||
loadStyles(modifyVars);
|
||||
});
|
||||
loadStyles(modifyVars);
|
||||
});
|
||||
};
|
||||
|
||||
less.refreshStyles = loadStyles;
|
||||
return less;
|
||||
};
|
||||
|
||||
less.refreshStyles = loadStyles;
|
||||
|
||||
less.pageLoadFinished = less.registerStylesheets().then(
|
||||
function () {
|
||||
return less.refresh(less.env === 'development');
|
||||
}
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ module.exports = function(less, options) {
|
||||
// 1 - Errors
|
||||
// 0 - None
|
||||
// Defaults to 2
|
||||
options.logLevel = typeof(options.logLevel) !== 'undefined' ? options.logLevel : (options.env === 'development' ? logLevel_info : logLevel_error);
|
||||
options.logLevel = typeof options.logLevel !== 'undefined' ? options.logLevel : (options.env === 'development' ? logLevel_info : logLevel_error);
|
||||
|
||||
if (!options.loggers) {
|
||||
options.loggers = [{
|
||||
@@ -37,7 +37,7 @@ module.exports = function(less, options) {
|
||||
}
|
||||
}];
|
||||
}
|
||||
for(var i = 0; i < options.loggers.length; i++) {
|
||||
for (var i = 0; i < options.loggers.length; i++) {
|
||||
less.logger.addListener(options.loggers[i]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
module.exports = {
|
||||
extractId: function(href) {
|
||||
return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' ) // Remove protocol & domain
|
||||
.replace(/^\//, '' ) // Remove root /
|
||||
.replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension
|
||||
.replace(/[^\.\w-]+/g, '-') // Replace illegal characters
|
||||
.replace(/\./g, ':'); // Replace dots with colons(for valid id)
|
||||
.replace(/[\?\&]livereload=\w+/, '' ) // Remove LiveReload cachebuster
|
||||
.replace(/^\//, '' ) // Remove root /
|
||||
.replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension
|
||||
.replace(/[^\.\w-]+/g, '-') // Replace illegal characters
|
||||
.replace(/\./g, ':'); // Replace dots with colons(for valid id)
|
||||
},
|
||||
addDataAttr: function(options, tag) {
|
||||
for (var opt in tag.dataset) {
|
||||
if (tag.dataset.hasOwnProperty(opt)) {
|
||||
if (opt === "env" || opt === "dumpLineNumbers" || opt === "rootpath" || opt === "errorReporting") {
|
||||
options[opt] = tag.dataset[opt];
|
||||
} else {
|
||||
try {
|
||||
options[opt] = JSON.parse(tag.dataset[opt]);
|
||||
}
|
||||
catch(_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
var path = require('path'),
|
||||
fs = require('./fs'),
|
||||
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
|
||||
PromiseConstructor,
|
||||
AbstractFileManager = require("../less/environment/abstract-file-manager.js");
|
||||
|
||||
try {
|
||||
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
|
||||
} catch(e) {
|
||||
}
|
||||
|
||||
var FileManager = function() {
|
||||
};
|
||||
|
||||
@@ -15,65 +20,89 @@ FileManager.prototype.supportsSync = function(filename, currentDirectory, option
|
||||
return true;
|
||||
};
|
||||
|
||||
FileManager.prototype.loadFile = function(filename, currentDirectory, options, environment) {
|
||||
return new PromiseConstructor(function(fullfill, reject) {
|
||||
var fullFilename,
|
||||
data;
|
||||
FileManager.prototype.loadFile = function(filename, currentDirectory, options, environment, callback) {
|
||||
var fullFilename,
|
||||
data,
|
||||
isAbsoluteFilename = this.isPathAbsolute(filename),
|
||||
filenamesTried = [];
|
||||
|
||||
options = options || {};
|
||||
options = options || {};
|
||||
|
||||
var paths = [currentDirectory];
|
||||
if (options.paths) paths.push.apply(paths, options.paths);
|
||||
if (paths.indexOf('.') === -1) paths.push('.');
|
||||
if (options.syncImport || !PromiseConstructor) {
|
||||
data = this.loadFileSync(filename, currentDirectory, options, environment, 'utf-8');
|
||||
callback(data.error, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.syncImport) {
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
try {
|
||||
fullFilename = path.join(paths[i], filename);
|
||||
fs.statSync(fullFilename);
|
||||
break;
|
||||
} catch (e) {
|
||||
fullFilename = null;
|
||||
var paths = isAbsoluteFilename ? [""] : [currentDirectory];
|
||||
if (options.paths) { paths.push.apply(paths, options.paths); }
|
||||
if (!isAbsoluteFilename && paths.indexOf('.') === -1) { paths.push('.'); }
|
||||
|
||||
// promise is guarenteed to be asyncronous
|
||||
// which helps as it allows the file handle
|
||||
// to be closed before it continues with the next file
|
||||
return new PromiseConstructor(function(fulfill, reject) {
|
||||
(function tryPathIndex(i) {
|
||||
if (i < paths.length) {
|
||||
fullFilename = filename;
|
||||
if (paths[i]) {
|
||||
fullFilename = path.join(paths[i], fullFilename);
|
||||
}
|
||||
fs.stat(fullFilename, function (err) {
|
||||
if (err) {
|
||||
filenamesTried.push(fullFilename);
|
||||
tryPathIndex(i + 1);
|
||||
} else {
|
||||
fs.readFile(fullFilename, 'utf-8', function(e, data) {
|
||||
if (e) { reject(e); return; }
|
||||
|
||||
fulfill({ contents: data, filename: fullFilename});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject({ type: 'File', message: "'" + filename + "' wasn't found. Tried - " + filenamesTried.join(",") });
|
||||
}
|
||||
|
||||
if (!fullFilename) {
|
||||
reject({ type: 'File', message: "'" + filename + "' wasn't found" });
|
||||
return;
|
||||
}
|
||||
|
||||
data = fs.readFileSync(fullFilename, 'utf-8');
|
||||
fullfill({ contents: data, filename: fullFilename});
|
||||
} else {
|
||||
(function tryPathIndex(i) {
|
||||
if (i < paths.length) {
|
||||
fullFilename = path.join(paths[i], filename);
|
||||
fs.stat(fullFilename, function (err) {
|
||||
if (err) {
|
||||
tryPathIndex(i + 1);
|
||||
} else {
|
||||
fs.readFile(fullFilename, 'utf-8', function(e, data) {
|
||||
if (e) { reject(e); return; }
|
||||
|
||||
// do processing in the next tick to allow
|
||||
// file handling to dispose
|
||||
process.nextTick(function() {
|
||||
fullfill({ contents: data, filename: fullFilename});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject({ type: 'File', message: "'" + filename + "' wasn't found" });
|
||||
}
|
||||
}(0));
|
||||
}
|
||||
}(0));
|
||||
});
|
||||
};
|
||||
|
||||
FileManager.prototype.loadFileSync = function(filename, currentDirectory, options, environment) {
|
||||
filename = path.join(currentDirectory, filename);
|
||||
return { contents: fs.readFileSync(filename), filename: filename };
|
||||
FileManager.prototype.loadFileSync = function(filename, currentDirectory, options, environment, encoding) {
|
||||
var fullFilename, paths, filenamesTried = [], isAbsoluteFilename = this.isPathAbsolute(filename) , data;
|
||||
options = options || {};
|
||||
|
||||
paths = isAbsoluteFilename ? [""] : [currentDirectory];
|
||||
if (options.paths) {
|
||||
paths.push.apply(paths, options.paths);
|
||||
}
|
||||
if (!isAbsoluteFilename && paths.indexOf('.') === -1) {
|
||||
paths.push('.');
|
||||
}
|
||||
|
||||
var err, result;
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
try {
|
||||
fullFilename = filename;
|
||||
if (paths[i]) {
|
||||
fullFilename = path.join(paths[i], fullFilename);
|
||||
}
|
||||
filenamesTried.push(fullFilename);
|
||||
fs.statSync(fullFilename);
|
||||
break;
|
||||
} catch (e) {
|
||||
fullFilename = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fullFilename) {
|
||||
err = { type: 'File', message: "'" + filename + "' wasn't found. Tried - " + filenamesTried.join(",") };
|
||||
result = { error: err };
|
||||
} else {
|
||||
data = fs.readFileSync(fullFilename, encoding);
|
||||
result = { contents: data, filename: fullFilename};
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = FileManager;
|
||||
|
||||
34
lib/less-node/image-size.js
Normal file
34
lib/less-node/image-size.js
Normal file
@@ -0,0 +1,34 @@
|
||||
var Dimension = require("../less/tree/dimension"),
|
||||
Expression = require("../less/tree/expression"),
|
||||
functionRegistry = require("./../less/functions/function-registry"),
|
||||
path = require("path");
|
||||
|
||||
function imageSize(filePathNode) {
|
||||
var filePath = filePathNode.value;
|
||||
var currentDirectory = filePathNode.currentFileInfo.relativeUrls ?
|
||||
filePathNode.currentFileInfo.currentDirectory : filePathNode.currentFileInfo.entryPath;
|
||||
|
||||
var sizeOf = require('image-size');
|
||||
filePath = path.join(currentDirectory, filePath);
|
||||
return sizeOf(filePath);
|
||||
}
|
||||
|
||||
var imageFunctions = {
|
||||
"image-size": function(filePathNode) {
|
||||
var size = imageSize(filePathNode);
|
||||
return new Expression([
|
||||
new Dimension(size.width, "px"),
|
||||
new Dimension(size.height, "px")
|
||||
]);
|
||||
},
|
||||
"image-width": function(filePathNode) {
|
||||
var size = imageSize(filePathNode);
|
||||
return new Dimension(size.width, "px");
|
||||
},
|
||||
"image-height": function(filePathNode) {
|
||||
var size = imageSize(filePathNode);
|
||||
return new Dimension(size.height, "px");
|
||||
}
|
||||
};
|
||||
|
||||
functionRegistry.addMultiple(imageFunctions);
|
||||
@@ -27,11 +27,11 @@ less.formatError = function(ctx, options) {
|
||||
return ctx.stack || ctx.message;
|
||||
}
|
||||
|
||||
if (typeof(extract[0]) === 'string') {
|
||||
if (typeof extract[0] === 'string') {
|
||||
error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
|
||||
}
|
||||
|
||||
if (typeof(extract[1]) === 'string') {
|
||||
if (typeof extract[1] === 'string') {
|
||||
var errorTxt = ctx.line + ' ';
|
||||
if (extract[1]) {
|
||||
errorTxt += extract[1].slice(0, ctx.column) +
|
||||
@@ -41,7 +41,7 @@ less.formatError = function(ctx, options) {
|
||||
error.push(errorTxt);
|
||||
}
|
||||
|
||||
if (typeof(extract[2]) === 'string') {
|
||||
if (typeof extract[2] === 'string') {
|
||||
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
|
||||
}
|
||||
error = error.join('\n') + stylize('', 'reset') + '\n';
|
||||
@@ -68,4 +68,7 @@ less.writeError = function (ctx, options) {
|
||||
console.error(less.formatError(ctx, options));
|
||||
};
|
||||
|
||||
// provide image-size functionality
|
||||
require('./image-size');
|
||||
|
||||
module.exports = less;
|
||||
|
||||
@@ -26,31 +26,31 @@ var lessc_helper = {
|
||||
console.log("If source is set to `-' (dash or hyphen-minus), input is read from stdin.");
|
||||
console.log("");
|
||||
console.log("options:");
|
||||
console.log(" -h, --help Print help (this message) and exit.");
|
||||
console.log(" --include-path=PATHS Set include paths. Separated by `:'. Use `;' on Windows.");
|
||||
console.log(" -M, --depends Output a makefile import dependency list to stdout");
|
||||
console.log(" --no-color Disable colorized output.");
|
||||
console.log(" --no-ie-compat Disable IE compatibility checks.");
|
||||
console.log(" --no-js Disable JavaScript in less files");
|
||||
console.log(" -h, --help Prints help (this message) and exit.");
|
||||
console.log(" --include-path=PATHS Sets include paths. Separated by `:'. Use `;' on Windows.");
|
||||
console.log(" -M, --depends Outputs a makefile import dependency list to stdout.");
|
||||
console.log(" --no-color Disables colorized output.");
|
||||
console.log(" --no-ie-compat Disables IE compatibility checks.");
|
||||
console.log(" --no-js Disables JavaScript in less files");
|
||||
console.log(" -l, --lint Syntax check only (lint).");
|
||||
console.log(" -s, --silent Suppress output of error messages.");
|
||||
console.log(" --strict-imports Force evaluation of imports.");
|
||||
console.log(" --insecure Allow imports from insecure https hosts.");
|
||||
console.log(" -v, --version Print version number and exit.");
|
||||
console.log(" -x, --compress Compress output by removing some whitespaces.");
|
||||
console.log(" --source-map[=FILENAME] Outputs a v3 sourcemap to the filename (or output filename.map)");
|
||||
console.log(" --source-map-rootpath=X adds this path onto the sourcemap filename and less file paths");
|
||||
console.log(" -s, --silent Suppresses output of error messages.");
|
||||
console.log(" --strict-imports Forces evaluation of imports.");
|
||||
console.log(" --insecure Allows imports from insecure https hosts.");
|
||||
console.log(" -v, --version Prints version number and exit.");
|
||||
console.log(" --source-map[=FILENAME] Outputs a v3 sourcemap to the filename (or output filename.map).");
|
||||
console.log(" --source-map-rootpath=X Adds this path onto the sourcemap filename and less file paths.");
|
||||
console.log(" --source-map-basepath=X Sets sourcemap base path, defaults to current working directory.");
|
||||
console.log(" --source-map-less-inline puts the less files into the map instead of referencing them");
|
||||
console.log(" --source-map-map-inline puts the map (and any less files) into the output css file");
|
||||
console.log(" --source-map-url=URL the complete url and filename put in the less file");
|
||||
console.log(" -rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls.");
|
||||
console.log(" --source-map-less-inline Puts the less files into the map instead of referencing them.");
|
||||
console.log(" --source-map-map-inline Puts the map (and any less files) as a base64 data uri into the output css file.");
|
||||
console.log(" --source-map-url=URL Sets a custom URL to map file, for sourceMappingURL comment");
|
||||
console.log(" in generated CSS file.");
|
||||
console.log(" -rp, --rootpath=URL Sets rootpath for url rewriting in relative imports and urls");
|
||||
console.log(" Works with or without the relative-urls option.");
|
||||
console.log(" -ru, --relative-urls re-write relative urls to the base less file.");
|
||||
console.log(" -sm=on|off Turn on or off strict math, where in strict mode, math");
|
||||
console.log(" --strict-math=on|off requires brackets. This option may default to on and then");
|
||||
console.log(" -ru, --relative-urls Re-writes relative urls to the base less file.");
|
||||
console.log(" -sm=on|off Turns on or off strict math, where in strict mode, math.");
|
||||
console.log(" --strict-math=on|off Requires brackets. This option may default to on and then");
|
||||
console.log(" be removed in the future.");
|
||||
console.log(" -su=on|off Allow mixed units, e.g. 1px+1em or 1px*1px which have units");
|
||||
console.log(" -su=on|off Allows mixed units, e.g. 1px+1em or 1px*1px which have units");
|
||||
console.log(" --strict-units=on|off that cannot be represented.");
|
||||
console.log(" --global-var='VAR=VALUE' Defines a variable that can be referenced by the file.");
|
||||
console.log(" --modify-var='VAR=VALUE' Modifies a variable already declared in the file.");
|
||||
@@ -70,6 +70,8 @@ var lessc_helper = {
|
||||
console.log(" media query which is compatible with the SASS");
|
||||
console.log(" format, and 'all' which will do both.");
|
||||
console.log(" --verbose Be verbose.");
|
||||
console.log(" -x, --compress Compresses output by removing some whitespaces.");
|
||||
console.log(" We recommend you use a dedicated minifer like less-plugin-clean-css");
|
||||
console.log("");
|
||||
console.log("Report bugs to: http://github.com/less/less.js/issues");
|
||||
console.log("Home page: <http://lesscss.org/>");
|
||||
|
||||
@@ -8,6 +8,11 @@ var PluginLoader = function(less) {
|
||||
PluginLoader.prototype.tryLoadPlugin = function(name, argument) {
|
||||
var plugin = this.tryRequirePlugin(name);
|
||||
if (plugin) {
|
||||
// support plugins being a function
|
||||
// so that the plugin can be more usable programmatically
|
||||
if (typeof plugin === "function") {
|
||||
plugin = new plugin();
|
||||
}
|
||||
if (plugin.minVersion) {
|
||||
if (this.compareVersion(plugin.minVersion, this.less.version) < 0) {
|
||||
console.log("plugin " + name + " requires version " + this.versionToString(plugin.minVersion));
|
||||
@@ -33,7 +38,7 @@ PluginLoader.prototype.tryLoadPlugin = function(name, argument) {
|
||||
return null;
|
||||
};
|
||||
PluginLoader.prototype.compareVersion = function(aVersion, bVersion) {
|
||||
for(var i = 0; i < aVersion.length; i++) {
|
||||
for (var i = 0; i < aVersion.length; i++) {
|
||||
if (aVersion[i] !== bVersion[i]) {
|
||||
return parseInt(aVersion[i]) > parseInt(bVersion[i]) ? -1 : 1;
|
||||
}
|
||||
@@ -42,7 +47,7 @@ PluginLoader.prototype.compareVersion = function(aVersion, bVersion) {
|
||||
};
|
||||
PluginLoader.prototype.versionToString = function(version) {
|
||||
var versionString = "";
|
||||
for(var i = 0; i < version.length; i++) {
|
||||
for (var i = 0; i < version.length; i++) {
|
||||
versionString += (versionString ? "." : "") + version[i];
|
||||
}
|
||||
return versionString;
|
||||
@@ -76,7 +81,7 @@ PluginLoader.prototype.tryRequirePlugin = function(name) {
|
||||
}
|
||||
};
|
||||
PluginLoader.prototype.printUsage = function(plugins) {
|
||||
for(var i = 0; i < plugins.length; i++) {
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
var plugin = plugins[i];
|
||||
if (plugin.printUsage) {
|
||||
plugin.printUsage();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var isUrlRe = /^(?:https?:)?\/\//i,
|
||||
url = require('url'),
|
||||
request,
|
||||
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
|
||||
PromiseConstructor,
|
||||
AbstractFileManager = require("../less/environment/abstract-file-manager.js"),
|
||||
logger = require("../less/logger");
|
||||
|
||||
@@ -15,7 +15,10 @@ UrlFileManager.prototype.supports = function(filename, currentDirectory, options
|
||||
};
|
||||
|
||||
UrlFileManager.prototype.loadFile = function(filename, currentDirectory, options, environment) {
|
||||
return new PromiseConstructor(function(fullfill, reject) {
|
||||
if (!PromiseConstructor) {
|
||||
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
|
||||
}
|
||||
return new PromiseConstructor(function(fulfill, reject) {
|
||||
if (request === undefined) {
|
||||
try { request = require('request'); }
|
||||
catch(e) { request = null; }
|
||||
@@ -35,7 +38,7 @@ UrlFileManager.prototype.loadFile = function(filename, currentDirectory, options
|
||||
|
||||
request.get({uri: urlStr, strictSSL: !options.insecure }, function (error, res, body) {
|
||||
if (error) {
|
||||
reject({ type: 'File', message: "resource '" + urlStr + "' gave this Error:\n "+ error +"\n" });
|
||||
reject({ type: 'File', message: "resource '" + urlStr + "' gave this Error:\n " + error + "\n" });
|
||||
return;
|
||||
}
|
||||
if (res && res.statusCode === 404) {
|
||||
@@ -43,9 +46,9 @@ UrlFileManager.prototype.loadFile = function(filename, currentDirectory, options
|
||||
return;
|
||||
}
|
||||
if (!body) {
|
||||
logger.warn('Warning: Empty body (HTTP '+ res.statusCode + ') returned by "' + urlStr +'"');
|
||||
logger.warn('Warning: Empty body (HTTP ' + res.statusCode + ') returned by "' + urlStr + '"');
|
||||
}
|
||||
fullfill({ contents: body, filename: urlStr });
|
||||
fulfill({ contents: body, filename: urlStr });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -18,11 +18,11 @@ function formatError(ctx, options) {
|
||||
return ctx.stack || ctx.message;
|
||||
}
|
||||
|
||||
if (typeof(extract[0]) === 'string') {
|
||||
if (typeof extract[0] === 'string') {
|
||||
error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey'));
|
||||
}
|
||||
|
||||
if (typeof(extract[1]) === 'string') {
|
||||
if (typeof extract[1] === 'string') {
|
||||
var errorTxt = ctx.line + ' ';
|
||||
if (extract[1]) {
|
||||
errorTxt += extract[1].slice(0, ctx.column) +
|
||||
@@ -32,14 +32,14 @@ function formatError(ctx, options) {
|
||||
error.push(errorTxt);
|
||||
}
|
||||
|
||||
if (typeof(extract[2]) === 'string') {
|
||||
if (typeof extract[2] === 'string') {
|
||||
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
|
||||
}
|
||||
error = error.join('\n') + stylize('', 'reset') + '\n';
|
||||
|
||||
message += stylize(ctx.type + 'Error: ' + ctx.message, 'red');
|
||||
if (ctx.filename) {
|
||||
message += stylize(' in ', 'red') + ctx.filename +
|
||||
message += stylize(' in ', 'red') + ctx.filename +
|
||||
stylize(' on line ' + ctx.line + ', column ' + (ctx.column + 1) + ':', 'grey');
|
||||
}
|
||||
|
||||
@@ -113,8 +113,8 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) {
|
||||
}
|
||||
|
||||
var j = file.lastIndexOf('/');
|
||||
if(newFileInfo.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) {
|
||||
var relativeSubDirectory = file.slice(0, j+1);
|
||||
if (newFileInfo.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) {
|
||||
var relativeSubDirectory = file.slice(0, j + 1);
|
||||
newFileInfo.rootpath = newFileInfo.rootpath + relativeSubDirectory; // append (sub|sup) directory path of imported file
|
||||
}
|
||||
newFileInfo.currentDirectory = path;
|
||||
@@ -135,7 +135,6 @@ less.Parser.fileLoader = function (file, currentFileInfo, callback, env) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function writeFile(filename, content) {
|
||||
var fstream = new java.io.FileWriter(filename);
|
||||
var out = new java.io.BufferedWriter(fstream);
|
||||
@@ -178,7 +177,7 @@ function writeFile(filename, content) {
|
||||
var checkBooleanArg = function(arg) {
|
||||
var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg);
|
||||
if (!onOff) {
|
||||
print(" unable to parse "+arg+" as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
|
||||
print(" unable to parse " + arg + " as a boolean. use one of on/t/true/y/yes/off/f/false/n/no");
|
||||
continueProcessing = false;
|
||||
return false;
|
||||
}
|
||||
@@ -197,8 +196,12 @@ function writeFile(filename, content) {
|
||||
}
|
||||
|
||||
match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i);
|
||||
if (match) { arg = match[1]; } // was (?:=([^\s]*)), check!
|
||||
else { return arg; }
|
||||
if (match) {
|
||||
arg = match[1];
|
||||
}
|
||||
else {
|
||||
return arg;
|
||||
}
|
||||
|
||||
switch (arg) {
|
||||
case 'v':
|
||||
@@ -306,10 +309,10 @@ function writeFile(filename, content) {
|
||||
case 'source-map-output-map-file':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
options.writeSourceMap = function(sourceMapContent) {
|
||||
writeFile(match[2], sourceMapContent);
|
||||
writeFile(match[2], sourceMapContent);
|
||||
};
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case 'rp':
|
||||
case 'rootpath':
|
||||
if (checkArgFunc(arg, match[2])) {
|
||||
@@ -371,7 +374,6 @@ function writeFile(filename, content) {
|
||||
options.sourceMapOutputFilename = options.sourceMap;
|
||||
}
|
||||
|
||||
|
||||
if (!name) {
|
||||
console.log("lessc: no inout files");
|
||||
console.log("");
|
||||
|
||||
@@ -4,7 +4,7 @@ module.exports = contexts;
|
||||
var copyFromOriginal = function copyFromOriginal(original, destination, propertiesToCopy) {
|
||||
if (!original) { return; }
|
||||
|
||||
for(var i = 0; i < propertiesToCopy.length; i++) {
|
||||
for (var i = 0; i < propertiesToCopy.length; i++) {
|
||||
if (original.hasOwnProperty(propertiesToCopy[i])) {
|
||||
destination[propertiesToCopy[i]] = original[propertiesToCopy[i]];
|
||||
}
|
||||
@@ -41,6 +41,7 @@ contexts.Parse = function(options) {
|
||||
};
|
||||
|
||||
var evalCopyProperties = [
|
||||
'paths', // additional include paths
|
||||
'compress', // whether to compress
|
||||
'ieCompat', // whether to enforce IE compatibility (IE8 data-uri)
|
||||
'strictMath', // whether math has to be within parenthesis
|
||||
@@ -49,13 +50,15 @@ var evalCopyProperties = [
|
||||
'importMultiple', // whether we are currently importing multiple copies
|
||||
'urlArgs', // whether to add args into url tokens
|
||||
'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true
|
||||
'pluginManager' // Used as the plugin manager for the session
|
||||
'pluginManager', // Used as the plugin manager for the session
|
||||
'importantScope' // used to bubble up !important statements
|
||||
];
|
||||
|
||||
contexts.Eval = function(options, frames) {
|
||||
copyFromOriginal(options, this, evalCopyProperties);
|
||||
|
||||
this.frames = frames || [];
|
||||
this.importantScope = this.importantScope || [];
|
||||
};
|
||||
|
||||
contexts.Eval.prototype.inParenthesis = function () {
|
||||
@@ -74,7 +77,7 @@ contexts.Eval.prototype.isMathOn = function () {
|
||||
};
|
||||
|
||||
contexts.Eval.prototype.isPathRelative = function (path) {
|
||||
return !/^(?:[a-z-]+:|\/)/.test(path);
|
||||
return !/^(?:[a-z-]+:|\/)/i.test(path);
|
||||
};
|
||||
|
||||
contexts.Eval.prototype.normalizePath = function( path ) {
|
||||
@@ -105,4 +108,3 @@ contexts.Eval.prototype.normalizePath = function( path ) {
|
||||
};
|
||||
|
||||
//todo - do the same for the toCSS ?
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ module.exports = {
|
||||
'plum':'#dda0dd',
|
||||
'powderblue':'#b0e0e6',
|
||||
'purple':'#800080',
|
||||
'rebeccapurple':'#663399',
|
||||
'red':'#ff0000',
|
||||
'rosybrown':'#bc8f8f',
|
||||
'royalblue':'#4169e1',
|
||||
|
||||
@@ -13,9 +13,9 @@ module.exports = {
|
||||
'ms': 0.001
|
||||
},
|
||||
angle: {
|
||||
'rad': 1/(2*Math.PI),
|
||||
'deg': 1/360,
|
||||
'grad': 1/400,
|
||||
'rad': 1 / (2 * Math.PI),
|
||||
'deg': 1 / 360,
|
||||
'grad': 1 / 400,
|
||||
'turn': 1
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,11 @@ var abstractFileManager = function() {
|
||||
};
|
||||
|
||||
abstractFileManager.prototype.getPath = function (filename) {
|
||||
var j = filename.lastIndexOf('/');
|
||||
var j = filename.lastIndexOf('?');
|
||||
if (j > 0) {
|
||||
filename = filename.slice(0, j);
|
||||
}
|
||||
j = filename.lastIndexOf('/');
|
||||
if (j < 0) {
|
||||
j = filename.lastIndexOf('\\');
|
||||
}
|
||||
@@ -44,15 +48,15 @@ abstractFileManager.prototype.pathDiff = function pathDiff(url, baseUrl) {
|
||||
return "";
|
||||
}
|
||||
max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
|
||||
for(i = 0; i < max; i++) {
|
||||
for (i = 0; i < max; i++) {
|
||||
if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
|
||||
}
|
||||
baseUrlDirectories = baseUrlParts.directories.slice(i);
|
||||
urlDirectories = urlParts.directories.slice(i);
|
||||
for(i = 0; i < baseUrlDirectories.length-1; i++) {
|
||||
for (i = 0; i < baseUrlDirectories.length - 1; i++) {
|
||||
diff += "../";
|
||||
}
|
||||
for(i = 0; i < urlDirectories.length-1; i++) {
|
||||
for (i = 0; i < urlDirectories.length - 1; i++) {
|
||||
diff += urlDirectories[i] + "/";
|
||||
}
|
||||
return diff;
|
||||
@@ -77,7 +81,7 @@ abstractFileManager.prototype.extractUrlParts = function extractUrlParts(url, ba
|
||||
if (baseUrl && (!urlParts[1] || urlParts[2])) {
|
||||
baseUrlParts = baseUrl.match(urlPartsRegex);
|
||||
if (!baseUrlParts) {
|
||||
throw new Error("Could not parse page url - '"+baseUrl+"'");
|
||||
throw new Error("Could not parse page url - '" + baseUrl + "'");
|
||||
}
|
||||
urlParts[1] = urlParts[1] || baseUrlParts[1] || "";
|
||||
if (!urlParts[2]) {
|
||||
@@ -89,16 +93,16 @@ abstractFileManager.prototype.extractUrlParts = function extractUrlParts(url, ba
|
||||
directories = urlParts[3].replace(/\\/g, "/").split("/");
|
||||
|
||||
// extract out . before .. so .. doesn't absorb a non-directory
|
||||
for(i = 0; i < directories.length; i++) {
|
||||
for (i = 0; i < directories.length; i++) {
|
||||
if (directories[i] === ".") {
|
||||
directories.splice(i, 1);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0; i < directories.length; i++) {
|
||||
for (i = 0; i < directories.length; i++) {
|
||||
if (directories[i] === ".." && i > 0) {
|
||||
directories.splice(i-1, 2);
|
||||
directories.splice(i - 1, 2);
|
||||
i -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
var logger = require("../logger");
|
||||
var environment = function(externalEnvironment, fileManagers) {
|
||||
this.fileManagers = fileManagers || [];
|
||||
externalEnvironment = externalEnvironment || {};
|
||||
@@ -6,7 +7,7 @@ var environment = function(externalEnvironment, fileManagers) {
|
||||
requiredFunctions = [],
|
||||
functions = requiredFunctions.concat(optionalFunctions);
|
||||
|
||||
for(var i = 0; i < functions.length; i++) {
|
||||
for (var i = 0; i < functions.length; i++) {
|
||||
var propName = functions[i],
|
||||
environmentFunc = externalEnvironment[propName];
|
||||
if (environmentFunc) {
|
||||
@@ -18,11 +19,19 @@ var environment = function(externalEnvironment, fileManagers) {
|
||||
};
|
||||
|
||||
environment.prototype.getFileManager = function (filename, currentDirectory, options, environment, isSync) {
|
||||
|
||||
if (!filename) {
|
||||
logger.warn("getFileManager called with no filename.. Please report this issue. continuing.");
|
||||
}
|
||||
if (currentDirectory == null) {
|
||||
logger.warn("getFileManager called with null directory.. Please report this issue. continuing.");
|
||||
}
|
||||
|
||||
var fileManagers = this.fileManagers;
|
||||
if (options.pluginManager) {
|
||||
fileManagers = [].concat(fileManagers).concat(options.pluginManager.getFileManagers());
|
||||
}
|
||||
for(var i = fileManagers.length - 1; i >= 0 ; i--) {
|
||||
for (var i = fileManagers.length - 1; i >= 0 ; i--) {
|
||||
var fileManager = fileManagers[i];
|
||||
if (fileManager[isSync ? "supportsSync" : "supports"](filename, currentDirectory, options, environment)) {
|
||||
return fileManager;
|
||||
|
||||
@@ -73,7 +73,7 @@ module.exports = {
|
||||
supports: function(filename, currentDirectory, options, environment) {
|
||||
},
|
||||
/**
|
||||
* Loads a file asynchronously. Expects a promise that either rejects with an error or fullfills with an
|
||||
* Loads a file asynchronously. Expects a promise that either rejects with an error or fulfills with an
|
||||
* object containing
|
||||
* { filename: - full resolved path to file
|
||||
* contents: - the contents of the file, as a string }
|
||||
|
||||
@@ -33,9 +33,9 @@ var colorBlendModeFunctions = {
|
||||
},
|
||||
overlay: function(cb, cs) {
|
||||
cb *= 2;
|
||||
return (cb <= 1)
|
||||
? colorBlendModeFunctions.multiply(cb, cs)
|
||||
: colorBlendModeFunctions.screen(cb - 1, cs);
|
||||
return (cb <= 1) ?
|
||||
colorBlendModeFunctions.multiply(cb, cs) :
|
||||
colorBlendModeFunctions.screen(cb - 1, cs);
|
||||
},
|
||||
softlight: function(cb, cs) {
|
||||
var d = 1, e = cb;
|
||||
|
||||
@@ -14,7 +14,7 @@ function hsla(color) {
|
||||
function number(n) {
|
||||
if (n instanceof Dimension) {
|
||||
return parseFloat(n.unit.is('%') ? n.value / 100 : n.value);
|
||||
} else if (typeof(n) === 'number') {
|
||||
} else if (typeof n === 'number') {
|
||||
return n;
|
||||
} else {
|
||||
throw {
|
||||
@@ -45,10 +45,18 @@ colorFunctions = {
|
||||
hsla: function (h, s, l, a) {
|
||||
function hue(h) {
|
||||
h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
|
||||
if (h * 6 < 1) { return m1 + (m2 - m1) * h * 6; }
|
||||
else if (h * 2 < 1) { return m2; }
|
||||
else if (h * 3 < 2) { return m1 + (m2 - m1) * (2/3 - h) * 6; }
|
||||
else { return m1; }
|
||||
if (h * 6 < 1) {
|
||||
return m1 + (m2 - m1) * h * 6;
|
||||
}
|
||||
else if (h * 2 < 1) {
|
||||
return m2;
|
||||
}
|
||||
else if (h * 3 < 2) {
|
||||
return m1 + (m2 - m1) * (2 / 3 - h) * 6;
|
||||
}
|
||||
else {
|
||||
return m1;
|
||||
}
|
||||
}
|
||||
|
||||
h = (number(h) % 360) / 360;
|
||||
@@ -57,9 +65,9 @@ colorFunctions = {
|
||||
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
|
||||
var m1 = l * 2 - m2;
|
||||
|
||||
return colorFunctions.rgba(hue(h + 1/3) * 255,
|
||||
return colorFunctions.rgba(hue(h + 1 / 3) * 255,
|
||||
hue(h) * 255,
|
||||
hue(h - 1/3) * 255,
|
||||
hue(h - 1 / 3) * 255,
|
||||
a);
|
||||
},
|
||||
|
||||
@@ -127,9 +135,9 @@ colorFunctions = {
|
||||
},
|
||||
luminance: function (color) {
|
||||
var luminance =
|
||||
(0.2126 * color.rgb[0] / 255)
|
||||
+ (0.7152 * color.rgb[1] / 255)
|
||||
+ (0.0722 * color.rgb[2] / 255);
|
||||
(0.2126 * color.rgb[0] / 255) +
|
||||
(0.7152 * color.rgb[1] / 255) +
|
||||
(0.0722 * color.rgb[2] / 255);
|
||||
|
||||
return new Dimension(luminance * color.alpha * 100, '%');
|
||||
},
|
||||
@@ -259,7 +267,7 @@ colorFunctions = {
|
||||
return new Color(c.value.slice(1));
|
||||
}
|
||||
if ((c instanceof Color) || (c = Color.fromKeyword(c.value))) {
|
||||
c.keyword = undefined;
|
||||
c.value = undefined;
|
||||
return c;
|
||||
}
|
||||
throw {
|
||||
@@ -268,7 +276,7 @@ colorFunctions = {
|
||||
};
|
||||
},
|
||||
tint: function(color, amount) {
|
||||
return colorFunctions.mix(colorFunctions.rgb(255,255,255), color, amount);
|
||||
return colorFunctions.mix(colorFunctions.rgb(255, 255, 255), color, amount);
|
||||
},
|
||||
shade: function(color, amount) {
|
||||
return colorFunctions.mix(colorFunctions.rgb(0, 0, 0), color, amount);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = function(environment) {
|
||||
var Anonymous = require("../tree/anonymous"),
|
||||
var Quoted = require("../tree/quoted"),
|
||||
URL = require("../tree/url"),
|
||||
functionRegistry = require("./function-registry"),
|
||||
fallback = function(functionThis, node) {
|
||||
@@ -9,39 +9,44 @@ module.exports = function(environment) {
|
||||
|
||||
functionRegistry.add("data-uri", function(mimetypeNode, filePathNode) {
|
||||
|
||||
var mimetype = mimetypeNode.value;
|
||||
var filePath = (filePathNode && filePathNode.value);
|
||||
|
||||
var fileManager = environment.getFileManager(filePath, this.context.currentFileInfo, this.context, environment, true);
|
||||
|
||||
if (!fileManager) {
|
||||
return fallback(this, filePathNode || mimetypeNode);
|
||||
if (!filePathNode) {
|
||||
filePathNode = mimetypeNode;
|
||||
mimetypeNode = null;
|
||||
}
|
||||
|
||||
var useBase64 = false;
|
||||
|
||||
if (arguments.length < 2) {
|
||||
filePath = mimetype;
|
||||
}
|
||||
var mimetype = mimetypeNode && mimetypeNode.value;
|
||||
var filePath = filePathNode.value;
|
||||
var currentFileInfo = this.currentFileInfo;
|
||||
var currentDirectory = currentFileInfo.relativeUrls ?
|
||||
currentFileInfo.currentDirectory : currentFileInfo.entryPath;
|
||||
|
||||
var fragmentStart = filePath.indexOf('#');
|
||||
var fragment = '';
|
||||
if (fragmentStart!==-1) {
|
||||
if (fragmentStart !== -1) {
|
||||
fragment = filePath.slice(fragmentStart);
|
||||
filePath = filePath.slice(0, fragmentStart);
|
||||
}
|
||||
|
||||
var currentDirectory = this.currentFileInfo.relativeUrls ?
|
||||
this.currentFileInfo.currentDirectory : this.currentFileInfo.entryPath;
|
||||
var fileManager = environment.getFileManager(filePath, currentDirectory, this.context, environment, true);
|
||||
|
||||
if (!fileManager) {
|
||||
return fallback(this, filePathNode);
|
||||
}
|
||||
|
||||
var useBase64 = false;
|
||||
|
||||
// detect the mimetype if not given
|
||||
if (arguments.length < 2) {
|
||||
if (!mimetypeNode) {
|
||||
|
||||
mimetype = environment.mimeLookup(filePath);
|
||||
|
||||
// use base 64 unless it's an ASCII or UTF-8 format
|
||||
var charset = environment.charsetLookup(mimetype);
|
||||
useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
|
||||
if (mimetype === "image/svg+xml") {
|
||||
useBase64 = false;
|
||||
} else {
|
||||
// use base 64 unless it's an ASCII or UTF-8 format
|
||||
var charset = environment.charsetLookup(mimetype);
|
||||
useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
|
||||
}
|
||||
if (useBase64) { mimetype += ';base64'; }
|
||||
}
|
||||
else {
|
||||
@@ -50,28 +55,31 @@ module.exports = function(environment) {
|
||||
|
||||
var fileSync = fileManager.loadFileSync(filePath, currentDirectory, this.context, environment);
|
||||
if (!fileSync.contents) {
|
||||
logger.warn("Skipped data-uri embedding because file not found");
|
||||
logger.warn("Skipped data-uri embedding of " + filePath + " because file not found");
|
||||
return fallback(this, filePathNode || mimetypeNode);
|
||||
}
|
||||
var buf = fileSync.contents;
|
||||
if (useBase64 && !environment.encodeBase64) {
|
||||
return fallback(this, filePathNode);
|
||||
}
|
||||
|
||||
// IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
|
||||
buf = useBase64 ? environment.encodeBase64(buf) : encodeURIComponent(buf);
|
||||
|
||||
var uri = "data:" + mimetype + ',' + buf + fragment;
|
||||
|
||||
// IE8 cannot handle a data-uri larger than 32,768 characters. If this is exceeded
|
||||
// and the --ieCompat flag is enabled, return a normal url() instead.
|
||||
var DATA_URI_MAX_KB = 32,
|
||||
fileSizeInKB = parseInt((buf.length / 1024), 10);
|
||||
if (fileSizeInKB >= DATA_URI_MAX_KB) {
|
||||
var DATA_URI_MAX = 32768;
|
||||
if (uri.length >= DATA_URI_MAX) {
|
||||
|
||||
if (this.context.ieCompat !== false) {
|
||||
logger.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB);
|
||||
logger.warn("Skipped data-uri embedding of " + filePath + " because its size (" + uri.length +
|
||||
" characters) exceeds IE8-safe " + DATA_URI_MAX + " characters!");
|
||||
|
||||
return fallback(this, filePathNode || mimetypeNode);
|
||||
}
|
||||
}
|
||||
|
||||
buf = useBase64 ? buf.toString('base64')
|
||||
: encodeURIComponent(buf);
|
||||
|
||||
var uri = "\"data:" + mimetype + ',' + buf + fragment + "\"";
|
||||
return new URL(new Anonymous(uri), this.index, this.currentFileInfo);
|
||||
return new URL(new Quoted('"' + uri + '"', uri, false, this.index, this.currentFileInfo), this.index, this.currentFileInfo);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,16 +2,16 @@ var functionRegistry = require("./function-registry");
|
||||
|
||||
var functionCaller = function(name, context, index, currentFileInfo) {
|
||||
this.name = name.toLowerCase();
|
||||
this.function = functionRegistry.get(this.name);
|
||||
this.func = functionRegistry.get(this.name);
|
||||
this.index = index;
|
||||
this.context = context;
|
||||
this.currentFileInfo = currentFileInfo;
|
||||
};
|
||||
functionCaller.prototype.isValid = function() {
|
||||
return Boolean(this.function);
|
||||
return Boolean(this.func);
|
||||
};
|
||||
functionCaller.prototype.call = function(args) {
|
||||
return this.function.apply(this, args);
|
||||
return this.func.apply(this, args);
|
||||
};
|
||||
|
||||
module.exports = functionCaller;
|
||||
|
||||
@@ -34,7 +34,7 @@ for (var f in mathFunctions) {
|
||||
}
|
||||
|
||||
mathFunctions.round = function (n, f) {
|
||||
var fraction = typeof(f) === "undefined" ? 0 : f.value;
|
||||
var fraction = typeof f === "undefined" ? 0 : f.value;
|
||||
return _math(function(num) { return num.toFixed(fraction); }, null, n);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ var minMax = function (isMin, args) {
|
||||
for (i = 0; i < args.length; i++) {
|
||||
current = args[i];
|
||||
if (!(current instanceof Dimension)) {
|
||||
if(Array.isArray(args[i].value)) {
|
||||
if (Array.isArray(args[i].value)) {
|
||||
Array.prototype.push.apply(args, Array.prototype.slice.call(args[i].value));
|
||||
}
|
||||
continue;
|
||||
@@ -25,7 +25,7 @@ var minMax = function (isMin, args) {
|
||||
unitClone = unit !== "" && unitClone === undefined ? current.unit.toString() : unitClone;
|
||||
j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit];
|
||||
if (j === undefined) {
|
||||
if(unitStatic !== undefined && unit !== unitStatic) {
|
||||
if (unitStatic !== undefined && unit !== unitStatic) {
|
||||
throw{ type: "Argument", message: "incompatible types" };
|
||||
}
|
||||
values[unit] = order.length;
|
||||
|
||||
@@ -8,7 +8,9 @@ functionRegistry.addMultiple({
|
||||
return new Anonymous(str instanceof JavaScript ? str.evaluated : str.value);
|
||||
},
|
||||
escape: function (str) {
|
||||
return new Anonymous(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
|
||||
return new Anonymous(
|
||||
encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B")
|
||||
.replace(/\(/g, "%28").replace(/\)/g, "%29"));
|
||||
},
|
||||
replace: function (string, pattern, replacement, flags) {
|
||||
var result = string.value;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
module.exports = function(environment) {
|
||||
var Dimension = require("../tree/dimension"),
|
||||
Color = require("../tree/color"),
|
||||
Anonymous = require("../tree/anonymous"),
|
||||
Expression = require("../tree/expression"),
|
||||
Quoted = require("../tree/quoted"),
|
||||
URL = require("../tree/url"),
|
||||
functionRegistry = require("./function-registry");
|
||||
|
||||
functionRegistry.add("svg-gradient", function(direction) {
|
||||
|
||||
function throwArgumentDescriptor() {
|
||||
throw { type: "Argument", message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" };
|
||||
throw { type: "Argument",
|
||||
message: "svg-gradient expects direction, start_color [start_position], [color position,]...," +
|
||||
" end_color [end_position]" };
|
||||
}
|
||||
|
||||
if (arguments.length < 3) {
|
||||
@@ -18,7 +21,6 @@ module.exports = function(environment) {
|
||||
gradientDirectionSvg,
|
||||
gradientType = "linear",
|
||||
rectangleDimension = 'x="0" y="0" width="1" height="1"',
|
||||
useBase64 = true,
|
||||
renderEnv = {compress: false},
|
||||
returner,
|
||||
directionValue = direction.toCSS(renderEnv),
|
||||
@@ -44,14 +46,15 @@ module.exports = function(environment) {
|
||||
rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
|
||||
break;
|
||||
default:
|
||||
throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" };
|
||||
throw { type: "Argument", message: "svg-gradient direction must be 'to bottom', 'to right'," +
|
||||
" 'to bottom right', 'to top right' or 'ellipse at center'" };
|
||||
}
|
||||
returner = '<?xml version="1.0" ?>' +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' +
|
||||
'<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>';
|
||||
|
||||
for (i = 0; i < stops.length; i+= 1) {
|
||||
if (stops[i].value) {
|
||||
if (stops[i] instanceof Expression) {
|
||||
color = stops[i].value[0];
|
||||
position = stops[i].value[1];
|
||||
} else {
|
||||
@@ -59,7 +62,7 @@ module.exports = function(environment) {
|
||||
position = undefined;
|
||||
}
|
||||
|
||||
if (!(color instanceof Color) || (!((i === 0 || i+1 === stops.length) && position === undefined) && !(position instanceof Dimension))) {
|
||||
if (!(color instanceof Color) || (!((i === 0 || i + 1 === stops.length) && position === undefined) && !(position instanceof Dimension))) {
|
||||
throwArgumentDescriptor();
|
||||
}
|
||||
positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%";
|
||||
@@ -69,15 +72,9 @@ module.exports = function(environment) {
|
||||
returner += '</' + gradientType + 'Gradient>' +
|
||||
'<rect ' + rectangleDimension + ' fill="url(#gradient)" /></svg>';
|
||||
|
||||
if (useBase64) {
|
||||
try {
|
||||
returner = environment.encodeBase64(returner);
|
||||
} catch(e) {
|
||||
useBase64 = false;
|
||||
}
|
||||
}
|
||||
returner = encodeURIComponent(returner);
|
||||
|
||||
returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'";
|
||||
return new URL(new Anonymous(returner), this.index, this.currentFileInfo);
|
||||
returner = "data:image/svg+xml," + returner;
|
||||
return new URL(new Quoted("'" + returner + "'", returner, false, this.index, this.currentFileInfo), this.index, this.currentFileInfo);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
var Keyword = require("../tree/keyword"),
|
||||
DetachedRuleset = require("../tree/detached-ruleset"),
|
||||
Dimension = require("../tree/dimension"),
|
||||
Color = require("../tree/color"),
|
||||
Quoted = require("../tree/quoted"),
|
||||
@@ -8,12 +9,22 @@ var Keyword = require("../tree/keyword"),
|
||||
functionRegistry = require("./function-registry");
|
||||
|
||||
var isa = function (n, Type) {
|
||||
return (n instanceof Type) ? Keyword.True : Keyword.False;
|
||||
return (n instanceof Type) ? Keyword.True : Keyword.False;
|
||||
},
|
||||
isunit = function (n, unit) {
|
||||
return (n instanceof Dimension) && n.unit.is(unit.value || unit) ? Keyword.True : Keyword.False;
|
||||
if (unit === undefined) {
|
||||
throw { type: "Argument", message: "missing the required second argument to isunit." };
|
||||
}
|
||||
unit = typeof unit.value === "string" ? unit.value : unit;
|
||||
if (typeof unit !== "string") {
|
||||
throw { type: "Argument", message: "Second argument to isunit should be a unit or a string." };
|
||||
}
|
||||
return (n instanceof Dimension) && n.unit.is(unit) ? Keyword.True : Keyword.False;
|
||||
};
|
||||
functionRegistry.addMultiple({
|
||||
isruleset: function (n) {
|
||||
return isa(n, DetachedRuleset);
|
||||
},
|
||||
iscolor: function (n) {
|
||||
return isa(n, Color);
|
||||
},
|
||||
@@ -40,8 +51,10 @@ functionRegistry.addMultiple({
|
||||
},
|
||||
isunit: isunit,
|
||||
unit: function (val, unit) {
|
||||
if(!(val instanceof Dimension)) {
|
||||
throw { type: "Argument", message: "the first argument to unit must be a number" + (val instanceof Operation ? ". Have you forgotten parenthesis?" : "") };
|
||||
if (!(val instanceof Dimension)) {
|
||||
throw { type: "Argument",
|
||||
message: "the first argument to unit must be a number" +
|
||||
(val instanceof Operation ? ". Have you forgotten parenthesis?" : "") };
|
||||
}
|
||||
if (unit) {
|
||||
if (unit instanceof Keyword) {
|
||||
|
||||
@@ -16,13 +16,13 @@ module.exports = function(environment) {
|
||||
this.rootFilename = rootFileInfo.filename;
|
||||
this.paths = context.paths || []; // Search paths, when importing
|
||||
this.contents = {}; // map - filename to contents of all the files
|
||||
this.contentsIgnoredChars = {}; // map - filename to lines at the begining of each file to ignore
|
||||
this.contentsIgnoredChars = {}; // map - filename to lines at the beginning of each file to ignore
|
||||
this.mime = context.mime;
|
||||
this.error = null;
|
||||
this.context = context;
|
||||
// Deprecated? Unused outside of here, could be useful.
|
||||
this.queue = []; // Files which haven't been imported yet
|
||||
this.files = []; // Holds the imported parse trees.
|
||||
this.files = {}; // Holds the imported parse trees.
|
||||
};
|
||||
/**
|
||||
* Add an import to be imported
|
||||
@@ -40,12 +40,14 @@ module.exports = function(environment) {
|
||||
importManager.queue.splice(importManager.queue.indexOf(path), 1); // Remove the path from the queue
|
||||
|
||||
var importedEqualsRoot = fullPath === importManager.rootFilename;
|
||||
|
||||
importManager.files[fullPath] = root;
|
||||
|
||||
if (e && !importManager.error) { importManager.error = e; }
|
||||
|
||||
callback(e, root, importedEqualsRoot, fullPath);
|
||||
if (importOptions.optional && e) {
|
||||
callback(null, {rules:[]}, false, null);
|
||||
}
|
||||
else {
|
||||
importManager.files[fullPath] = root;
|
||||
if (e && !importManager.error) { importManager.error = e; }
|
||||
callback(e, root, importedEqualsRoot, fullPath);
|
||||
}
|
||||
};
|
||||
|
||||
var newFileInfo = {
|
||||
@@ -66,48 +68,59 @@ module.exports = function(environment) {
|
||||
path = fileManager.tryAppendLessExtension(path);
|
||||
}
|
||||
|
||||
fileManager.loadFile(path, currentFileInfo.currentDirectory, this.context, environment)
|
||||
.then(function loadFileCallback(loadedFile) {
|
||||
var resolvedFilename = loadedFile.filename,
|
||||
contents = loadedFile.contents;
|
||||
var loadFileCallback = function(loadedFile) {
|
||||
var resolvedFilename = loadedFile.filename,
|
||||
contents = loadedFile.contents.replace(/^\uFEFF/, '');
|
||||
|
||||
// Pass on an updated rootpath if path of imported file is relative and file
|
||||
// is in a (sub|sup) directory
|
||||
//
|
||||
// Examples:
|
||||
// - If path of imported file is 'module/nav/nav.less' and rootpath is 'less/',
|
||||
// then rootpath should become 'less/module/nav/'
|
||||
// - If path of imported file is '../mixins.less' and rootpath is 'less/',
|
||||
// then rootpath should become 'less/../'
|
||||
newFileInfo.currentDirectory = fileManager.getPath(resolvedFilename);
|
||||
if(newFileInfo.relativeUrls) {
|
||||
newFileInfo.rootpath = fileManager.join((importManager.context.rootpath || ""), fileManager.pathDiff(newFileInfo.currentDirectory, newFileInfo.entryPath));
|
||||
if (!fileManager.isPathAbsolute(newFileInfo.rootpath) && fileManager.alwaysMakePathsAbsolute()) {
|
||||
newFileInfo.rootpath = fileManager.join(newFileInfo.entryPath, newFileInfo.rootpath);
|
||||
}
|
||||
// Pass on an updated rootpath if path of imported file is relative and file
|
||||
// is in a (sub|sup) directory
|
||||
//
|
||||
// Examples:
|
||||
// - If path of imported file is 'module/nav/nav.less' and rootpath is 'less/',
|
||||
// then rootpath should become 'less/module/nav/'
|
||||
// - If path of imported file is '../mixins.less' and rootpath is 'less/',
|
||||
// then rootpath should become 'less/../'
|
||||
newFileInfo.currentDirectory = fileManager.getPath(resolvedFilename);
|
||||
if (newFileInfo.relativeUrls) {
|
||||
newFileInfo.rootpath = fileManager.join(
|
||||
(importManager.context.rootpath || ""),
|
||||
fileManager.pathDiff(newFileInfo.currentDirectory, newFileInfo.entryPath));
|
||||
|
||||
if (!fileManager.isPathAbsolute(newFileInfo.rootpath) && fileManager.alwaysMakePathsAbsolute()) {
|
||||
newFileInfo.rootpath = fileManager.join(newFileInfo.entryPath, newFileInfo.rootpath);
|
||||
}
|
||||
newFileInfo.filename = resolvedFilename;
|
||||
}
|
||||
newFileInfo.filename = resolvedFilename;
|
||||
|
||||
var newEnv = new contexts.Parse(importManager.context);
|
||||
var newEnv = new contexts.Parse(importManager.context);
|
||||
|
||||
newEnv.processImports = false;
|
||||
importManager.contents[resolvedFilename] = contents;
|
||||
newEnv.processImports = false;
|
||||
importManager.contents[resolvedFilename] = contents;
|
||||
|
||||
if (currentFileInfo.reference || importOptions.reference) {
|
||||
newFileInfo.reference = true;
|
||||
}
|
||||
if (currentFileInfo.reference || importOptions.reference) {
|
||||
newFileInfo.reference = true;
|
||||
}
|
||||
|
||||
if (importOptions.inline) {
|
||||
fileParsedFunc(null, contents, resolvedFilename);
|
||||
} else {
|
||||
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (e, root) {
|
||||
fileParsedFunc(e, root, resolvedFilename);
|
||||
});
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
fileParsedFunc(error);
|
||||
});
|
||||
if (importOptions.inline) {
|
||||
fileParsedFunc(null, contents, resolvedFilename);
|
||||
} else {
|
||||
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (e, root) {
|
||||
fileParsedFunc(e, root, resolvedFilename);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var promise = fileManager.loadFile(path, currentFileInfo.currentDirectory, this.context, environment,
|
||||
function(err, loadedFile) {
|
||||
if (err) {
|
||||
fileParsedFunc(err);
|
||||
} else {
|
||||
loadFileCallback(loadedFile);
|
||||
}
|
||||
});
|
||||
if (promise) {
|
||||
promise.then(loadFileCallback, fileParsedFunc);
|
||||
}
|
||||
};
|
||||
return ImportManager;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ module.exports = function(environment, fileManagers) {
|
||||
var SourceMapOutput, SourceMapBuilder, ParseTree, ImportManager, Environment;
|
||||
|
||||
var less = {
|
||||
version: [2, 0, "0-b1"],
|
||||
version: [2, 3, 1],
|
||||
data: require('./data'),
|
||||
tree: require('./tree'),
|
||||
Environment: (Environment = require("./environment/environment")),
|
||||
@@ -13,10 +13,11 @@ module.exports = function(environment, fileManagers) {
|
||||
functions: require('./functions')(environment),
|
||||
contexts: require("./contexts"),
|
||||
SourceMapOutput: (SourceMapOutput = require('./source-map-output')(environment)),
|
||||
SourceMapBuilder: (SourceMapBuilder = require('./source-map-builder')(SourceMapOutput)),
|
||||
SourceMapBuilder: (SourceMapBuilder = require('./source-map-builder')(SourceMapOutput, environment)),
|
||||
ParseTree: (ParseTree = require('./parse-tree')(SourceMapBuilder)),
|
||||
ImportManager: (ImportManager = require('./import-manager')(environment)),
|
||||
render: require("./render")(environment, ParseTree, ImportManager),
|
||||
parse: require("./parse")(environment, ParseTree, ImportManager),
|
||||
LessError: require('./less-error'),
|
||||
transformTree: require('./transform-tree'),
|
||||
utils: require('./utils'),
|
||||
|
||||
@@ -17,7 +17,7 @@ var LessError = module.exports = function LessError(e, importManager, currentFil
|
||||
this.type = e.type || 'Syntax';
|
||||
this.filename = filename;
|
||||
this.index = e.index;
|
||||
this.line = typeof(line) === 'number' ? line + 1 : null;
|
||||
this.line = typeof line === 'number' ? line + 1 : null;
|
||||
this.callLine = callLine + 1;
|
||||
this.callExtract = lines[callLine];
|
||||
this.column = col;
|
||||
@@ -31,5 +31,12 @@ var LessError = module.exports = function LessError(e, importManager, currentFil
|
||||
this.stack = e.stack;
|
||||
};
|
||||
|
||||
LessError.prototype = Object.create(Error.prototype);
|
||||
if (typeof Object.create === 'undefined') {
|
||||
var F = function () {};
|
||||
F.prototype = Error.prototype;
|
||||
LessError.prototype = new F();
|
||||
} else {
|
||||
LessError.prototype = Object.create(Error.prototype);
|
||||
}
|
||||
|
||||
LessError.prototype.constructor = LessError;
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
this._listeners.push(listener);
|
||||
},
|
||||
removeListener: function(listener) {
|
||||
for(var i = 0; i < this._listeners.length; i++) {
|
||||
for (var i = 0; i < this._listeners.length; i++) {
|
||||
if (this._listeners[i] === listener) {
|
||||
this._listeners.splice(i, 1);
|
||||
return;
|
||||
@@ -23,7 +23,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
_fireEvent: function(type, msg) {
|
||||
for(var i = 0; i < this._listeners.length; i++) {
|
||||
for (var i = 0; i < this._listeners.length; i++) {
|
||||
var logFunction = this._listeners[i][type];
|
||||
if (logFunction) {
|
||||
logFunction(msg);
|
||||
|
||||
@@ -1,47 +1,60 @@
|
||||
var LessError = require('./less-error'),
|
||||
transformTree = require("./transform-tree");
|
||||
transformTree = require("./transform-tree"),
|
||||
logger = require("./logger");
|
||||
|
||||
module.exports = function(SourceMapBuilder) {
|
||||
var ParseTree = function(root, imports) {
|
||||
this.root = root;
|
||||
this.imports = imports;
|
||||
};
|
||||
var ParseTree = function(root, imports) {
|
||||
this.root = root;
|
||||
this.imports = imports;
|
||||
};
|
||||
|
||||
ParseTree.prototype.toCSS = function(options) {
|
||||
var evaldRoot, result = {}, sourceMapBuilder;
|
||||
try {
|
||||
evaldRoot = transformTree(this.root, options);
|
||||
} catch (e) {
|
||||
throw new LessError(e, this.imports);
|
||||
}
|
||||
ParseTree.prototype.toCSS = function(options) {
|
||||
var evaldRoot, result = {}, sourceMapBuilder;
|
||||
try {
|
||||
evaldRoot = transformTree(this.root, options);
|
||||
} catch (e) {
|
||||
throw new LessError(e, this.imports);
|
||||
}
|
||||
|
||||
try {
|
||||
var toCSSOptions = {
|
||||
compress: Boolean(options.compress),
|
||||
dumpLineNumbers: options.dumpLineNumbers,
|
||||
strictUnits: Boolean(options.strictUnits),
|
||||
numPrecision: 8};
|
||||
try {
|
||||
var compress = Boolean(options.compress);
|
||||
if (compress) {
|
||||
logger.warn("The compress option has been deprecated. We recommend you use a dedicated css minifier, for instance see less-plugin-clean-css.");
|
||||
}
|
||||
|
||||
var toCSSOptions = {
|
||||
compress: compress,
|
||||
dumpLineNumbers: options.dumpLineNumbers,
|
||||
strictUnits: Boolean(options.strictUnits),
|
||||
numPrecision: 8};
|
||||
|
||||
if (options.sourceMap) {
|
||||
sourceMapBuilder = new SourceMapBuilder(options.sourceMap);
|
||||
result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);
|
||||
} else {
|
||||
result.css = evaldRoot.toCSS(toCSSOptions);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new LessError(e, this.imports);
|
||||
}
|
||||
|
||||
if (options.pluginManager) {
|
||||
var postProcessors = options.pluginManager.getPostProcessors();
|
||||
for (var i = 0; i < postProcessors.length; i++) {
|
||||
result.css = postProcessors[i].process(result.css, { sourceMap: sourceMapBuilder, options: options, imports: this.imports });
|
||||
}
|
||||
}
|
||||
if (options.sourceMap) {
|
||||
sourceMapBuilder = new SourceMapBuilder(options.sourceMap);
|
||||
result.css = sourceMapBuilder.toCSS(evaldRoot, toCSSOptions, this.imports);
|
||||
} else {
|
||||
result.css = evaldRoot.toCSS(toCSSOptions);
|
||||
result.map = sourceMapBuilder.getExternalSourceMap();
|
||||
}
|
||||
} catch (e) {
|
||||
throw new LessError(e, this.imports);
|
||||
}
|
||||
|
||||
if (options.pluginManager) {
|
||||
var postProcessors = options.pluginManager.getPostProcessors();
|
||||
for(var i = 0; i < postProcessors.length; i++) {
|
||||
result.css = postProcessors[i].process(result.css, { sourceMap: sourceMapBuilder, options: options, imports: this.imports });
|
||||
result.imports = [];
|
||||
for (var file in this.imports.files) {
|
||||
if (this.imports.files.hasOwnProperty(file) && file !== this.imports.rootFilename) {
|
||||
result.imports.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.sourceMap) {
|
||||
result.map = sourceMapBuilder.getExternalSourceMap();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
return ParseTree;
|
||||
return result;
|
||||
};
|
||||
return ParseTree;
|
||||
};
|
||||
|
||||
68
lib/less/parse.js
Normal file
68
lib/less/parse.js
Normal file
@@ -0,0 +1,68 @@
|
||||
var PromiseConstructor,
|
||||
contexts = require("./contexts"),
|
||||
Parser = require('./parser/parser'),
|
||||
PluginManager = require('./plugin-manager');
|
||||
|
||||
module.exports = function(environment, ParseTree, ImportManager) {
|
||||
var parse = function (input, options, callback) {
|
||||
options = options || {};
|
||||
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
if (!PromiseConstructor) {
|
||||
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
|
||||
}
|
||||
var self = this;
|
||||
return new PromiseConstructor(function (resolve, reject) {
|
||||
parse.call(self, input, options, function(err, output) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(output);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
var context,
|
||||
rootFileInfo,
|
||||
pluginManager = new PluginManager(this);
|
||||
|
||||
pluginManager.addPlugins(options.plugins);
|
||||
options.pluginManager = pluginManager;
|
||||
|
||||
context = new contexts.Parse(options);
|
||||
|
||||
if (options.rootFileInfo) {
|
||||
rootFileInfo = options.rootFileInfo;
|
||||
} else {
|
||||
var filename = options.filename || "input";
|
||||
var entryPath = filename.replace(/[^\/\\]*$/, "");
|
||||
rootFileInfo = {
|
||||
filename: filename,
|
||||
relativeUrls: context.relativeUrls,
|
||||
rootpath: context.rootpath || "",
|
||||
currentDirectory: entryPath,
|
||||
entryPath: entryPath,
|
||||
rootFilename: filename
|
||||
};
|
||||
// add in a missing trailing slash
|
||||
if (rootFileInfo.rootpath && rootFileInfo.rootpath.slice(-1) !== "/") {
|
||||
rootFileInfo.rootpath += "/";
|
||||
}
|
||||
}
|
||||
|
||||
var imports = new ImportManager(context, rootFileInfo);
|
||||
|
||||
new Parser(context, imports, rootFileInfo)
|
||||
.parse(input, function (e, root) {
|
||||
if (e) { return callback(e); }
|
||||
callback(null, root, imports, options);
|
||||
}, options);
|
||||
}
|
||||
};
|
||||
return parse;
|
||||
};
|
||||
@@ -16,7 +16,8 @@ module.exports = function() {
|
||||
saveStack.push( { current: current, i: parserInput.i, j: j });
|
||||
};
|
||||
parserInput.restore = function(possibleErrorMessage) {
|
||||
if (parserInput.i > furthest) {
|
||||
|
||||
if (parserInput.i > furthest || (parserInput.i === furthest && possibleErrorMessage && !furthestPossibleErrorMessage)) {
|
||||
furthest = parserInput.i;
|
||||
furthestPossibleErrorMessage = possibleErrorMessage;
|
||||
}
|
||||
@@ -72,7 +73,7 @@ module.exports = function() {
|
||||
//
|
||||
skipWhitespace(length);
|
||||
|
||||
if(typeof(match) === 'string') {
|
||||
if (typeof match === 'string') {
|
||||
return match;
|
||||
} else {
|
||||
return match.length === 1 ? match[0] : match;
|
||||
@@ -91,7 +92,7 @@ module.exports = function() {
|
||||
}
|
||||
|
||||
skipWhitespace(m[0].length);
|
||||
if(typeof m === "string") {
|
||||
if (typeof m === "string") {
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -132,7 +133,7 @@ module.exports = function() {
|
||||
c = inp.charCodeAt(parserInput.i);
|
||||
|
||||
if (parserInput.autoCommentAbsorb && c === CHARCODE_FORWARD_SLASH) {
|
||||
nextChar = inp[parserInput.i + 1];
|
||||
nextChar = inp.charAt(parserInput.i + 1);
|
||||
if (nextChar === '/') {
|
||||
comment = {index: parserInput.i, isLineComment: true};
|
||||
var nextNewLine = inp.indexOf("\n", parserInput.i + 1);
|
||||
@@ -184,7 +185,7 @@ module.exports = function() {
|
||||
// Same as $(), but don't change the state of the parser,
|
||||
// just return the match.
|
||||
parserInput.peek = function(tok) {
|
||||
if (typeof(tok) === 'string') {
|
||||
if (typeof tok === 'string') {
|
||||
return input.charAt(parserInput.i) === tok;
|
||||
} else {
|
||||
return tok.test(current);
|
||||
@@ -238,7 +239,7 @@ module.exports = function() {
|
||||
|
||||
parserInput.end = function() {
|
||||
var message,
|
||||
isFinished = parserInput.i >= input.length - 1;
|
||||
isFinished = parserInput.i >= input.length;
|
||||
|
||||
if (parserInput.i < furthest) {
|
||||
message = furthestPossibleErrorMessage;
|
||||
|
||||
@@ -47,7 +47,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + parserInput.currentChar() + "'"
|
||||
error(msg || (typeof arg === 'string' ? "expected '" + arg + "' got '" + parserInput.currentChar() + "'"
|
||||
: "unexpected token"));
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
ignored[fileInfo.filename] += preText.length;
|
||||
}
|
||||
|
||||
str = str.replace(/\r\n/g, '\n');
|
||||
str = str.replace(/\r\n?/g, '\n');
|
||||
// Remove potential UTF Byte Order Mark
|
||||
str = preText + str.replace(/^\uFEFF/, '') + modifyVars;
|
||||
imports.contents[fileInfo.filename] = str;
|
||||
@@ -213,7 +213,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
// Ruleset (Selector '.class', [
|
||||
// Rule ("color", Value ([Expression [Color #fff]]))
|
||||
// Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
|
||||
// Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
|
||||
// Rule ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
|
||||
// Ruleset (Selector [Element '>', '.child'], [...])
|
||||
// ])
|
||||
//
|
||||
@@ -240,17 +240,28 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
primary: function () {
|
||||
var mixin = this.mixin, root = [], node;
|
||||
|
||||
while (!parserInput.finished)
|
||||
while (true)
|
||||
{
|
||||
while(true) {
|
||||
while (true) {
|
||||
node = this.comment();
|
||||
if (!node) { break; }
|
||||
root.push(node);
|
||||
}
|
||||
// always process comments before deciding if finished
|
||||
if (parserInput.finished) {
|
||||
break;
|
||||
}
|
||||
if (parserInput.peek('}')) {
|
||||
break;
|
||||
}
|
||||
node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() ||
|
||||
|
||||
node = this.extendRule();
|
||||
if (node) {
|
||||
root = root.concat(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
node = mixin.definition() || this.rule() || this.ruleset() ||
|
||||
mixin.call() || this.rulesetCall() || this.directive();
|
||||
if (node) {
|
||||
root.push(node);
|
||||
@@ -330,7 +341,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
|
||||
if (nameLC === 'alpha') {
|
||||
alpha = parsers.alpha();
|
||||
if(alpha) {
|
||||
if (alpha) {
|
||||
return alpha;
|
||||
}
|
||||
}
|
||||
@@ -452,7 +463,9 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
var rgb;
|
||||
|
||||
if (parserInput.currentChar() === '#' && (rgb = parserInput.$re(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) {
|
||||
var colorCandidateString = rgb.input.match(/^#([\w]+).*/); // strip colons, brackets, whitespaces and other characters that should not definitely be part of color string
|
||||
// strip colons, brackets, whitespaces and other characters that should not
|
||||
// definitely be part of color string
|
||||
var colorCandidateString = rgb.input.match(/^#([\w]+).*/);
|
||||
colorCandidateString = colorCandidateString[1];
|
||||
if (!colorCandidateString.match(/^[A-Fa-f0-9]+$/)) { // verify if candidate consists only of allowed HEX characters
|
||||
error("Invalid HEX color code");
|
||||
@@ -543,17 +556,27 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
elements = null;
|
||||
while (! (option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
|
||||
e = this.element();
|
||||
if (!e) { break; }
|
||||
if (elements) { elements.push(e); } else { elements = [ e ]; }
|
||||
if (!e) {
|
||||
break;
|
||||
}
|
||||
if (elements) {
|
||||
elements.push(e);
|
||||
} else {
|
||||
elements = [ e ];
|
||||
}
|
||||
}
|
||||
|
||||
option = option && option[1];
|
||||
if (!elements)
|
||||
if (!elements) {
|
||||
error("Missing target selector for :extend().");
|
||||
}
|
||||
extend = new(tree.Extend)(new(tree.Selector)(elements), option, index);
|
||||
if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
|
||||
|
||||
} while(parserInput.$char(","));
|
||||
if (extendList) {
|
||||
extendList.push(extend);
|
||||
} else {
|
||||
extendList = [ extend ];
|
||||
}
|
||||
} while (parserInput.$char(","));
|
||||
|
||||
expect(/^\)/);
|
||||
|
||||
@@ -601,7 +624,11 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
break;
|
||||
}
|
||||
elem = new(tree.Element)(c, e, elemIndex, fileInfo);
|
||||
if (elements) { elements.push(elem); } else { elements = [ elem ]; }
|
||||
if (elements) {
|
||||
elements.push(elem);
|
||||
} else {
|
||||
elements = [ elem ];
|
||||
}
|
||||
c = parserInput.$char('>');
|
||||
}
|
||||
|
||||
@@ -627,7 +654,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
var entities = parsers.entities,
|
||||
returner = { args:null, variadic: false },
|
||||
expressions = [], argsSemiColon = [], argsComma = [],
|
||||
isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg;
|
||||
isSemiColonSeparated, expressionContainsNamed, name, nameLoop, value, arg;
|
||||
|
||||
parserInput.save();
|
||||
|
||||
@@ -638,10 +665,10 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
parserInput.commentStore.length = 0;
|
||||
if (parserInput.currentChar() === '.' && parserInput.$re(/^\.{3}/)) {
|
||||
returner.variadic = true;
|
||||
if (parserInput.$char(";") && !isSemiColonSeperated) {
|
||||
isSemiColonSeperated = true;
|
||||
if (parserInput.$char(";") && !isSemiColonSeparated) {
|
||||
isSemiColonSeparated = true;
|
||||
}
|
||||
(isSemiColonSeperated ? argsSemiColon : argsComma)
|
||||
(isSemiColonSeparated ? argsSemiColon : argsComma)
|
||||
.push({ variadic: true });
|
||||
break;
|
||||
}
|
||||
@@ -671,7 +698,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
if (val && val instanceof tree.Variable) {
|
||||
if (parserInput.$char(':')) {
|
||||
if (expressions.length > 0) {
|
||||
if (isSemiColonSeperated) {
|
||||
if (isSemiColonSeparated) {
|
||||
error("Cannot mix ; and , as delimiter types");
|
||||
}
|
||||
expressionContainsNamed = true;
|
||||
@@ -694,10 +721,10 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
nameLoop = (name = val.name);
|
||||
} else if (!isCall && parserInput.$re(/^\.{3}/)) {
|
||||
returner.variadic = true;
|
||||
if (parserInput.$char(";") && !isSemiColonSeperated) {
|
||||
isSemiColonSeperated = true;
|
||||
if (parserInput.$char(";") && !isSemiColonSeparated) {
|
||||
isSemiColonSeparated = true;
|
||||
}
|
||||
(isSemiColonSeperated ? argsSemiColon : argsComma)
|
||||
(isSemiColonSeparated ? argsSemiColon : argsComma)
|
||||
.push({ name: arg.name, variadic: true });
|
||||
break;
|
||||
} else if (!isCall) {
|
||||
@@ -716,13 +743,13 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parserInput.$char(';') || isSemiColonSeperated) {
|
||||
if (parserInput.$char(';') || isSemiColonSeparated) {
|
||||
|
||||
if (expressionContainsNamed) {
|
||||
error("Cannot mix ; and , as delimiter types");
|
||||
}
|
||||
|
||||
isSemiColonSeperated = true;
|
||||
isSemiColonSeparated = true;
|
||||
|
||||
if (expressions.length > 1) {
|
||||
value = new(tree.Value)(expressions);
|
||||
@@ -736,7 +763,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
}
|
||||
|
||||
parserInput.forget();
|
||||
returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
|
||||
returner.args = isSemiColonSeparated ? argsSemiColon : argsComma;
|
||||
return returner;
|
||||
},
|
||||
//
|
||||
@@ -859,8 +886,10 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
|
||||
c = this.combinator();
|
||||
|
||||
e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) || parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
|
||||
parserInput.$char('*') || parserInput.$char('&') || this.attribute() || parserInput.$re(/^\([^()@]+\)/) || parserInput.$re(/^[\.#](?=@)/) ||
|
||||
e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) ||
|
||||
parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
|
||||
parserInput.$char('*') || parserInput.$char('&') || this.attribute() ||
|
||||
parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[\.#:](?=@)/) ||
|
||||
this.entities.variableCurly();
|
||||
|
||||
if (! e) {
|
||||
@@ -932,19 +961,27 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
// Selectors are made out of one or more Elements, see above.
|
||||
//
|
||||
selector: function (isLess) {
|
||||
var index = parserInput.i, elements, extendList, c, e, extend, when, condition;
|
||||
var index = parserInput.i, elements, extendList, c, e, allExtends, when, condition;
|
||||
|
||||
while ((isLess && (extend = this.extend())) || (isLess && (when = parserInput.$re(/^when/))) || (e = this.element())) {
|
||||
while ((isLess && (extendList = this.extend())) || (isLess && (when = parserInput.$re(/^when/))) || (e = this.element())) {
|
||||
if (when) {
|
||||
condition = expect(this.conditions, 'expected condition');
|
||||
} else if (condition) {
|
||||
error("CSS guard can only be used at the end of selector");
|
||||
} else if (extend) {
|
||||
if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
|
||||
} else if (extendList) {
|
||||
if (allExtends) {
|
||||
allExtends = allExtends.concat(extendList);
|
||||
} else {
|
||||
allExtends = extendList;
|
||||
}
|
||||
} else {
|
||||
if (extendList) { error("Extend can only be used at the end of selector"); }
|
||||
if (allExtends) { error("Extend can only be used at the end of selector"); }
|
||||
c = parserInput.currentChar();
|
||||
if (elements) { elements.push(e); } else { elements = [ e ]; }
|
||||
if (elements) {
|
||||
elements.push(e);
|
||||
} else {
|
||||
elements = [ e ];
|
||||
}
|
||||
e = null;
|
||||
}
|
||||
if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
|
||||
@@ -952,8 +989,8 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
if (elements) { return new(tree.Selector)(elements, extendList, condition, index, fileInfo); }
|
||||
if (extendList) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
|
||||
if (elements) { return new(tree.Selector)(elements, allExtends, condition, index, fileInfo); }
|
||||
if (allExtends) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
|
||||
},
|
||||
attribute: function () {
|
||||
if (! parserInput.$char('[')) { return; }
|
||||
@@ -1019,7 +1056,11 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
if (!s) {
|
||||
break;
|
||||
}
|
||||
if (selectors) { selectors.push(s); } else { selectors = [ s ]; }
|
||||
if (selectors) {
|
||||
selectors.push(s);
|
||||
} else {
|
||||
selectors = [ s ];
|
||||
}
|
||||
parserInput.commentStore.length = 0;
|
||||
if (s.condition && selectors.length > 1) {
|
||||
error("Guards are only currently allowed on a single selector.");
|
||||
@@ -1156,11 +1197,11 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
case "css":
|
||||
optionName = "less";
|
||||
value = false;
|
||||
break;
|
||||
break;
|
||||
case "once":
|
||||
optionName = "multiple";
|
||||
value = false;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
options[optionName] = value;
|
||||
if (! parserInput.$char(',')) { break; }
|
||||
@@ -1171,7 +1212,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
},
|
||||
|
||||
importOption: function() {
|
||||
var opt = parserInput.$re(/^(less|css|multiple|once|inline|reference)/);
|
||||
var opt = parserInput.$re(/^(less|css|multiple|once|inline|reference|optional)/);
|
||||
if (opt) {
|
||||
return opt[1];
|
||||
}
|
||||
@@ -1299,6 +1340,10 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
hasBlock = true;
|
||||
break;
|
||||
*/
|
||||
case "@counter-style":
|
||||
hasIdentifier = true;
|
||||
hasBlock = true;
|
||||
break;
|
||||
case "@charset":
|
||||
hasIdentifier = true;
|
||||
hasBlock = false;
|
||||
@@ -1367,7 +1412,7 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
expressions.push(e);
|
||||
if (! parserInput.$char(',')) { break; }
|
||||
}
|
||||
} while(e);
|
||||
} while (e);
|
||||
|
||||
if (expressions.length > 0) {
|
||||
return new(tree.Value)(expressions);
|
||||
@@ -1381,15 +1426,19 @@ var Parser = function Parser(context, imports, fileInfo) {
|
||||
sub: function () {
|
||||
var a, e;
|
||||
|
||||
parserInput.save();
|
||||
if (parserInput.$char('(')) {
|
||||
a = this.addition();
|
||||
if (a) {
|
||||
if (a && parserInput.$char(')')) {
|
||||
parserInput.forget();
|
||||
e = new(tree.Expression)([a]);
|
||||
expectChar(')');
|
||||
e.parens = true;
|
||||
return e;
|
||||
}
|
||||
parserInput.restore("Expected ')'");
|
||||
return;
|
||||
}
|
||||
parserInput.restore();
|
||||
},
|
||||
multiplication: function () {
|
||||
var m, a, op, operation, isSpaced;
|
||||
@@ -1596,8 +1645,8 @@ Parser.serializeVars = function(vars) {
|
||||
for (var name in vars) {
|
||||
if (Object.hasOwnProperty.call(vars, name)) {
|
||||
var value = vars[name];
|
||||
s += ((name[0] === '@') ? '' : '@') + name +': '+ value +
|
||||
((('' + value).slice(-1) === ';') ? '' : ';');
|
||||
s += ((name[0] === '@') ? '' : '@') + name + ': ' + value +
|
||||
((String(value).slice(-1) === ';') ? '' : ';');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ var PluginManager = function(less) {
|
||||
*/
|
||||
PluginManager.prototype.addPlugins = function(plugins) {
|
||||
if (plugins) {
|
||||
for(var i = 0;i < plugins.length; i++) {
|
||||
for (var i = 0; i < plugins.length; i++) {
|
||||
this.addPlugin(plugins[i]);
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ PluginManager.prototype.addPreProcessor = function(preProcessor, priority) {
|
||||
*/
|
||||
PluginManager.prototype.addPostProcessor = function(postProcessor, priority) {
|
||||
var indexToInsertAt;
|
||||
for(indexToInsertAt = 0; indexToInsertAt < this.postProcessors.length; indexToInsertAt++) {
|
||||
for (indexToInsertAt = 0; indexToInsertAt < this.postProcessors.length; indexToInsertAt++) {
|
||||
if (this.postProcessors[indexToInsertAt].priority >= priority) {
|
||||
break;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ PluginManager.prototype.getPreProcessors = function() {
|
||||
*/
|
||||
PluginManager.prototype.getPostProcessors = function() {
|
||||
var postProcessors = [];
|
||||
for(var i = 0; i < this.postProcessors.length; i++) {
|
||||
for (var i = 0; i < this.postProcessors.length; i++) {
|
||||
postProcessors.push(this.postProcessors[i].postProcessor);
|
||||
}
|
||||
return postProcessors;
|
||||
|
||||
@@ -1,65 +1,41 @@
|
||||
var PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
|
||||
contexts = require("./contexts"),
|
||||
Parser = require('./parser/parser'),
|
||||
PluginManager = require('./plugin-manager');
|
||||
var PromiseConstructor;
|
||||
|
||||
module.exports = function(environment, ParseTree, ImportManager) {
|
||||
var render = function (input, options, callback) {
|
||||
options = options || {};
|
||||
|
||||
if (typeof(options) === 'function') {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
render.call(this, input, options)
|
||||
.then(function(css) {
|
||||
callback(null, css);
|
||||
},
|
||||
function(error) {
|
||||
callback(error);
|
||||
});
|
||||
} else {
|
||||
var context,
|
||||
rootFileInfo,
|
||||
pluginManager = new PluginManager(this);
|
||||
|
||||
pluginManager.addPlugins(options.plugins);
|
||||
options.pluginManager = pluginManager;
|
||||
|
||||
context = new contexts.Parse(options);
|
||||
|
||||
if (options.rootFileInfo) {
|
||||
rootFileInfo = options.rootFileInfo;
|
||||
} else {
|
||||
var filename = options.filename || "input";
|
||||
var entryPath = filename.replace(/[^\/\\]*$/, "");
|
||||
rootFileInfo = {
|
||||
filename: filename,
|
||||
relativeUrls: context.relativeUrls,
|
||||
rootpath: context.rootpath || "",
|
||||
currentDirectory: entryPath,
|
||||
entryPath: entryPath,
|
||||
rootFilename: filename
|
||||
};
|
||||
if (!callback) {
|
||||
if (!PromiseConstructor) {
|
||||
PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise;
|
||||
}
|
||||
|
||||
var imports = new ImportManager(context, rootFileInfo);
|
||||
var parser = new Parser(context, imports, rootFileInfo);
|
||||
|
||||
var self = this;
|
||||
return new PromiseConstructor(function (resolve, reject) {
|
||||
parser.parse(input, function (e, root) {
|
||||
if (e) { return reject(e); }
|
||||
try {
|
||||
var parseTree = new ParseTree(root, imports);
|
||||
var result = parseTree.toCSS(options);
|
||||
resolve(result);
|
||||
render.call(self, input, options, function(err, output) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(output);
|
||||
}
|
||||
catch (err) { reject( err); }
|
||||
}, options);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.parse(input, options, function(err, root, imports, options) {
|
||||
if (err) { return callback(err); }
|
||||
|
||||
var result;
|
||||
try {
|
||||
var parseTree = new ParseTree(root, imports);
|
||||
result = parseTree.toCSS(options);
|
||||
}
|
||||
catch (err) { return callback(err); }
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return render;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function (SourceMapOutput) {
|
||||
module.exports = function (SourceMapOutput, environment) {
|
||||
|
||||
var SourceMapBuilder = function (options) {
|
||||
this.options = options;
|
||||
@@ -26,7 +26,19 @@ module.exports = function (SourceMapOutput) {
|
||||
if (this.options.sourceMapInputFilename) {
|
||||
this.sourceMapInputFilename = sourceMapOutput.normalizeFilename(this.options.sourceMapInputFilename);
|
||||
}
|
||||
return css;
|
||||
return css + this.getCSSAppendage();
|
||||
};
|
||||
|
||||
SourceMapBuilder.prototype.getCSSAppendage = function() {
|
||||
var sourceMapURL = this.sourceMapURL;
|
||||
if (this.options.sourceMapFileInline) {
|
||||
sourceMapURL = "data:application/json;base64," + environment.encodeBase64(this.sourceMap);
|
||||
}
|
||||
|
||||
if (sourceMapURL) {
|
||||
return "/*# sourceMappingURL=" + sourceMapURL + " */";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
SourceMapBuilder.prototype.getExternalSourceMap = function() {
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = function (environment) {
|
||||
}
|
||||
if (options.sourceMapRootpath) {
|
||||
this._sourceMapRootpath = options.sourceMapRootpath.replace(/\\/g, '/');
|
||||
if (this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1) !== '/') {
|
||||
if (this._sourceMapRootpath.charAt(this._sourceMapRootpath.length - 1) !== '/') {
|
||||
this._sourceMapRootpath += '/';
|
||||
}
|
||||
} else {
|
||||
@@ -23,7 +23,6 @@ module.exports = function (environment) {
|
||||
}
|
||||
this._outputSourceFiles = options.outputSourceFiles;
|
||||
this._sourceMapGeneratorConstructor = environment.getSourceMapGenerator();
|
||||
this._sourceMapFileInline = options.sourceMapFileInline;
|
||||
|
||||
this._lineNumber = 0;
|
||||
this._column = 0;
|
||||
@@ -35,7 +34,7 @@ module.exports = function (environment) {
|
||||
if (this._sourceMapBasepath && filename.indexOf(this._sourceMapBasepath) === 0) {
|
||||
filename = filename.substring(this._sourceMapBasepath.length);
|
||||
if (filename.charAt(0) === '\\' || filename.charAt(0) === '/') {
|
||||
filename = filename.substring(1);
|
||||
filename = filename.substring(1);
|
||||
}
|
||||
}
|
||||
return (this._sourceMapRootpath || "") + filename;
|
||||
@@ -67,11 +66,11 @@ module.exports = function (environment) {
|
||||
}
|
||||
inputSource = inputSource.substring(0, index);
|
||||
sourceLines = inputSource.split("\n");
|
||||
sourceColumns = sourceLines[sourceLines.length-1];
|
||||
sourceColumns = sourceLines[sourceLines.length - 1];
|
||||
}
|
||||
|
||||
lines = chunk.split("\n");
|
||||
columns = lines[lines.length-1];
|
||||
columns = lines[lines.length - 1];
|
||||
|
||||
if (fileInfo) {
|
||||
if (!mapLines) {
|
||||
@@ -79,7 +78,7 @@ module.exports = function (environment) {
|
||||
original: { line: sourceLines.length, column: sourceColumns.length},
|
||||
source: this.normalizeFilename(fileInfo.filename)});
|
||||
} else {
|
||||
for(i = 0; i < lines.length; i++) {
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
this._sourceMapGenerator.addMapping({ generated: { line: this._lineNumber + i + 1, column: i === 0 ? this._column : 0},
|
||||
original: { line: sourceLines.length + i, column: i === 0 ? sourceColumns.length : 0},
|
||||
source: this.normalizeFilename(fileInfo.filename)});
|
||||
@@ -105,7 +104,7 @@ module.exports = function (environment) {
|
||||
this._sourceMapGenerator = new this._sourceMapGeneratorConstructor({ file: this._outputFilename, sourceRoot: null });
|
||||
|
||||
if (this._outputSourceFiles) {
|
||||
for(var filename in this._contentsMap) {
|
||||
for (var filename in this._contentsMap) {
|
||||
if (this._contentsMap.hasOwnProperty(filename))
|
||||
{
|
||||
var source = this._contentsMap[filename];
|
||||
@@ -130,15 +129,7 @@ module.exports = function (environment) {
|
||||
}
|
||||
this.sourceMapURL = sourceMapURL;
|
||||
|
||||
if (!this._sourceMapFileInline) {
|
||||
this.sourceMap = sourceMapContent;
|
||||
} else {
|
||||
sourceMapURL = "data:application/json;base64," + environment.encodeBase64(sourceMapContent);
|
||||
}
|
||||
|
||||
if (sourceMapURL) {
|
||||
this._css.push("/*# sourceMappingURL=" + sourceMapURL + " */");
|
||||
}
|
||||
this.sourceMap = sourceMapContent;
|
||||
}
|
||||
|
||||
return this._css.join('');
|
||||
|
||||
@@ -21,7 +21,7 @@ module.exports = function(root, options) {
|
||||
// ])
|
||||
// )
|
||||
//
|
||||
if (typeof(variables) === 'object' && !Array.isArray(variables)) {
|
||||
if (typeof variables === 'object' && !Array.isArray(variables)) {
|
||||
variables = Object.keys(variables).map(function (k) {
|
||||
var value = variables[k];
|
||||
|
||||
@@ -45,7 +45,7 @@ module.exports = function(root, options) {
|
||||
|
||||
if (options.pluginManager) {
|
||||
var pluginVisitors = options.pluginManager.getVisitors();
|
||||
for(i =0; i < pluginVisitors.length; i++) {
|
||||
for (i = 0; i < pluginVisitors.length; i++) {
|
||||
var pluginVisitor = pluginVisitors[i];
|
||||
if (pluginVisitor.isPreEvalVisitor) {
|
||||
preEvalVisitors.push(pluginVisitor);
|
||||
@@ -59,13 +59,13 @@ module.exports = function(root, options) {
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0; i < preEvalVisitors.length; i++) {
|
||||
for (i = 0; i < preEvalVisitors.length; i++) {
|
||||
preEvalVisitors[i].run(root);
|
||||
}
|
||||
|
||||
evaldRoot = root.eval(evalEnv);
|
||||
|
||||
for(i = 0; i < visitors.length; i++) {
|
||||
for (i = 0; i < visitors.length; i++) {
|
||||
visitors[i].run(evaldRoot);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ var Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike)
|
||||
this.index = index;
|
||||
this.mapLines = mapLines;
|
||||
this.currentFileInfo = currentFileInfo;
|
||||
this.rulesetLike = (typeof rulesetLike === 'undefined')? false : rulesetLike;
|
||||
this.rulesetLike = (typeof rulesetLike === 'undefined') ? false : rulesetLike;
|
||||
};
|
||||
Anonymous.prototype = new Node();
|
||||
Anonymous.prototype.type = "Anonymous";
|
||||
|
||||
@@ -25,4 +25,3 @@ Assignment.prototype.genCSS = function (context, output) {
|
||||
}
|
||||
};
|
||||
module.exports = Assignment;
|
||||
|
||||
|
||||
@@ -18,13 +18,11 @@ Call.prototype.accept = function (visitor) {
|
||||
};
|
||||
//
|
||||
// When evaluating a function call,
|
||||
// we either find the function in `less.functions` [1],
|
||||
// we either find the function in the functionRegistry,
|
||||
// in which case we call it, passing the evaluated arguments,
|
||||
// if this returns null or we cannot find the function, we
|
||||
// simply print it out as it appeared originally [2].
|
||||
//
|
||||
// The *functions.js* file contains the built-in functions.
|
||||
//
|
||||
// The reason why we evaluate the arguments, is in the case where
|
||||
// we try to pass a variable to a function, like: `saturate(@color)`.
|
||||
// The function should receive the value, not the variable.
|
||||
@@ -52,7 +50,7 @@ Call.prototype.eval = function (context) {
|
||||
Call.prototype.genCSS = function (context, output) {
|
||||
output.add(this.name + "(", this.currentFileInfo, this.index);
|
||||
|
||||
for(var i = 0; i < this.args.length; i++) {
|
||||
for (var i = 0; i < this.args.length; i++) {
|
||||
this.args[i].genCSS(context, output);
|
||||
if (i + 1 < this.args.length) {
|
||||
output.add(", ");
|
||||
|
||||
@@ -22,7 +22,7 @@ var Color = function (rgb, a) {
|
||||
return parseInt(c + c, 16);
|
||||
});
|
||||
}
|
||||
this.alpha = typeof(a) === 'number' ? a : 1;
|
||||
this.alpha = typeof a === 'number' ? a : 1;
|
||||
};
|
||||
|
||||
Color.prototype = new Node();
|
||||
@@ -56,11 +56,11 @@ Color.prototype.genCSS = function (context, output) {
|
||||
Color.prototype.toCSS = function (context, doNotCompress) {
|
||||
var compress = context && context.compress && !doNotCompress, color, alpha;
|
||||
|
||||
// `keyword` is set if this color was originally
|
||||
// `value` is set if this color was originally
|
||||
// converted from a named color string so we need
|
||||
// to respect this and try to output named color too.
|
||||
if (this.keyword) {
|
||||
return this.keyword;
|
||||
if (this.value) {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
// If we have some transparency, the only way to represent it
|
||||
@@ -149,7 +149,7 @@ Color.prototype.toHSV = function () {
|
||||
if (max === min) {
|
||||
h = 0;
|
||||
} else {
|
||||
switch(max){
|
||||
switch(max) {
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
@@ -179,7 +179,7 @@ Color.fromKeyword = function(keyword) {
|
||||
}
|
||||
|
||||
if (c) {
|
||||
c.keyword = keyword;
|
||||
c.value = keyword;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,13 +20,17 @@ Condition.prototype.eval = function (context) {
|
||||
case 'or': return a || b;
|
||||
default:
|
||||
switch (Node.compare(a, b)) {
|
||||
case -1: return op === '<' || op === '=<' || op === '<=';
|
||||
case 0: return op === '=' || op === '>=' || op === '=<' || op === '<=';
|
||||
case 1: return op === '>' || op === '>=';
|
||||
default: return false;
|
||||
case -1:
|
||||
return op === '<' || op === '=<' || op === '<=';
|
||||
case 0:
|
||||
return op === '=' || op === '>=' || op === '=<' || op === '<=';
|
||||
case 1:
|
||||
return op === '>' || op === '>=';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}) (this.op, this.lvalue.eval(context), this.rvalue.eval(context));
|
||||
})(this.op, this.lvalue.eval(context), this.rvalue.eval(context));
|
||||
|
||||
return this.negate ? !result : result;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var debugInfo = function(context, ctx, lineSeperator) {
|
||||
var result="";
|
||||
var debugInfo = function(context, ctx, lineSeparator) {
|
||||
var result = "";
|
||||
if (context.dumpLineNumbers && !context.compress) {
|
||||
switch(context.dumpLineNumbers) {
|
||||
case 'comments':
|
||||
@@ -9,7 +9,7 @@ var debugInfo = function(context, ctx, lineSeperator) {
|
||||
result = debugInfo.asMediaQuery(ctx);
|
||||
break;
|
||||
case 'all':
|
||||
result = debugInfo.asComment(ctx) + (lineSeperator || "") + debugInfo.asMediaQuery(ctx);
|
||||
result = debugInfo.asComment(ctx) + (lineSeparator || "") + debugInfo.asMediaQuery(ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,12 @@ debugInfo.asComment = function(ctx) {
|
||||
};
|
||||
|
||||
debugInfo.asMediaQuery = function(ctx) {
|
||||
var filenameWithProtocol = ctx.debugInfo.fileName;
|
||||
if (!/^[a-z]+:\/\//i.test(filenameWithProtocol)) {
|
||||
filenameWithProtocol = 'file://' + filenameWithProtocol;
|
||||
}
|
||||
return '@media -sass-debug-info{filename{font-family:' +
|
||||
('file://' + ctx.debugInfo.fileName).replace(/([.:\/\\])/g, function (a) {
|
||||
filenameWithProtocol.replace(/([.:\/\\])/g, function (a) {
|
||||
if (a == '\\') {
|
||||
a = '\/';
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Dimension.prototype.toColor = function () {
|
||||
};
|
||||
Dimension.prototype.genCSS = function (context, output) {
|
||||
if ((context && context.strictUnits) && !this.unit.isSingular()) {
|
||||
throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());
|
||||
throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: " + this.unit.toString());
|
||||
}
|
||||
|
||||
var value = this.fround(context, this.value),
|
||||
@@ -70,9 +70,9 @@ Dimension.prototype.operate = function (context, op, other) {
|
||||
} else {
|
||||
other = other.convertTo(this.unit.usedUnits());
|
||||
|
||||
if(context.strictUnits && other.unit.toString() !== unit.toString()) {
|
||||
throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() +
|
||||
"' and '" + other.unit.toString() + "'.");
|
||||
if (context.strictUnits && other.unit.toString() !== unit.toString()) {
|
||||
throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() +
|
||||
"' and '" + other.unit.toString() + "'.");
|
||||
}
|
||||
|
||||
value = this._operate(context, op, this.value, other.value);
|
||||
@@ -116,7 +116,7 @@ Dimension.prototype.convertTo = function (conversions) {
|
||||
i, groupName, group, targetUnit, derivedConversions = {}, applyUnit;
|
||||
|
||||
if (typeof conversions === 'string') {
|
||||
for(i in unitConversions) {
|
||||
for (i in unitConversions) {
|
||||
if (unitConversions[i].hasOwnProperty(conversions)) {
|
||||
derivedConversions = {};
|
||||
derivedConversions[i] = conversions;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var Node = require("./node"),
|
||||
Ruleset = require("./ruleset");
|
||||
|
||||
var Directive = function (name, value, rules, index, currentFileInfo, debugInfo) {
|
||||
var Directive = function (name, value, rules, index, currentFileInfo, debugInfo, isReferenced) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
if (rules) {
|
||||
@@ -11,6 +11,7 @@ var Directive = function (name, value, rules, index, currentFileInfo, debugInfo)
|
||||
this.index = index;
|
||||
this.currentFileInfo = currentFileInfo;
|
||||
this.debugInfo = debugInfo;
|
||||
this.isReferenced = isReferenced;
|
||||
};
|
||||
|
||||
Directive.prototype = new Node();
|
||||
@@ -38,7 +39,10 @@ Directive.prototype.genCSS = function (context, output) {
|
||||
value.genCSS(context, output);
|
||||
}
|
||||
if (rules) {
|
||||
this.outputRuleset(context, output, [rules]);
|
||||
if (rules.type === "Ruleset") {
|
||||
rules = [rules];
|
||||
}
|
||||
this.outputRuleset(context, output, rules);
|
||||
} else {
|
||||
output.add(';');
|
||||
}
|
||||
@@ -53,11 +57,23 @@ Directive.prototype.eval = function (context) {
|
||||
rules.root = true;
|
||||
}
|
||||
return new Directive(this.name, value, rules,
|
||||
this.index, this.currentFileInfo, this.debugInfo);
|
||||
this.index, this.currentFileInfo, this.debugInfo, this.isReferenced);
|
||||
};
|
||||
Directive.prototype.variable = function (name) {
|
||||
if (this.rules) {
|
||||
return Ruleset.prototype.variable.call(this.rules, name);
|
||||
}
|
||||
};
|
||||
Directive.prototype.find = function () {
|
||||
if (this.rules) {
|
||||
return Ruleset.prototype.find.apply(this.rules, arguments);
|
||||
}
|
||||
};
|
||||
Directive.prototype.rulesets = function () {
|
||||
if (this.rules) {
|
||||
return Ruleset.prototype.rulesets.apply(this.rules);
|
||||
}
|
||||
};
|
||||
Directive.prototype.variable = function (name) { if (this.rules) return Ruleset.prototype.variable.call(this.rules, name); };
|
||||
Directive.prototype.find = function () { if (this.rules) return Ruleset.prototype.find.apply(this.rules, arguments); };
|
||||
Directive.prototype.rulesets = function () { if (this.rules) return Ruleset.prototype.rulesets.apply(this.rules); };
|
||||
Directive.prototype.markReferenced = function () {
|
||||
var i, rules;
|
||||
this.isReferenced = true;
|
||||
@@ -70,6 +86,9 @@ Directive.prototype.markReferenced = function () {
|
||||
}
|
||||
}
|
||||
};
|
||||
Directive.prototype.getIsReferenced = function () {
|
||||
return !this.currentFileInfo || !this.currentFileInfo.reference || this.isReferenced;
|
||||
};
|
||||
Directive.prototype.outputRuleset = function (context, output, rules) {
|
||||
var ruleCnt = rules.length, i;
|
||||
context.tabLevel = (context.tabLevel | 0) + 1;
|
||||
|
||||
@@ -6,7 +6,7 @@ var Element = function (combinator, value, index, currentFileInfo) {
|
||||
this.combinator = combinator instanceof Combinator ?
|
||||
combinator : new Combinator(combinator);
|
||||
|
||||
if (typeof(value) === 'string') {
|
||||
if (typeof value === 'string') {
|
||||
this.value = value.trim();
|
||||
} else if (value) {
|
||||
this.value = value;
|
||||
|
||||
@@ -5,7 +5,7 @@ var Node = require("./node"),
|
||||
var Expression = function (value) {
|
||||
this.value = value;
|
||||
if (!value) {
|
||||
throw new Error("Expression requires a array parameter");
|
||||
throw new Error("Expression requires an array parameter");
|
||||
}
|
||||
};
|
||||
Expression.prototype = new Node();
|
||||
@@ -41,7 +41,7 @@ Expression.prototype.eval = function (context) {
|
||||
return returnValue;
|
||||
};
|
||||
Expression.prototype.genCSS = function (context, output) {
|
||||
for(var i = 0; i < this.value.length; i++) {
|
||||
for (var i = 0; i < this.value.length; i++) {
|
||||
this.value[i].genCSS(context, output);
|
||||
if (i + 1 < this.value.length) {
|
||||
output.add(" ");
|
||||
|
||||
@@ -11,11 +11,11 @@ var Extend = function Extend(selector, option, index) {
|
||||
case "all":
|
||||
this.allowBefore = true;
|
||||
this.allowAfter = true;
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
this.allowBefore = false;
|
||||
this.allowAfter = false;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
};
|
||||
Extend.next_id = 0;
|
||||
@@ -36,7 +36,7 @@ Extend.prototype.findSelfSelectors = function (selectors) {
|
||||
i,
|
||||
selectorElements;
|
||||
|
||||
for(i = 0; i < selectors.length; i++) {
|
||||
for (i = 0; i < selectors.length; i++) {
|
||||
selectorElements = selectors[i].elements;
|
||||
// duplicate the logic in genCSS function inside the selector node.
|
||||
// future TODO - move both logics into the selector joiner visitor
|
||||
|
||||
@@ -28,7 +28,7 @@ var Import = function (path, features, options, index, currentFileInfo) {
|
||||
this.css = !this.options.less || this.options.inline;
|
||||
} else {
|
||||
var pathValue = this.getPath();
|
||||
if (pathValue && /css([\?;].*)?$/.test(pathValue)) {
|
||||
if (pathValue && /[#\.\&\?\/]css([\?;].*)?$/.test(pathValue)) {
|
||||
this.css = true;
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,17 @@ Import.prototype.getPath = function () {
|
||||
}
|
||||
return null;
|
||||
};
|
||||
Import.prototype.isVariableImport = function () {
|
||||
var path = this.path;
|
||||
if (path instanceof URL) {
|
||||
path = path.value;
|
||||
}
|
||||
if (path instanceof Quoted) {
|
||||
return path.containsVariables();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
Import.prototype.evalForImport = function (context) {
|
||||
var path = this.path;
|
||||
if (path instanceof URL) {
|
||||
@@ -89,7 +100,7 @@ Import.prototype.evalPath = function (context) {
|
||||
var pathValue = path.value;
|
||||
// Add the base path if the import is relative
|
||||
if (pathValue && context.isPathRelative(pathValue)) {
|
||||
path.value = rootpath +pathValue;
|
||||
path.value = rootpath + pathValue;
|
||||
}
|
||||
}
|
||||
path.value = context.normalizePath(path.value);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var tree = {};
|
||||
|
||||
tree.Node = require('./node');
|
||||
tree.Alpha = require('./alpha');
|
||||
tree.Color = require('./color');
|
||||
tree.Directive = require('./directive');
|
||||
|
||||
@@ -14,9 +14,9 @@ JavaScript.prototype.type = "JavaScript";
|
||||
JavaScript.prototype.eval = function(context) {
|
||||
var result = this.evaluateJavaScript(this.expression, context);
|
||||
|
||||
if (typeof(result) === 'number') {
|
||||
if (typeof result === 'number') {
|
||||
return new Dimension(result);
|
||||
} else if (typeof(result) === 'string') {
|
||||
} else if (typeof result === 'string') {
|
||||
return new Quoted('"' + result + '"', result, this.escaped, this.index);
|
||||
} else if (Array.isArray(result)) {
|
||||
return new Anonymous(result.join(', '));
|
||||
|
||||
@@ -39,7 +39,7 @@ Media.prototype.eval = function (context) {
|
||||
}
|
||||
|
||||
var media = new Media(null, [], this.index, this.currentFileInfo);
|
||||
if(this.debugInfo) {
|
||||
if (this.debugInfo) {
|
||||
this.rules[0].debugInfo = this.debugInfo;
|
||||
media.debugInfo = this.debugInfo;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ Media.prototype.evalNested = function (context) {
|
||||
return fragment.toCSS ? fragment : new Anonymous(fragment);
|
||||
});
|
||||
|
||||
for(i = path.length - 1; i > 0; i--) {
|
||||
for (i = path.length - 1; i > 0; i--) {
|
||||
path.splice(i, 0, new Anonymous("and"));
|
||||
}
|
||||
|
||||
@@ -138,24 +138,25 @@ Media.prototype.evalNested = function (context) {
|
||||
return new Ruleset([], []);
|
||||
};
|
||||
Media.prototype.permute = function (arr) {
|
||||
if (arr.length === 0) {
|
||||
return [];
|
||||
} else if (arr.length === 1) {
|
||||
return arr[0];
|
||||
} else {
|
||||
var result = [];
|
||||
var rest = this.permute(arr.slice(1));
|
||||
for (var i = 0; i < rest.length; i++) {
|
||||
for (var j = 0; j < arr[0].length; j++) {
|
||||
result.push([arr[0][j]].concat(rest[i]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (arr.length === 0) {
|
||||
return [];
|
||||
} else if (arr.length === 1) {
|
||||
return arr[0];
|
||||
} else {
|
||||
var result = [];
|
||||
var rest = this.permute(arr.slice(1));
|
||||
for (var i = 0; i < rest.length; i++) {
|
||||
for (var j = 0; j < arr[0].length; j++) {
|
||||
result.push([arr[0][j]].concat(rest[i]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
Media.prototype.bubbleSelectors = function (selectors) {
|
||||
if (!selectors)
|
||||
return;
|
||||
this.rules = [new Ruleset(selectors.slice(0), [this.rules[0]])];
|
||||
if (!selectors) {
|
||||
return;
|
||||
}
|
||||
this.rules = [new Ruleset(selectors.slice(0), [this.rules[0]])];
|
||||
};
|
||||
module.exports = Media;
|
||||
|
||||
@@ -22,7 +22,7 @@ MixinCall.prototype.accept = function (visitor) {
|
||||
};
|
||||
MixinCall.prototype.eval = function (context) {
|
||||
var mixins, mixin, mixinPath, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule,
|
||||
candidates = [], candidate, conditionResult = [], defaultResult, defFalseEitherCase=-1,
|
||||
candidates = [], candidate, conditionResult = [], defaultResult, defFalseEitherCase = -1,
|
||||
defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset, noArgumentsFilter;
|
||||
|
||||
function calcDefGroup(mixin, mixinPath) {
|
||||
@@ -31,7 +31,7 @@ MixinCall.prototype.eval = function (context) {
|
||||
for (f = 0; f < 2; f++) {
|
||||
conditionResult[f] = true;
|
||||
defaultFunc.value(f);
|
||||
for(p = 0; p < mixinPath.length && conditionResult[f]; p++) {
|
||||
for (p = 0; p < mixinPath.length && conditionResult[f]; p++) {
|
||||
namespace = mixinPath[p];
|
||||
if (namespace.matchCondition) {
|
||||
conditionResult[f] = conditionResult[f] && namespace.matchCondition(null, context);
|
||||
@@ -71,7 +71,7 @@ MixinCall.prototype.eval = function (context) {
|
||||
mixin = mixins[m].rule;
|
||||
mixinPath = mixins[m].path;
|
||||
isRecursive = false;
|
||||
for(f = 0; f < context.frames.length; f++) {
|
||||
for (f = 0; f < context.frames.length; f++) {
|
||||
if ((!(mixin instanceof MixinDefinition)) && mixin === (context.frames[f].originalRuleset || context.frames[f])) {
|
||||
isRecursive = true;
|
||||
break;
|
||||
@@ -84,7 +84,7 @@ MixinCall.prototype.eval = function (context) {
|
||||
if (mixin.matchArgs(args, context)) {
|
||||
candidate = {mixin: mixin, group: calcDefGroup(mixin, mixinPath)};
|
||||
|
||||
if (candidate.group!==defFalseEitherCase) {
|
||||
if (candidate.group !== defFalseEitherCase) {
|
||||
candidates.push(candidate);
|
||||
}
|
||||
|
||||
@@ -105,8 +105,7 @@ MixinCall.prototype.eval = function (context) {
|
||||
defaultResult = defTrue;
|
||||
if ((count[defTrue] + count[defFalse]) > 1) {
|
||||
throw { type: 'Runtime',
|
||||
message: 'Ambiguous use of `default()` found when matching for `'
|
||||
+ this.format(args) + '`',
|
||||
message: 'Ambiguous use of `default()` found when matching for `' + this.format(args) + '`',
|
||||
index: this.index, filename: this.currentFileInfo.filename };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,12 @@ var Definition = function (name, params, rules, condition, variadic, frames) {
|
||||
this.rules = rules;
|
||||
this._lookups = {};
|
||||
this.required = params.reduce(function (count, p) {
|
||||
if (!p.name || (p.name && !p.value)) { return count + 1; }
|
||||
else { return count; }
|
||||
if (!p.name || (p.name && !p.value)) {
|
||||
return count + 1;
|
||||
}
|
||||
else {
|
||||
return count;
|
||||
}
|
||||
}, 0);
|
||||
this.frames = frames;
|
||||
};
|
||||
@@ -45,11 +49,11 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
|
||||
args = args.slice(0);
|
||||
argsLength = args.length;
|
||||
|
||||
for(i = 0; i < argsLength; i++) {
|
||||
for (i = 0; i < argsLength; i++) {
|
||||
arg = args[i];
|
||||
if (name = (arg && arg.name)) {
|
||||
isNamedFound = false;
|
||||
for(j = 0; j < params.length; j++) {
|
||||
for (j = 0; j < params.length; j++) {
|
||||
if (!evaldArguments[j] && name === params[j].name) {
|
||||
evaldArguments[j] = arg.value.eval(context);
|
||||
frame.prependRule(new Rule(name, arg.value.eval(context)));
|
||||
@@ -108,6 +112,17 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
|
||||
|
||||
return frame;
|
||||
};
|
||||
Definition.prototype.makeImportant = function() {
|
||||
var rules = !this.rules ? this.rules : this.rules.map(function (r) {
|
||||
if (r.makeImportant) {
|
||||
return r.makeImportant(true);
|
||||
} else {
|
||||
return r;
|
||||
}
|
||||
});
|
||||
var result = new Definition (this.name, this.params, rules, this.condition, this.variadic, this.frames);
|
||||
return result;
|
||||
};
|
||||
Definition.prototype.eval = function (context) {
|
||||
return new Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || context.frames.slice(0));
|
||||
};
|
||||
@@ -125,16 +140,17 @@ Definition.prototype.evalCall = function (context, args, important) {
|
||||
ruleset.originalRuleset = this;
|
||||
ruleset = ruleset.eval(new contexts.Eval(context, [this, frame].concat(mixinFrames)));
|
||||
if (important) {
|
||||
ruleset = this.makeImportant.apply(ruleset);
|
||||
ruleset = ruleset.makeImportant();
|
||||
}
|
||||
return ruleset;
|
||||
};
|
||||
Definition.prototype.matchCondition = function (args, context) {
|
||||
if (this.condition && !this.condition.eval(
|
||||
new contexts.Eval(context,
|
||||
[this.evalParams(context, new contexts.Eval(context, this.frames ? this.frames.concat(context.frames) : context.frames), args, [])] // the parameter variables
|
||||
.concat(this.frames) // the parent namespace/mixin frames
|
||||
.concat(context.frames)))) { // the current environment frames
|
||||
[this.evalParams(context, /* the parameter variables*/
|
||||
new contexts.Eval(context, this.frames ? this.frames.concat(context.frames) : context.frames), args, [])]
|
||||
.concat(this.frames) // the parent namespace/mixin frames
|
||||
.concat(context.frames)))) { // the current environment frames
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -143,10 +159,16 @@ Definition.prototype.matchArgs = function (args, context) {
|
||||
var argsLength = (args && args.length) || 0, len;
|
||||
|
||||
if (! this.variadic) {
|
||||
if (argsLength < this.required) { return false; }
|
||||
if (argsLength > this.params.length) { return false; }
|
||||
if (argsLength < this.required) {
|
||||
return false;
|
||||
}
|
||||
if (argsLength > this.params.length) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (argsLength < (this.required - 1)) { return false; }
|
||||
if (argsLength < (this.required - 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
len = Math.min(argsLength, this.arity);
|
||||
|
||||
@@ -3,7 +3,7 @@ var Node = require("./node"),
|
||||
Variable = require("./variable");
|
||||
|
||||
var Quoted = function (str, content, escaped, index, currentFileInfo) {
|
||||
this.escaped = escaped;
|
||||
this.escaped = (escaped == null) ? true : escaped;
|
||||
this.value = content || '';
|
||||
this.quote = str.charAt(0);
|
||||
this.index = index;
|
||||
@@ -20,6 +20,9 @@ Quoted.prototype.genCSS = function (context, output) {
|
||||
output.add(this.quote);
|
||||
}
|
||||
};
|
||||
Quoted.prototype.containsVariables = function() {
|
||||
return this.value.match(/(`([^`]+)`)|@\{([\w-]+)\}/);
|
||||
};
|
||||
Quoted.prototype.eval = function (context) {
|
||||
var that = this, value = this.value;
|
||||
var javascriptReplacement = function (_, exp) {
|
||||
@@ -32,9 +35,9 @@ Quoted.prototype.eval = function (context) {
|
||||
function iterativeReplace(value, regexp, replacementFnc) {
|
||||
var evaluatedValue = value;
|
||||
do {
|
||||
value = evaluatedValue;
|
||||
evaluatedValue = value.replace(regexp, replacementFnc);
|
||||
} while (value!==evaluatedValue);
|
||||
value = evaluatedValue;
|
||||
evaluatedValue = value.replace(regexp, replacementFnc);
|
||||
} while (value !== evaluatedValue);
|
||||
return evaluatedValue;
|
||||
}
|
||||
value = iterativeReplace(value, /`([^`]+)`/g, javascriptReplacement);
|
||||
|
||||
@@ -42,26 +42,31 @@ Rule.prototype.eval = function (context) {
|
||||
if (typeof name !== "string") {
|
||||
// expand 'primitive' name directly to get
|
||||
// things faster (~10% for benchmark.less):
|
||||
name = (name.length === 1)
|
||||
&& (name[0] instanceof Keyword)
|
||||
? name[0].value : evalName(context, name);
|
||||
variable = false; // never treat expanded interpolation as new variable name
|
||||
name = (name.length === 1) && (name[0] instanceof Keyword) ?
|
||||
name[0].value : evalName(context, name);
|
||||
variable = false; // never treat expanded interpolation as new variable name
|
||||
}
|
||||
if (name === "font" && !context.strictMath) {
|
||||
strictMathBypass = true;
|
||||
context.strictMath = true;
|
||||
}
|
||||
try {
|
||||
context.importantScope.push({});
|
||||
evaldValue = this.value.eval(context);
|
||||
|
||||
if (!this.variable && evaldValue.type === "DetachedRuleset") {
|
||||
throw { message: "Rulesets cannot be evaluated on a property.",
|
||||
index: this.index, filename: this.currentFileInfo.filename };
|
||||
}
|
||||
var important = this.important,
|
||||
importantResult = context.importantScope.pop();
|
||||
if (!important && importantResult.important) {
|
||||
important = importantResult.important;
|
||||
}
|
||||
|
||||
return new Rule(name,
|
||||
evaldValue,
|
||||
this.important,
|
||||
important,
|
||||
this.merge,
|
||||
this.index, this.currentFileInfo, this.inline,
|
||||
variable);
|
||||
|
||||
@@ -2,6 +2,7 @@ var Node = require("./node"),
|
||||
Rule = require("./rule"),
|
||||
Selector = require("./selector"),
|
||||
Element = require("./element"),
|
||||
Paren = require("./paren"),
|
||||
contexts = require("../contexts"),
|
||||
defaultFunc = require("../functions/default"),
|
||||
getDebugInfo = require("./debug-info");
|
||||
@@ -57,7 +58,7 @@ Ruleset.prototype.eval = function (context) {
|
||||
ruleset.firstRoot = this.firstRoot;
|
||||
ruleset.allowImports = this.allowImports;
|
||||
|
||||
if(this.debugInfo) {
|
||||
if (this.debugInfo) {
|
||||
ruleset.debugInfo = this.debugInfo;
|
||||
}
|
||||
|
||||
@@ -107,7 +108,7 @@ Ruleset.prototype.eval = function (context) {
|
||||
});
|
||||
rsRules.splice.apply(rsRules, [i, 1].concat(rules));
|
||||
rsRuleCnt += rules.length - 1;
|
||||
i += rules.length-1;
|
||||
i += rules.length - 1;
|
||||
ruleset.resetCache();
|
||||
} else if (rsRules[i].type === "RulesetCall") {
|
||||
/*jshint loopfunc:true */
|
||||
@@ -120,7 +121,7 @@ Ruleset.prototype.eval = function (context) {
|
||||
});
|
||||
rsRules.splice.apply(rsRules, [i, 1].concat(rules));
|
||||
rsRuleCnt += rules.length - 1;
|
||||
i += rules.length-1;
|
||||
i += rules.length - 1;
|
||||
ruleset.resetCache();
|
||||
}
|
||||
}
|
||||
@@ -142,7 +143,7 @@ Ruleset.prototype.eval = function (context) {
|
||||
if (rule.selectors[0].isJustParentSelector()) {
|
||||
rsRules.splice(i--, 1);
|
||||
|
||||
for(var j = 0; j < rule.rules.length; j++) {
|
||||
for (var j = 0; j < rule.rules.length; j++) {
|
||||
subRule = rule.rules[j];
|
||||
if (!(subRule instanceof Rule) || !subRule.variable) {
|
||||
rsRules.splice(++i, 0, subRule);
|
||||
@@ -173,7 +174,7 @@ Ruleset.prototype.evalImports = function(context) {
|
||||
importRules = rules[i].eval(context);
|
||||
if (importRules && importRules.length) {
|
||||
rules.splice.apply(rules, [i, 1].concat(importRules));
|
||||
i+= importRules.length-1;
|
||||
i+= importRules.length - 1;
|
||||
} else {
|
||||
rules.splice(i, 1, importRules);
|
||||
}
|
||||
@@ -182,20 +183,22 @@ Ruleset.prototype.evalImports = function(context) {
|
||||
}
|
||||
};
|
||||
Ruleset.prototype.makeImportant = function() {
|
||||
return new Ruleset(this.selectors, this.rules.map(function (r) {
|
||||
if (r.makeImportant) {
|
||||
return r.makeImportant();
|
||||
} else {
|
||||
return r;
|
||||
}
|
||||
}), this.strictImports);
|
||||
var result = new Ruleset(this.selectors, this.rules.map(function (r) {
|
||||
if (r.makeImportant) {
|
||||
return r.makeImportant();
|
||||
} else {
|
||||
return r;
|
||||
}
|
||||
}), this.strictImports);
|
||||
|
||||
return result;
|
||||
};
|
||||
Ruleset.prototype.matchArgs = function (args) {
|
||||
return !args || args.length === 0;
|
||||
};
|
||||
// lets you call a css selector with a guard
|
||||
Ruleset.prototype.matchCondition = function (args, context) {
|
||||
var lastSelector = this.selectors[this.selectors.length-1];
|
||||
var lastSelector = this.selectors[this.selectors.length - 1];
|
||||
if (!lastSelector.evaldCondition) {
|
||||
return false;
|
||||
}
|
||||
@@ -218,6 +221,17 @@ Ruleset.prototype.variables = function () {
|
||||
if (r instanceof Rule && r.variable === true) {
|
||||
hash[r.name] = r;
|
||||
}
|
||||
// when evaluating variables in an import statement, imports have not been eval'd
|
||||
// so we need to go inside import statements.
|
||||
// guard against root being a string (in the case of inlined less)
|
||||
if (r.type === "Import" && r.root && r.root.variables) {
|
||||
var vars = r.root.variables();
|
||||
for (var name in vars) {
|
||||
if (vars.hasOwnProperty(name)) {
|
||||
hash[name] = vars[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}, {});
|
||||
}
|
||||
@@ -243,7 +257,11 @@ Ruleset.prototype.rulesets = function () {
|
||||
};
|
||||
Ruleset.prototype.prependRule = function (rule) {
|
||||
var rules = this.rules;
|
||||
if (rules) { rules.unshift(rule); } else { this.rules = [ rule ]; }
|
||||
if (rules) {
|
||||
rules.unshift(rule);
|
||||
} else {
|
||||
this.rules = [ rule ];
|
||||
}
|
||||
};
|
||||
Ruleset.prototype.find = function (selector, self, filter) {
|
||||
self = self || this;
|
||||
@@ -258,13 +276,13 @@ Ruleset.prototype.find = function (selector, self, filter) {
|
||||
match = selector.match(rule.selectors[j]);
|
||||
if (match) {
|
||||
if (selector.elements.length > match) {
|
||||
if (!filter || filter(rule)) {
|
||||
foundMixins = rule.find(new Selector(selector.elements.slice(match)), self, filter);
|
||||
for (var i = 0; i < foundMixins.length; ++i) {
|
||||
foundMixins[i].path.push(rule);
|
||||
if (!filter || filter(rule)) {
|
||||
foundMixins = rule.find(new Selector(selector.elements.slice(match)), self, filter);
|
||||
for (var i = 0; i < foundMixins.length; ++i) {
|
||||
foundMixins[i].path.push(rule);
|
||||
}
|
||||
Array.prototype.push.apply(rules, foundMixins);
|
||||
}
|
||||
Array.prototype.push.apply(rules, foundMixins);
|
||||
}
|
||||
} else {
|
||||
rules.push({ rule: rule, path: []});
|
||||
}
|
||||
@@ -297,19 +315,17 @@ Ruleset.prototype.genCSS = function (context, output) {
|
||||
sep;
|
||||
|
||||
function isRulesetLikeNode(rule, root) {
|
||||
// if it has nested rules, then it should be treated like a ruleset
|
||||
// medias and comments do not have nested rules, but should be treated like rulesets anyway
|
||||
// some directives and anonymous nodes are ruleset like, others are not
|
||||
if (typeof rule.isRulesetLike === "boolean")
|
||||
{
|
||||
return rule.isRulesetLike;
|
||||
} else if (typeof rule.isRulesetLike === "function")
|
||||
{
|
||||
return rule.isRulesetLike(root);
|
||||
}
|
||||
// if it has nested rules, then it should be treated like a ruleset
|
||||
// medias and comments do not have nested rules, but should be treated like rulesets anyway
|
||||
// some directives and anonymous nodes are ruleset like, others are not
|
||||
if (typeof rule.isRulesetLike === "boolean") {
|
||||
return rule.isRulesetLike;
|
||||
} else if (typeof rule.isRulesetLike === "function") {
|
||||
return rule.isRulesetLike(root);
|
||||
}
|
||||
|
||||
//anything else is assumed to be a rule
|
||||
return false;
|
||||
//anything else is assumed to be a rule
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.rules.length; i++) {
|
||||
@@ -403,189 +419,296 @@ Ruleset.prototype.genCSS = function (context, output) {
|
||||
}
|
||||
};
|
||||
Ruleset.prototype.markReferenced = function () {
|
||||
if (!this.selectors) {
|
||||
return;
|
||||
var s;
|
||||
if (this.selectors) {
|
||||
for (s = 0; s < this.selectors.length; s++) {
|
||||
this.selectors[s].markReferenced();
|
||||
}
|
||||
}
|
||||
for (var s = 0; s < this.selectors.length; s++) {
|
||||
this.selectors[s].markReferenced();
|
||||
|
||||
if (this.rules) {
|
||||
for (s = 0; s < this.rules.length; s++) {
|
||||
if (this.rules[s].markReferenced) {
|
||||
this.rules[s].markReferenced();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ruleset.prototype.getIsReferenced = function() {
|
||||
var i, j, path, selector;
|
||||
|
||||
if (this.paths) {
|
||||
for (i = 0; i < this.paths.length; i++) {
|
||||
path = this.paths[i];
|
||||
for (j = 0; j < path.length; j++) {
|
||||
if (path[j].getIsReferenced && path[j].getIsReferenced()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectors) {
|
||||
for (i = 0; i < this.selectors.length; i++) {
|
||||
selector = this.selectors[i];
|
||||
if (selector.getIsReferenced && selector.getIsReferenced()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Ruleset.prototype.joinSelectors = function (paths, context, selectors) {
|
||||
for (var s = 0; s < selectors.length; s++) {
|
||||
this.joinSelector(paths, context, selectors[s]);
|
||||
}
|
||||
};
|
||||
|
||||
Ruleset.prototype.joinSelector = function (paths, context, selector) {
|
||||
|
||||
var i, j, k,
|
||||
hasParentSelector, newSelectors, el, sel, parentSel,
|
||||
newSelectorPath, afterParentJoin, newJoinedSelector,
|
||||
newJoinedSelectorEmpty, lastSelector, currentElements,
|
||||
selectorsMultiplied;
|
||||
|
||||
for (i = 0; i < selector.elements.length; i++) {
|
||||
el = selector.elements[i];
|
||||
if (el.value === '&') {
|
||||
hasParentSelector = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasParentSelector) {
|
||||
if (context.length > 0) {
|
||||
for (i = 0; i < context.length; i++) {
|
||||
paths.push(context[i].concat(selector));
|
||||
}
|
||||
}
|
||||
else {
|
||||
paths.push([selector]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The paths are [[Selector]]
|
||||
// The first list is a list of comma seperated selectors
|
||||
// The inner list is a list of inheritance seperated selectors
|
||||
// e.g.
|
||||
// .a, .b {
|
||||
// .c {
|
||||
// }
|
||||
// }
|
||||
// == [[.a] [.c]] [[.b] [.c]]
|
||||
//
|
||||
|
||||
// the elements from the current selector so far
|
||||
currentElements = [];
|
||||
// the current list of new selectors to add to the path.
|
||||
// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
|
||||
// by the parents
|
||||
newSelectors = [[]];
|
||||
|
||||
for (i = 0; i < selector.elements.length; i++) {
|
||||
el = selector.elements[i];
|
||||
// non parent reference elements just get added
|
||||
if (el.value !== "&") {
|
||||
currentElements.push(el);
|
||||
function createParenthesis(elementsToPak, originalElement) {
|
||||
var replacementParen, j;
|
||||
if (elementsToPak.length === 0) {
|
||||
replacementParen = new Paren(elementsToPak[0]);
|
||||
} else {
|
||||
// the new list of selectors to add
|
||||
selectorsMultiplied = [];
|
||||
var insideParent = [];
|
||||
for (j = 0; j < elementsToPak.length; j++) {
|
||||
insideParent.push(new Element(null, elementsToPak[j], originalElement.index, originalElement.currentFileInfo));
|
||||
}
|
||||
replacementParen = new Paren(new Selector(insideParent));
|
||||
}
|
||||
return replacementParen;
|
||||
}
|
||||
|
||||
// merge the current list of non parent selector elements
|
||||
// on to the current list of selectors to add
|
||||
if (currentElements.length > 0) {
|
||||
this.mergeElementsOnToSelectors(currentElements, newSelectors);
|
||||
function createSelector(containedElement, originalElement) {
|
||||
var element, selector;
|
||||
element = new Element(null, containedElement, originalElement.index, originalElement.currentFileInfo);
|
||||
selector = new Selector([element]);
|
||||
return selector;
|
||||
}
|
||||
|
||||
// replace all parent selectors inside `inSelector` by content of `context` array
|
||||
// resulting selectors are returned inside `paths` array
|
||||
// returns true if `inSelector` contained at least one parent selector
|
||||
function replaceParentSelector(paths, context, inSelector) {
|
||||
// The paths are [[Selector]]
|
||||
// The first list is a list of comma separated selectors
|
||||
// The inner list is a list of inheritance separated selectors
|
||||
// e.g.
|
||||
// .a, .b {
|
||||
// .c {
|
||||
// }
|
||||
// }
|
||||
// == [[.a] [.c]] [[.b] [.c]]
|
||||
//
|
||||
var i, j, k, currentElements, newSelectors, selectorsMultiplied, sel, el, hadParentSelector = false, length, lastSelector;
|
||||
function findNestedSelector(element) {
|
||||
var maybeSelector;
|
||||
if (element.value.type !== 'Paren') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// loop through our current selectors
|
||||
for (j = 0; j < newSelectors.length; j++) {
|
||||
sel = newSelectors[j];
|
||||
// if we don't have any parent paths, the & might be in a mixin so that it can be used
|
||||
// whether there are parents or not
|
||||
if (context.length === 0) {
|
||||
// the combinator used on el should now be applied to the next element instead so that
|
||||
// it is not lost
|
||||
if (sel.length > 0) {
|
||||
sel[0].elements = sel[0].elements.slice(0);
|
||||
sel[0].elements.push(new Element(el.combinator, '', el.index, el.currentFileInfo));
|
||||
maybeSelector = element.value.value;
|
||||
if (maybeSelector.type !== 'Selector') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return maybeSelector;
|
||||
}
|
||||
|
||||
// the elements from the current selector so far
|
||||
currentElements = [];
|
||||
// the current list of new selectors to add to the path.
|
||||
// We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
|
||||
// by the parents
|
||||
newSelectors = [
|
||||
[]
|
||||
];
|
||||
|
||||
for (i = 0; i < inSelector.elements.length; i++) {
|
||||
el = inSelector.elements[i];
|
||||
// non parent reference elements just get added
|
||||
if (el.value !== "&") {
|
||||
var nestedSelector = findNestedSelector(el);
|
||||
if (nestedSelector != null) {
|
||||
// merge the current list of non parent selector elements
|
||||
// on to the current list of selectors to add
|
||||
mergeElementsOnToSelectors(currentElements, newSelectors);
|
||||
|
||||
var nestedPaths = [], replaced, replacedNewSelectors = [];
|
||||
replaced = replaceParentSelector(nestedPaths, context, nestedSelector);
|
||||
hadParentSelector = hadParentSelector || replaced;
|
||||
//the nestedPaths array should have only one member - replaceParentSelector does not multiply selectors
|
||||
for (k = 0; k < nestedPaths.length; k++) {
|
||||
var replacementSelector = createSelector(createParenthesis(nestedPaths[k], el), el);
|
||||
addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector, replacedNewSelectors);
|
||||
}
|
||||
selectorsMultiplied.push(sel);
|
||||
newSelectors = replacedNewSelectors;
|
||||
currentElements = [];
|
||||
|
||||
} else {
|
||||
currentElements.push(el);
|
||||
}
|
||||
else {
|
||||
// and the parent selectors
|
||||
for (k = 0; k < context.length; k++) {
|
||||
parentSel = context[k];
|
||||
// We need to put the current selectors
|
||||
// then join the last selector's elements on to the parents selectors
|
||||
|
||||
// our new selector path
|
||||
newSelectorPath = [];
|
||||
// selectors from the parent after the join
|
||||
afterParentJoin = [];
|
||||
newJoinedSelectorEmpty = true;
|
||||
} else {
|
||||
hadParentSelector = true;
|
||||
// the new list of selectors to add
|
||||
selectorsMultiplied = [];
|
||||
|
||||
//construct the joined selector - if & is the first thing this will be empty,
|
||||
// if not newJoinedSelector will be the last set of elements in the selector
|
||||
// merge the current list of non parent selector elements
|
||||
// on to the current list of selectors to add
|
||||
mergeElementsOnToSelectors(currentElements, newSelectors);
|
||||
|
||||
// loop through our current selectors
|
||||
for (j = 0; j < newSelectors.length; j++) {
|
||||
sel = newSelectors[j];
|
||||
// if we don't have any parent paths, the & might be in a mixin so that it can be used
|
||||
// whether there are parents or not
|
||||
if (context.length === 0) {
|
||||
// the combinator used on el should now be applied to the next element instead so that
|
||||
// it is not lost
|
||||
if (sel.length > 0) {
|
||||
newSelectorPath = sel.slice(0);
|
||||
lastSelector = newSelectorPath.pop();
|
||||
newJoinedSelector = selector.createDerived(lastSelector.elements.slice(0));
|
||||
newJoinedSelectorEmpty = false;
|
||||
sel[0].elements.push(new Element(el.combinator, '', el.index, el.currentFileInfo));
|
||||
}
|
||||
else {
|
||||
newJoinedSelector = selector.createDerived([]);
|
||||
selectorsMultiplied.push(sel);
|
||||
}
|
||||
else {
|
||||
// and the parent selectors
|
||||
for (k = 0; k < context.length; k++) {
|
||||
// We need to put the current selectors
|
||||
// then join the last selector's elements on to the parents selectors
|
||||
var newSelectorPath = addReplacementIntoPath(sel, context[k], el, inSelector);
|
||||
// add that to our new set of selectors
|
||||
selectorsMultiplied.push(newSelectorPath);
|
||||
}
|
||||
|
||||
//put together the parent selectors after the join
|
||||
if (parentSel.length > 1) {
|
||||
afterParentJoin = afterParentJoin.concat(parentSel.slice(1));
|
||||
}
|
||||
|
||||
if (parentSel.length > 0) {
|
||||
newJoinedSelectorEmpty = false;
|
||||
|
||||
// /deep/ is a combinator that is valid without anything in front of it
|
||||
// so if the & does not have a combinator that is "" or " " then
|
||||
// and there is a combinator on the parent, then grab that.
|
||||
// this also allows + a { & .b { .a & { ... though not sure why you would want to do that
|
||||
var combinator = el.combinator,
|
||||
parentEl = parentSel[0].elements[0];
|
||||
if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) {
|
||||
combinator = parentEl.combinator;
|
||||
}
|
||||
// join the elements so far with the first part of the parent
|
||||
newJoinedSelector.elements.push(new Element(combinator, parentEl.value, el.index, el.currentFileInfo));
|
||||
newJoinedSelector.elements = newJoinedSelector.elements.concat(parentSel[0].elements.slice(1));
|
||||
}
|
||||
|
||||
if (!newJoinedSelectorEmpty) {
|
||||
// now add the joined selector
|
||||
newSelectorPath.push(newJoinedSelector);
|
||||
}
|
||||
|
||||
// and the rest of the parent
|
||||
newSelectorPath = newSelectorPath.concat(afterParentJoin);
|
||||
|
||||
// add that to our new set of selectors
|
||||
selectorsMultiplied.push(newSelectorPath);
|
||||
}
|
||||
}
|
||||
|
||||
// our new selectors has been multiplied, so reset the state
|
||||
newSelectors = selectorsMultiplied;
|
||||
currentElements = [];
|
||||
}
|
||||
|
||||
// our new selectors has been multiplied, so reset the state
|
||||
newSelectors = selectorsMultiplied;
|
||||
currentElements = [];
|
||||
}
|
||||
}
|
||||
|
||||
// if we have any elements left over (e.g. .a& .b == .b)
|
||||
// add them on to all the current selectors
|
||||
if (currentElements.length > 0) {
|
||||
this.mergeElementsOnToSelectors(currentElements, newSelectors);
|
||||
}
|
||||
// if we have any elements left over (e.g. .a& .b == .b)
|
||||
// add them on to all the current selectors
|
||||
mergeElementsOnToSelectors(currentElements, newSelectors);
|
||||
|
||||
for (i = 0; i < newSelectors.length; i++) {
|
||||
if (newSelectors[i].length > 0) {
|
||||
paths.push(newSelectors[i]);
|
||||
for (i = 0; i < newSelectors.length; i++) {
|
||||
length = newSelectors[i].length;
|
||||
if (length > 0) {
|
||||
paths.push(newSelectors[i]);
|
||||
lastSelector = newSelectors[i][length - 1];
|
||||
newSelectors[i][length - 1] = lastSelector.createDerived(lastSelector.elements, inSelector.extendList);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ruleset.prototype.mergeElementsOnToSelectors = function(elements, selectors) {
|
||||
var i, sel;
|
||||
|
||||
if (selectors.length === 0) {
|
||||
selectors.push([ new Selector(elements) ]);
|
||||
return;
|
||||
return hadParentSelector;
|
||||
}
|
||||
|
||||
for (i = 0; i < selectors.length; i++) {
|
||||
sel = selectors[i];
|
||||
// joins selector path from `beginningPath` with selector path in `addPath`
|
||||
// `replacedElement` contains element that is being replaced by `addPath`
|
||||
// returns concatenated path
|
||||
function addReplacementIntoPath(beginningPath, addPath, replacedElement, originalSelector) {
|
||||
var newSelectorPath, lastSelector, newJoinedSelector;
|
||||
// our new selector path
|
||||
newSelectorPath = [];
|
||||
|
||||
// if the previous thing in sel is a parent this needs to join on to it
|
||||
if (sel.length > 0) {
|
||||
sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements));
|
||||
//construct the joined selector - if & is the first thing this will be empty,
|
||||
// if not newJoinedSelector will be the last set of elements in the selector
|
||||
if (beginningPath.length > 0) {
|
||||
newSelectorPath = beginningPath.slice(0);
|
||||
lastSelector = newSelectorPath.pop();
|
||||
newJoinedSelector = originalSelector.createDerived(lastSelector.elements.slice(0));
|
||||
}
|
||||
else {
|
||||
sel.push(new Selector(elements));
|
||||
newJoinedSelector = originalSelector.createDerived([]);
|
||||
}
|
||||
|
||||
if (addPath.length > 0) {
|
||||
// /deep/ is a combinator that is valid without anything in front of it
|
||||
// so if the & does not have a combinator that is "" or " " then
|
||||
// and there is a combinator on the parent, then grab that.
|
||||
// this also allows + a { & .b { .a & { ... though not sure why you would want to do that
|
||||
var combinator = replacedElement.combinator, parentEl = addPath[0].elements[0];
|
||||
if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) {
|
||||
combinator = parentEl.combinator;
|
||||
}
|
||||
// join the elements so far with the first part of the parent
|
||||
newJoinedSelector.elements.push(new Element(combinator, parentEl.value, replacedElement.index, replacedElement.currentFileInfo));
|
||||
newJoinedSelector.elements = newJoinedSelector.elements.concat(addPath[0].elements.slice(1));
|
||||
}
|
||||
|
||||
// now add the joined selector - but only if it is not empty
|
||||
if (newJoinedSelector.elements.length !== 0) {
|
||||
newSelectorPath.push(newJoinedSelector);
|
||||
}
|
||||
|
||||
//put together the parent selectors after the join (e.g. the rest of the parent)
|
||||
if (addPath.length > 1) {
|
||||
newSelectorPath = newSelectorPath.concat(addPath.slice(1));
|
||||
}
|
||||
return newSelectorPath;
|
||||
}
|
||||
|
||||
// joins selector path from `beginningPath` with every selector path in `addPaths` array
|
||||
// `replacedElement` contains element that is being replaced by `addPath`
|
||||
// returns array with all concatenated paths
|
||||
function addAllReplacementsIntoPath( beginningPath, addPaths, replacedElement, originalSelector, result) {
|
||||
var j;
|
||||
for (j = 0; j < beginningPath.length; j++) {
|
||||
var newSelectorPath = addReplacementIntoPath(beginningPath[j], addPaths, replacedElement, originalSelector);
|
||||
result.push(newSelectorPath);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function mergeElementsOnToSelectors(elements, selectors) {
|
||||
var i, sel;
|
||||
|
||||
if (elements.length === 0) {
|
||||
return ;
|
||||
}
|
||||
if (selectors.length === 0) {
|
||||
selectors.push([ new Selector(elements) ]);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < selectors.length; i++) {
|
||||
sel = selectors[i];
|
||||
|
||||
// if the previous thing in sel is a parent this needs to join on to it
|
||||
if (sel.length > 0) {
|
||||
sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements));
|
||||
}
|
||||
else {
|
||||
sel.push(new Selector(elements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// joinSelector code follows
|
||||
var i, newPaths, hadParentSelector;
|
||||
|
||||
newPaths = [];
|
||||
hadParentSelector = replaceParentSelector(newPaths, context, selector);
|
||||
|
||||
if (!hadParentSelector) {
|
||||
if (context.length > 0) {
|
||||
newPaths = [];
|
||||
for (i = 0; i < context.length; i++) {
|
||||
newPaths.push(context[i].concat(selector));
|
||||
}
|
||||
}
|
||||
else {
|
||||
newPaths = [[selector]];
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < newPaths.length; i++) {
|
||||
paths.push(newPaths[i]);
|
||||
}
|
||||
|
||||
};
|
||||
module.exports = Ruleset;
|
||||
|
||||
@@ -51,8 +51,9 @@ Selector.prototype.match = function (other) {
|
||||
return olen; // return number of matched elements
|
||||
};
|
||||
Selector.prototype.CacheElements = function() {
|
||||
if (this._elements)
|
||||
if (this._elements) {
|
||||
return;
|
||||
}
|
||||
|
||||
var elements = this.elements.map( function(v) {
|
||||
return v.combinator.value + (v.value.value || v.value);
|
||||
@@ -90,7 +91,7 @@ Selector.prototype.genCSS = function (context, output) {
|
||||
}
|
||||
if (!this._css) {
|
||||
//TODO caching? speed comparison?
|
||||
for(i = 0; i < this.elements.length; i++) {
|
||||
for (i = 0; i < this.elements.length; i++) {
|
||||
element = this.elements[i];
|
||||
element.genCSS(context, output);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ var Node = require("./node"),
|
||||
var Unit = function (numerator, denominator, backupUnit) {
|
||||
this.numerator = numerator ? numerator.slice(0).sort() : [];
|
||||
this.denominator = denominator ? denominator.slice(0).sort() : [];
|
||||
this.backupUnit = backupUnit;
|
||||
if (backupUnit) {
|
||||
this.backupUnit = backupUnit;
|
||||
} else if (numerator && numerator.length) {
|
||||
this.backupUnit = numerator[0];
|
||||
}
|
||||
};
|
||||
|
||||
Unit.prototype = new Node();
|
||||
@@ -13,14 +17,14 @@ Unit.prototype.clone = function () {
|
||||
return new Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit);
|
||||
};
|
||||
Unit.prototype.genCSS = function (context, output) {
|
||||
if (this.numerator.length >= 1) {
|
||||
output.add(this.numerator[0]);
|
||||
} else
|
||||
if (this.denominator.length >= 1) {
|
||||
output.add(this.denominator[0]);
|
||||
} else
|
||||
if ((!context || !context.strictUnits) && this.backupUnit) {
|
||||
// Dimension checks the unit is singular and throws an error if in strict math mode.
|
||||
var strictUnits = context && context.strictUnits;
|
||||
if (this.numerator.length === 1) {
|
||||
output.add(this.numerator[0]); // the ideal situation
|
||||
} else if (!strictUnits && this.backupUnit) {
|
||||
output.add(this.backupUnit);
|
||||
} else if (!strictUnits && this.denominator.length) {
|
||||
output.add(this.denominator[0]);
|
||||
}
|
||||
};
|
||||
Unit.prototype.toString = function () {
|
||||
@@ -79,21 +83,15 @@ Unit.prototype.usedUnits = function() {
|
||||
return result;
|
||||
};
|
||||
Unit.prototype.cancel = function () {
|
||||
var counter = {}, atomicUnit, i, backup;
|
||||
var counter = {}, atomicUnit, i;
|
||||
|
||||
for (i = 0; i < this.numerator.length; i++) {
|
||||
atomicUnit = this.numerator[i];
|
||||
if (!backup) {
|
||||
backup = atomicUnit;
|
||||
}
|
||||
counter[atomicUnit] = (counter[atomicUnit] || 0) + 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.denominator.length; i++) {
|
||||
atomicUnit = this.denominator[i];
|
||||
if (!backup) {
|
||||
backup = atomicUnit;
|
||||
}
|
||||
counter[atomicUnit] = (counter[atomicUnit] || 0) - 1;
|
||||
}
|
||||
|
||||
@@ -116,10 +114,6 @@ Unit.prototype.cancel = function () {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.numerator.length === 0 && this.denominator.length === 0 && backup) {
|
||||
this.backupUnit = backup;
|
||||
}
|
||||
|
||||
this.numerator.sort();
|
||||
this.denominator.sort();
|
||||
};
|
||||
|
||||
@@ -23,9 +23,12 @@ URL.prototype.eval = function (context) {
|
||||
if (!this.isEvald) {
|
||||
// Add the base path if the URL is relative
|
||||
rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
|
||||
if (rootpath && typeof val.value === "string" && context.isPathRelative(val.value)) {
|
||||
if (rootpath &&
|
||||
typeof val.value === "string" &&
|
||||
context.isPathRelative(val.value)) {
|
||||
|
||||
if (!val.quote) {
|
||||
rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; });
|
||||
rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\" + match; });
|
||||
}
|
||||
val.value = rootpath + val.value;
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ Value.prototype.eval = function (context) {
|
||||
};
|
||||
Value.prototype.genCSS = function (context, output) {
|
||||
var i;
|
||||
for(i = 0; i < this.value.length; i++) {
|
||||
for (i = 0; i < this.value.length; i++) {
|
||||
this.value[i].genCSS(context, output);
|
||||
if (i+1 < this.value.length) {
|
||||
if (i + 1 < this.value.length) {
|
||||
output.add((context && context.compress) ? ',' : ', ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ Variable.prototype.eval = function (context) {
|
||||
variable = this.find(context.frames, function (frame) {
|
||||
var v = frame.variable(name);
|
||||
if (v) {
|
||||
if (v.important) {
|
||||
var importantScope = context.importantScope[context.importantScope.length - 1];
|
||||
importantScope.important = v.important;
|
||||
}
|
||||
return v.value.eval(context);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var tree = require("../tree"),
|
||||
Visitor = require("./visitor");
|
||||
Visitor = require("./visitor"),
|
||||
logger = require("../logger");
|
||||
|
||||
/*jshint loopfunc:true */
|
||||
|
||||
@@ -30,7 +31,7 @@ ExtendFinderVisitor.prototype = {
|
||||
|
||||
// get &:extend(.a); rules which apply to all selectors in this ruleset
|
||||
var rules = rulesetNode.rules, ruleCnt = rules ? rules.length : 0;
|
||||
for(i = 0; i < ruleCnt; i++) {
|
||||
for (i = 0; i < ruleCnt; i++) {
|
||||
if (rulesetNode.rules[i] instanceof tree.Extend) {
|
||||
allSelectorsExtendList.push(rules[i]);
|
||||
rulesetNode.extendOnEveryPath = true;
|
||||
@@ -40,7 +41,7 @@ ExtendFinderVisitor.prototype = {
|
||||
// now find every selector and apply the extends that apply to all extends
|
||||
// and the ones which apply to an individual extend
|
||||
var paths = rulesetNode.paths;
|
||||
for(i = 0; i < paths.length; i++) {
|
||||
for (i = 0; i < paths.length; i++) {
|
||||
var selectorPath = paths[i],
|
||||
selector = selectorPath[selectorPath.length - 1],
|
||||
selExtendList = selector.extendList;
|
||||
@@ -54,13 +55,13 @@ ExtendFinderVisitor.prototype = {
|
||||
});
|
||||
}
|
||||
|
||||
for(j = 0; j < extendList.length; j++) {
|
||||
for (j = 0; j < extendList.length; j++) {
|
||||
this.foundExtends = true;
|
||||
extend = extendList[j];
|
||||
extend.findSelfSelectors(selectorPath);
|
||||
extend.ruleset = rulesetNode;
|
||||
if (j === 0) { extend.firstExtendOnThisSelectorPath = true; }
|
||||
this.allExtendsStack[this.allExtendsStack.length-1].push(extend);
|
||||
this.allExtendsStack[this.allExtendsStack.length - 1].push(extend);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,23 +95,44 @@ var ProcessExtendsVisitor = function() {
|
||||
ProcessExtendsVisitor.prototype = {
|
||||
run: function(root) {
|
||||
var extendFinder = new ExtendFinderVisitor();
|
||||
this.extendIndicies = {};
|
||||
extendFinder.run(root);
|
||||
if (!extendFinder.foundExtends) { return root; }
|
||||
root.allExtends = root.allExtends.concat(this.doExtendChaining(root.allExtends, root.allExtends));
|
||||
this.allExtendsStack = [root.allExtends];
|
||||
return this._visitor.visit(root);
|
||||
var newRoot = this._visitor.visit(root);
|
||||
this.checkExtendsForNonMatched(root.allExtends);
|
||||
return newRoot;
|
||||
},
|
||||
checkExtendsForNonMatched: function(extendList) {
|
||||
var indicies = this.extendIndicies;
|
||||
extendList.filter(function(extend) {
|
||||
return !extend.hasFoundMatches && extend.parent_ids.length == 1;
|
||||
}).forEach(function(extend) {
|
||||
var selector = "_unknown_";
|
||||
try {
|
||||
selector = extend.selector.toCSS({});
|
||||
}
|
||||
catch(_) {}
|
||||
|
||||
if (!indicies[extend.index + ' ' + selector]) {
|
||||
indicies[extend.index + ' ' + selector] = true;
|
||||
logger.warn("extend '" + selector + "' has no matches");
|
||||
}
|
||||
});
|
||||
},
|
||||
doExtendChaining: function (extendsList, extendsListTarget, iterationCount) {
|
||||
//
|
||||
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
|
||||
// the selector we would do normally, but we are also adding an extend with the same target selector
|
||||
// chaining is different from normal extension.. if we extend an extend then we are not just copying, altering
|
||||
// and pasting the selector we would do normally, but we are also adding an extend with the same target selector
|
||||
// this means this new extend can then go and alter other extends
|
||||
//
|
||||
// this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
|
||||
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
|
||||
// we look at each selector at a time, as is done in visitRuleset
|
||||
// this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already
|
||||
// processed if we look at each selector at a time, as is done in visitRuleset
|
||||
|
||||
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend;
|
||||
var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath,
|
||||
extend, targetExtend, newExtend;
|
||||
|
||||
iterationCount = iterationCount || 0;
|
||||
|
||||
@@ -120,14 +142,14 @@ ProcessExtendsVisitor.prototype = {
|
||||
// and the second is the target.
|
||||
// the seperation into two lists allows us to process a subset of chains with a bigger set, as is the
|
||||
// case when processing media queries
|
||||
for(extendIndex = 0; extendIndex < extendsList.length; extendIndex++){
|
||||
for(targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++){
|
||||
for (extendIndex = 0; extendIndex < extendsList.length; extendIndex++) {
|
||||
for (targetExtendIndex = 0; targetExtendIndex < extendsListTarget.length; targetExtendIndex++) {
|
||||
|
||||
extend = extendsList[extendIndex];
|
||||
targetExtend = extendsListTarget[targetExtendIndex];
|
||||
|
||||
// look for circular references
|
||||
if( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ){ continue; }
|
||||
if ( extend.parent_ids.indexOf( targetExtend.object_id ) >= 0 ) { continue; }
|
||||
|
||||
// find a match in the target extends self selector (the bit before :extend)
|
||||
selectorPath = [targetExtend.selfSelectors[0]];
|
||||
@@ -135,6 +157,8 @@ ProcessExtendsVisitor.prototype = {
|
||||
|
||||
if (matches.length) {
|
||||
|
||||
extend.hasFoundMatches = true;
|
||||
|
||||
// we found a match, so for each self selector..
|
||||
extend.selfSelectors.forEach(function(selfSelector) {
|
||||
|
||||
@@ -146,7 +170,7 @@ ProcessExtendsVisitor.prototype = {
|
||||
newExtend.selfSelectors = newSelector;
|
||||
|
||||
// add the extend onto the list of extends for that selector
|
||||
newSelector[newSelector.length-1].extendList = [newExtend];
|
||||
newSelector[newSelector.length - 1].extendList = [newExtend];
|
||||
|
||||
// record that we need to add it.
|
||||
extendsToAdd.push(newExtend);
|
||||
@@ -180,11 +204,13 @@ ProcessExtendsVisitor.prototype = {
|
||||
selectorTwo = extendsToAdd[0].selector.toCSS();
|
||||
}
|
||||
catch(e) {}
|
||||
throw {message: "extend circular reference detected. One of the circular extends is currently:"+selectorOne+":extend(" + selectorTwo+")"};
|
||||
throw { message: "extend circular reference detected. One of the circular extends is currently:" +
|
||||
selectorOne + ":extend(" + selectorTwo + ")"};
|
||||
}
|
||||
|
||||
// now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
|
||||
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount+1));
|
||||
// now process the new extends on the existing rules so that we can handle a extending b extending c extending
|
||||
// d extending e...
|
||||
return extendsToAdd.concat(extendVisitor.doExtendChaining(extendsToAdd, extendsListTarget, iterationCount + 1));
|
||||
} else {
|
||||
return extendsToAdd;
|
||||
}
|
||||
@@ -202,22 +228,24 @@ ProcessExtendsVisitor.prototype = {
|
||||
if (rulesetNode.root) {
|
||||
return;
|
||||
}
|
||||
var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length-1], selectorsToAdd = [], extendVisitor = this, selectorPath;
|
||||
var matches, pathIndex, extendIndex, allExtends = this.allExtendsStack[this.allExtendsStack.length - 1],
|
||||
selectorsToAdd = [], extendVisitor = this, selectorPath;
|
||||
|
||||
// look at each selector path in the ruleset, find any extend matches and then copy, find and replace
|
||||
|
||||
for(extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
|
||||
for(pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
|
||||
for (extendIndex = 0; extendIndex < allExtends.length; extendIndex++) {
|
||||
for (pathIndex = 0; pathIndex < rulesetNode.paths.length; pathIndex++) {
|
||||
selectorPath = rulesetNode.paths[pathIndex];
|
||||
|
||||
// extending extends happens initially, before the main pass
|
||||
if (rulesetNode.extendOnEveryPath) { continue; }
|
||||
var extendList = selectorPath[selectorPath.length-1].extendList;
|
||||
var extendList = selectorPath[selectorPath.length - 1].extendList;
|
||||
if (extendList && extendList.length) { continue; }
|
||||
|
||||
matches = this.findMatch(allExtends[extendIndex], selectorPath);
|
||||
|
||||
if (matches.length) {
|
||||
allExtends[extendIndex].hasFoundMatches = true;
|
||||
|
||||
allExtends[extendIndex].selfSelectors.forEach(function(selfSelector) {
|
||||
selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector));
|
||||
@@ -239,24 +267,25 @@ ProcessExtendsVisitor.prototype = {
|
||||
potentialMatches = [], potentialMatch, matches = [];
|
||||
|
||||
// loop through the haystack elements
|
||||
for(haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
|
||||
for (haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) {
|
||||
hackstackSelector = haystackSelectorPath[haystackSelectorIndex];
|
||||
|
||||
for(hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
|
||||
for (hackstackElementIndex = 0; hackstackElementIndex < hackstackSelector.elements.length; hackstackElementIndex++) {
|
||||
|
||||
haystackElement = hackstackSelector.elements[hackstackElementIndex];
|
||||
|
||||
// if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
|
||||
if (extend.allowBefore || (haystackSelectorIndex === 0 && hackstackElementIndex === 0)) {
|
||||
potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0, initialCombinator: haystackElement.combinator});
|
||||
potentialMatches.push({pathIndex: haystackSelectorIndex, index: hackstackElementIndex, matched: 0,
|
||||
initialCombinator: haystackElement.combinator});
|
||||
}
|
||||
|
||||
for(i = 0; i < potentialMatches.length; i++) {
|
||||
for (i = 0; i < potentialMatches.length; i++) {
|
||||
potentialMatch = potentialMatches[i];
|
||||
|
||||
// selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
|
||||
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
|
||||
// what the resulting combinator will be
|
||||
// then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to
|
||||
// work out what the resulting combinator will be
|
||||
targetCombinator = haystackElement.combinator.value;
|
||||
if (targetCombinator === '' && hackstackElementIndex === 0) {
|
||||
targetCombinator = ' ';
|
||||
@@ -274,7 +303,8 @@ ProcessExtendsVisitor.prototype = {
|
||||
if (potentialMatch) {
|
||||
potentialMatch.finished = potentialMatch.matched === needleElements.length;
|
||||
if (potentialMatch.finished &&
|
||||
(!extend.allowAfter && (hackstackElementIndex+1 < hackstackSelector.elements.length || haystackSelectorIndex+1 < haystackSelectorPath.length))) {
|
||||
(!extend.allowAfter &&
|
||||
(hackstackElementIndex + 1 < hackstackSelector.elements.length || haystackSelectorIndex + 1 < haystackSelectorPath.length))) {
|
||||
potentialMatch = null;
|
||||
}
|
||||
}
|
||||
@@ -320,7 +350,7 @@ ProcessExtendsVisitor.prototype = {
|
||||
if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) {
|
||||
return false;
|
||||
}
|
||||
for(var i = 0; i <elementValue1.elements.length; i++) {
|
||||
for (var i = 0; i < elementValue1.elements.length; i++) {
|
||||
if (elementValue1.elements[i].combinator.value !== elementValue2.elements[i].combinator.value) {
|
||||
if (i !== 0 || (elementValue1.elements[i].combinator.value || ' ') !== (elementValue2.elements[i].combinator.value || ' ')) {
|
||||
return false;
|
||||
@@ -358,7 +388,8 @@ ProcessExtendsVisitor.prototype = {
|
||||
);
|
||||
|
||||
if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) {
|
||||
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
|
||||
path[path.length - 1].elements = path[path.length - 1]
|
||||
.elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
|
||||
currentSelectorPathElementIndex = 0;
|
||||
currentSelectorPathIndex++;
|
||||
}
|
||||
@@ -387,7 +418,8 @@ ProcessExtendsVisitor.prototype = {
|
||||
}
|
||||
|
||||
if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) {
|
||||
path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
|
||||
path[path.length - 1].elements = path[path.length - 1]
|
||||
.elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex));
|
||||
currentSelectorPathIndex++;
|
||||
}
|
||||
|
||||
@@ -398,20 +430,24 @@ ProcessExtendsVisitor.prototype = {
|
||||
visitRulesetOut: function (rulesetNode) {
|
||||
},
|
||||
visitMedia: function (mediaNode, visitArgs) {
|
||||
var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
|
||||
var newAllExtends = mediaNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length - 1]);
|
||||
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, mediaNode.allExtends));
|
||||
this.allExtendsStack.push(newAllExtends);
|
||||
},
|
||||
visitMediaOut: function (mediaNode) {
|
||||
this.allExtendsStack.length = this.allExtendsStack.length - 1;
|
||||
var lastIndex = this.allExtendsStack.length - 1;
|
||||
this.checkExtendsForNonMatched(this.allExtendsStack[lastIndex]);
|
||||
this.allExtendsStack.length = lastIndex;
|
||||
},
|
||||
visitDirective: function (directiveNode, visitArgs) {
|
||||
var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);
|
||||
var newAllExtends = directiveNode.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length - 1]);
|
||||
newAllExtends = newAllExtends.concat(this.doExtendChaining(newAllExtends, directiveNode.allExtends));
|
||||
this.allExtendsStack.push(newAllExtends);
|
||||
},
|
||||
visitDirectiveOut: function (directiveNode) {
|
||||
this.allExtendsStack.length = this.allExtendsStack.length - 1;
|
||||
var lastIndex = this.allExtendsStack.length - 1;
|
||||
this.checkExtendsForNonMatched(this.allExtendsStack[lastIndex]);
|
||||
this.allExtendsStack.length = lastIndex;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
54
lib/less/visitors/import-sequencer.js
Normal file
54
lib/less/visitors/import-sequencer.js
Normal file
@@ -0,0 +1,54 @@
|
||||
function ImportSequencer(onSequencerEmpty) {
|
||||
this.imports = [];
|
||||
this.variableImports = [];
|
||||
this._onSequencerEmpty = onSequencerEmpty;
|
||||
this._currentDepth = 0;
|
||||
}
|
||||
|
||||
ImportSequencer.prototype.addImport = function(callback) {
|
||||
var importSequencer = this,
|
||||
importItem = {
|
||||
callback: callback,
|
||||
args: null,
|
||||
isReady: false
|
||||
};
|
||||
this.imports.push(importItem);
|
||||
return function() {
|
||||
importItem.args = Array.prototype.slice.call(arguments, 0);
|
||||
importItem.isReady = true;
|
||||
importSequencer.tryRun();
|
||||
};
|
||||
};
|
||||
|
||||
ImportSequencer.prototype.addVariableImport = function(callback) {
|
||||
this.variableImports.push(callback);
|
||||
};
|
||||
|
||||
ImportSequencer.prototype.tryRun = function() {
|
||||
this._currentDepth++;
|
||||
try {
|
||||
while (true) {
|
||||
while (this.imports.length > 0) {
|
||||
var importItem = this.imports[0];
|
||||
if (!importItem.isReady) {
|
||||
return;
|
||||
}
|
||||
this.imports = this.imports.slice(1);
|
||||
importItem.callback.apply(null, importItem.args);
|
||||
}
|
||||
if (this.variableImports.length === 0) {
|
||||
break;
|
||||
}
|
||||
var variableImport = this.variableImports[0];
|
||||
this.variableImports = this.variableImports.slice(1);
|
||||
variableImport();
|
||||
}
|
||||
} finally {
|
||||
this._currentDepth--;
|
||||
}
|
||||
if (this._currentDepth === 0 && this._onSequencerEmpty) {
|
||||
this._onSequencerEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ImportSequencer;
|
||||
@@ -1,144 +1,171 @@
|
||||
var contexts = require("../contexts"),
|
||||
Visitor = require("./visitor");
|
||||
Visitor = require("./visitor"),
|
||||
ImportSequencer = require("./import-sequencer");
|
||||
|
||||
var ImportVisitor = function(importer, finish) {
|
||||
|
||||
var ImportVisitor = function(importer, finish, evalEnv, onceFileDetectionMap, recursionDetector) {
|
||||
this._visitor = new Visitor(this);
|
||||
this._importer = importer;
|
||||
this._finish = finish;
|
||||
this.context = evalEnv || new contexts.Eval();
|
||||
this.context = new contexts.Eval();
|
||||
this.importCount = 0;
|
||||
this.onceFileDetectionMap = onceFileDetectionMap || {};
|
||||
this.onceFileDetectionMap = {};
|
||||
this.recursionDetector = {};
|
||||
if (recursionDetector) {
|
||||
for(var fullFilename in recursionDetector) {
|
||||
if (recursionDetector.hasOwnProperty(fullFilename)) {
|
||||
this.recursionDetector[fullFilename] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._sequencer = new ImportSequencer(this._onSequencerEmpty.bind(this));
|
||||
};
|
||||
|
||||
ImportVisitor.prototype = {
|
||||
isReplacing: true,
|
||||
isReplacing: false,
|
||||
run: function (root) {
|
||||
var error;
|
||||
try {
|
||||
// process the contents
|
||||
this._visitor.visit(root);
|
||||
}
|
||||
catch(e) {
|
||||
error = e;
|
||||
this.error = e;
|
||||
}
|
||||
|
||||
this.isFinished = true;
|
||||
|
||||
if (this.importCount === 0) {
|
||||
this._finish(error);
|
||||
this._sequencer.tryRun();
|
||||
},
|
||||
_onSequencerEmpty: function() {
|
||||
if (!this.isFinished) {
|
||||
return;
|
||||
}
|
||||
this._finish(this.error);
|
||||
},
|
||||
visitImport: function (importNode, visitArgs) {
|
||||
var importVisitor = this,
|
||||
evaldImportNode,
|
||||
inlineCSS = importNode.options.inline;
|
||||
var inlineCSS = importNode.options.inline;
|
||||
|
||||
if (!importNode.css || inlineCSS) {
|
||||
|
||||
try {
|
||||
evaldImportNode = importNode.evalForImport(this.context);
|
||||
} catch(e){
|
||||
if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
|
||||
// attempt to eval properly and treat as css
|
||||
importNode.css = true;
|
||||
// if that fails, this error will be thrown
|
||||
importNode.error = e;
|
||||
}
|
||||
var context = new contexts.Eval(this.context, this.context.frames.slice(0));
|
||||
var importParent = context.frames[0];
|
||||
|
||||
if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
|
||||
importNode = evaldImportNode;
|
||||
this.importCount++;
|
||||
var context = new contexts.Eval(this.context, this.context.frames.slice(0));
|
||||
|
||||
if (importNode.options.multiple) {
|
||||
context.importMultiple = true;
|
||||
}
|
||||
|
||||
// try appending if we haven't determined if it is css or not
|
||||
var tryAppendLessExtension = importNode.css === undefined;
|
||||
this._importer.push(importNode.getPath(), tryAppendLessExtension, importNode.currentFileInfo, importNode.options, function (e, root, importedAtRoot, fullPath) {
|
||||
if (e && !e.filename) {
|
||||
e.index = importNode.index; e.filename = importNode.currentFileInfo.filename;
|
||||
}
|
||||
|
||||
var duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector;
|
||||
if (!context.importMultiple) {
|
||||
if (duplicateImport) {
|
||||
importNode.skip = true;
|
||||
} else {
|
||||
importNode.skip = function() {
|
||||
if (fullPath in importVisitor.onceFileDetectionMap) {
|
||||
return true;
|
||||
}
|
||||
importVisitor.onceFileDetectionMap[fullPath] = true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var subFinish = function(e) {
|
||||
importVisitor.importCount--;
|
||||
|
||||
if (importVisitor.importCount === 0 && importVisitor.isFinished) {
|
||||
importVisitor._finish(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (root) {
|
||||
importNode.root = root;
|
||||
importNode.importedFilename = fullPath;
|
||||
|
||||
if (!inlineCSS && (context.importMultiple || !duplicateImport)) {
|
||||
importVisitor.recursionDetector[fullPath] = true;
|
||||
new ImportVisitor(importVisitor._importer, subFinish, context, importVisitor.onceFileDetectionMap, importVisitor.recursionDetector)
|
||||
.run(root);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
subFinish();
|
||||
});
|
||||
this.importCount++;
|
||||
if (importNode.isVariableImport()) {
|
||||
this._sequencer.addVariableImport(this.processImportNode.bind(this, importNode, context, importParent));
|
||||
} else {
|
||||
this.processImportNode(importNode, context, importParent);
|
||||
}
|
||||
}
|
||||
visitArgs.visitDeeper = false;
|
||||
return importNode;
|
||||
},
|
||||
processImportNode: function(importNode, context, importParent) {
|
||||
var evaldImportNode,
|
||||
inlineCSS = importNode.options.inline;
|
||||
|
||||
try {
|
||||
evaldImportNode = importNode.evalForImport(context);
|
||||
} catch(e) {
|
||||
if (!e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; }
|
||||
// attempt to eval properly and treat as css
|
||||
importNode.css = true;
|
||||
// if that fails, this error will be thrown
|
||||
importNode.error = e;
|
||||
}
|
||||
|
||||
if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
|
||||
|
||||
if (evaldImportNode.options.multiple) {
|
||||
context.importMultiple = true;
|
||||
}
|
||||
|
||||
// try appending if we haven't determined if it is css or not
|
||||
var tryAppendLessExtension = evaldImportNode.css === undefined;
|
||||
|
||||
for (var i = 0; i < importParent.rules.length; i++) {
|
||||
if (importParent.rules[i] === importNode) {
|
||||
importParent.rules[i] = evaldImportNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var onImported = this.onImported.bind(this, evaldImportNode, context),
|
||||
sequencedOnImported = this._sequencer.addImport(onImported);
|
||||
|
||||
this._importer.push(evaldImportNode.getPath(), tryAppendLessExtension, evaldImportNode.currentFileInfo,
|
||||
evaldImportNode.options, sequencedOnImported);
|
||||
} else {
|
||||
this.importCount--;
|
||||
if (this.isFinished) {
|
||||
this._sequencer.tryRun();
|
||||
}
|
||||
}
|
||||
},
|
||||
onImported: function (importNode, context, e, root, importedAtRoot, fullPath) {
|
||||
if (e) {
|
||||
if (!e.filename) {
|
||||
e.index = importNode.index; e.filename = importNode.currentFileInfo.filename;
|
||||
}
|
||||
this.error = e;
|
||||
}
|
||||
|
||||
var importVisitor = this,
|
||||
inlineCSS = importNode.options.inline,
|
||||
duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector;
|
||||
|
||||
if (!context.importMultiple) {
|
||||
if (duplicateImport) {
|
||||
importNode.skip = true;
|
||||
} else {
|
||||
importNode.skip = function() {
|
||||
if (fullPath in importVisitor.onceFileDetectionMap) {
|
||||
return true;
|
||||
}
|
||||
importVisitor.onceFileDetectionMap[fullPath] = true;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (root) {
|
||||
importNode.root = root;
|
||||
importNode.importedFilename = fullPath;
|
||||
|
||||
if (!inlineCSS && (context.importMultiple || !duplicateImport)) {
|
||||
importVisitor.recursionDetector[fullPath] = true;
|
||||
|
||||
var oldContext = this.context;
|
||||
this.context = context;
|
||||
try {
|
||||
this._visitor.visit(root);
|
||||
} catch (e) {
|
||||
this.error = e;
|
||||
}
|
||||
this.context = oldContext;
|
||||
}
|
||||
}
|
||||
|
||||
importVisitor.importCount--;
|
||||
|
||||
if (importVisitor.isFinished) {
|
||||
importVisitor._sequencer.tryRun();
|
||||
}
|
||||
},
|
||||
visitRule: function (ruleNode, visitArgs) {
|
||||
visitArgs.visitDeeper = false;
|
||||
return ruleNode;
|
||||
},
|
||||
visitDirective: function (directiveNode, visitArgs) {
|
||||
this.context.frames.unshift(directiveNode);
|
||||
return directiveNode;
|
||||
},
|
||||
visitDirectiveOut: function (directiveNode) {
|
||||
this.context.frames.shift();
|
||||
},
|
||||
visitMixinDefinition: function (mixinDefinitionNode, visitArgs) {
|
||||
this.context.frames.unshift(mixinDefinitionNode);
|
||||
return mixinDefinitionNode;
|
||||
},
|
||||
visitMixinDefinitionOut: function (mixinDefinitionNode) {
|
||||
this.context.frames.shift();
|
||||
},
|
||||
visitRuleset: function (rulesetNode, visitArgs) {
|
||||
this.context.frames.unshift(rulesetNode);
|
||||
return rulesetNode;
|
||||
},
|
||||
visitRulesetOut: function (rulesetNode) {
|
||||
this.context.frames.shift();
|
||||
},
|
||||
visitMedia: function (mediaNode, visitArgs) {
|
||||
this.context.frames.unshift(mediaNode.rules[0]);
|
||||
return mediaNode;
|
||||
},
|
||||
visitMediaOut: function (mediaNode) {
|
||||
this.context.frames.shift();
|
||||
|
||||
@@ -14,7 +14,7 @@ ToCSSVisitor.prototype = {
|
||||
|
||||
visitRule: function (ruleNode, visitArgs) {
|
||||
if (ruleNode.variable) {
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
return ruleNode;
|
||||
},
|
||||
@@ -23,16 +23,14 @@ ToCSSVisitor.prototype = {
|
||||
// mixin definitions do not get eval'd - this means they keep state
|
||||
// so we have to clear that state here so it isn't used if toCSS is called twice
|
||||
mixinNode.frames = [];
|
||||
return [];
|
||||
},
|
||||
|
||||
visitExtend: function (extendNode, visitArgs) {
|
||||
return [];
|
||||
},
|
||||
|
||||
visitComment: function (commentNode, visitArgs) {
|
||||
if (commentNode.isSilent(this._context)) {
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
return commentNode;
|
||||
},
|
||||
@@ -42,38 +40,70 @@ ToCSSVisitor.prototype = {
|
||||
visitArgs.visitDeeper = false;
|
||||
|
||||
if (!mediaNode.rules.length) {
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
return mediaNode;
|
||||
},
|
||||
|
||||
visitDirective: function(directiveNode, visitArgs) {
|
||||
if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) {
|
||||
return [];
|
||||
}
|
||||
if (directiveNode.name === "@charset") {
|
||||
if (!directiveNode.getIsReferenced()) {
|
||||
return;
|
||||
}
|
||||
// Only output the debug info together with subsequent @charset definitions
|
||||
// a comment (or @media statement) before the actual @charset directive would
|
||||
// be considered illegal css as it has to be on the first line
|
||||
if (this.charset) {
|
||||
if (directiveNode.debugInfo) {
|
||||
var comment = new tree.Comment("/* " + directiveNode.toCSS(this._context).replace(/\n/g, "")+" */\n");
|
||||
var comment = new tree.Comment("/* " + directiveNode.toCSS(this._context).replace(/\n/g, "") + " */\n");
|
||||
comment.debugInfo = directiveNode.debugInfo;
|
||||
return this._visitor.visit(comment);
|
||||
}
|
||||
return [];
|
||||
return;
|
||||
}
|
||||
this.charset = true;
|
||||
}
|
||||
if (directiveNode.rules && directiveNode.rules.rules) {
|
||||
this._mergeRules(directiveNode.rules.rules);
|
||||
//process childs
|
||||
directiveNode.accept(this._visitor);
|
||||
visitArgs.visitDeeper = false;
|
||||
|
||||
// the directive was directly referenced and therefore needs to be shown in the output
|
||||
if (directiveNode.getIsReferenced()) {
|
||||
return directiveNode;
|
||||
}
|
||||
|
||||
if (!directiveNode.rules.rules) {
|
||||
return ;
|
||||
}
|
||||
|
||||
//the directive was not directly referenced
|
||||
for (var r = 0; r < directiveNode.rules.rules.length; r++) {
|
||||
var rule = directiveNode.rules.rules[r];
|
||||
if (rule.getIsReferenced && rule.getIsReferenced()) {
|
||||
//the directive contains something that was referenced (likely by extend)
|
||||
//therefore it needs to be shown in output too
|
||||
|
||||
//marking as referenced in case the directive is stored inside another directive
|
||||
directiveNode.markReferenced();
|
||||
return directiveNode;
|
||||
}
|
||||
}
|
||||
//The directive was not directly referenced and does not contain anything that
|
||||
//was referenced. Therefore it must not be shown in output.
|
||||
return ;
|
||||
} else {
|
||||
if (!directiveNode.getIsReferenced()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return directiveNode;
|
||||
},
|
||||
|
||||
checkPropertiesInRoot: function(rules) {
|
||||
var ruleNode;
|
||||
for(var i = 0; i < rules.length; i++) {
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
ruleNode = rules[i];
|
||||
if (ruleNode instanceof tree.Rule && !ruleNode.variable) {
|
||||
throw { message: "properties must be inside selector blocks, they cannot be in the root.",
|
||||
@@ -95,7 +125,7 @@ ToCSSVisitor.prototype = {
|
||||
if (p[0].elements[0].combinator.value === ' ') {
|
||||
p[0].elements[0].combinator = new(tree.Combinator)('');
|
||||
}
|
||||
for(i = 0; i < p.length; i++) {
|
||||
for (i = 0; i < p.length; i++) {
|
||||
if (p[i].getIsReferenced() && p[i].getIsOutput()) {
|
||||
return true;
|
||||
}
|
||||
@@ -160,7 +190,7 @@ ToCSSVisitor.prototype = {
|
||||
var ruleCache = {},
|
||||
ruleList, rule, i;
|
||||
|
||||
for(i = rules.length - 1; i >= 0 ; i--) {
|
||||
for (i = rules.length - 1; i >= 0 ; i--) {
|
||||
rule = rules[i];
|
||||
if (rule instanceof tree.Rule) {
|
||||
if (!ruleCache[rule.name]) {
|
||||
@@ -227,8 +257,8 @@ ToCSSVisitor.prototype = {
|
||||
var spacedGroups = [];
|
||||
var lastSpacedGroup = [];
|
||||
parts.map(function (p) {
|
||||
if (p.merge==="+") {
|
||||
if (lastSpacedGroup.length > 0) {
|
||||
if (p.merge === "+") {
|
||||
if (lastSpacedGroup.length > 0) {
|
||||
spacedGroups.push(toExpression(lastSpacedGroup));
|
||||
}
|
||||
lastSpacedGroup = [];
|
||||
|
||||
@@ -106,6 +106,7 @@ Visitor.prototype = {
|
||||
var out = [];
|
||||
for (i = 0; i < cnt; i++) {
|
||||
var evald = this.visit(nodes[i]);
|
||||
if (evald === undefined) { continue; }
|
||||
if (!evald.splice) {
|
||||
out.push(evald);
|
||||
} else if (evald.length) {
|
||||
@@ -124,6 +125,9 @@ Visitor.prototype = {
|
||||
|
||||
for (i = 0, cnt = arr.length; i < cnt; i++) {
|
||||
item = arr[i];
|
||||
if (item === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (!item.splice) {
|
||||
out.push(item);
|
||||
continue;
|
||||
@@ -131,6 +135,9 @@ Visitor.prototype = {
|
||||
|
||||
for (j = 0, nestedCnt = item.length; j < nestedCnt; j++) {
|
||||
nestedItem = item[j];
|
||||
if (nestedItem === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (!nestedItem.splice) {
|
||||
out.push(nestedItem);
|
||||
} else if (nestedItem.length) {
|
||||
|
||||
@@ -1872,7 +1872,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
|
||||
if (original.source !== null
|
||||
&& original.line !== null
|
||||
&& original.column !== null) {
|
||||
if(lastOriginalSource !== original.source
|
||||
if (lastOriginalSource !== original.source
|
||||
|| lastOriginalLine !== original.line
|
||||
|| lastOriginalColumn !== original.column
|
||||
|| lastOriginalName !== original.name) {
|
||||
|
||||
44
package.json
44
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "less",
|
||||
"version": "2.0.0-b1",
|
||||
"version": "2.3.1",
|
||||
"description": "Leaner CSS",
|
||||
"homepage": "http://lesscss.org",
|
||||
"author": {
|
||||
@@ -26,7 +26,7 @@
|
||||
"bin": {
|
||||
"lessc": "./bin/lessc"
|
||||
},
|
||||
"main": "./lib/less-node/index",
|
||||
"main": "index",
|
||||
"directories": {
|
||||
"test": "./test"
|
||||
},
|
||||
@@ -40,26 +40,30 @@
|
||||
"test": "grunt test"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "~3.0.4",
|
||||
"mime": "~1.2.11",
|
||||
"request": "~2.45.0",
|
||||
"mkdirp": "~0.5.0",
|
||||
"source-map": "0.1.x",
|
||||
"promise": "~6.0.1"
|
||||
"errno": "^0.1.1",
|
||||
"graceful-fs": "^3.0.5",
|
||||
"image-size": "~0.3.5",
|
||||
"mime": "^1.2.11",
|
||||
"mkdirp": "^0.5.0",
|
||||
"promise": "^6.0.1",
|
||||
"request": "^2.51.0",
|
||||
"source-map": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"diff": "~1.0",
|
||||
"grunt": "~0.4.5",
|
||||
"grunt-contrib-clean": "~0.6.0",
|
||||
"grunt-contrib-concat": "~0.5.0",
|
||||
"grunt-contrib-connect": "~0.8.0",
|
||||
"grunt-contrib-jasmine": "~0.8.0",
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-contrib-uglify": "~0.6.0",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"matchdep": "~0.3.0",
|
||||
"time-grunt": "~1.0.0",
|
||||
"grunt-browserify": "~3.0.1"
|
||||
"diff": "^1.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-contrib-clean": "^0.6.0",
|
||||
"grunt-contrib-concat": "^0.5.0",
|
||||
"grunt-contrib-connect": "^0.9.0",
|
||||
"grunt-contrib-jasmine": "^0.8.1",
|
||||
"grunt-contrib-jshint": "^0.11.0",
|
||||
"grunt-contrib-uglify": "^0.7.0",
|
||||
"grunt-jscs": "^1.2.0",
|
||||
"grunt-shell": "^1.1.1",
|
||||
"grunt-browserify": "^3.2.0",
|
||||
"matchdep": "^0.3.0",
|
||||
"time-grunt": "^1.0.0",
|
||||
"grunt-saucelabs": "^8.3.2"
|
||||
},
|
||||
"keywords": [
|
||||
"compile less",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/* Add js reporter for sauce */
|
||||
|
||||
jasmine.getEnv().addReporter(new jasmine.JSReporter2());
|
||||
|
||||
/* record log messages for testing */
|
||||
|
||||
var logMessages = [];
|
||||
@@ -39,6 +43,25 @@ var testLessInDocument = function (testFunc) {
|
||||
}
|
||||
};
|
||||
|
||||
var ieFormat = function(text) {
|
||||
var styleNode = document.createElement('style');
|
||||
styleNode.setAttribute('type', 'text/css');
|
||||
var headNode = document.getElementsByTagName('head')[0];
|
||||
headNode.appendChild(styleNode);
|
||||
try {
|
||||
if (styleNode.styleSheet) {
|
||||
styleNode.styleSheet.cssText = text;
|
||||
} else {
|
||||
styleNode.innerText = text;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error("Couldn't reassign styleSheet.cssText.");
|
||||
}
|
||||
var transformedText = styleNode.styleSheet ? styleNode.styleSheet.cssText : styleNode.innerText;
|
||||
headNode.removeChild(styleNode);
|
||||
return transformedText;
|
||||
};
|
||||
|
||||
var testSheet = function (sheet) {
|
||||
it(sheet.id + " should match the expected output", function (done) {
|
||||
var lessOutputId = sheet.id.replace("original-", ""),
|
||||
@@ -52,11 +75,16 @@ var testSheet = function (sheet) {
|
||||
less.pageLoadFinished
|
||||
.then(function () {
|
||||
lessOutputObj = document.getElementById(lessOutputId);
|
||||
lessOutput = lessOutputObj.innerText || lessOutputObj.innerHTML;
|
||||
lessOutput = lessOutputObj.styleSheet ? lessOutputObj.styleSheet.cssText :
|
||||
(lessOutputObj.innerText || lessOutputObj.innerHTML);
|
||||
|
||||
expectedOutput
|
||||
.then(function (text) {
|
||||
expect(text).toEqual(lessOutput);
|
||||
if (window.navigator.userAgent.indexOf("MSIE") >= 0 ||
|
||||
window.navigator.userAgent.indexOf("Trident/") >= 0) {
|
||||
text = ieFormat(text);
|
||||
}
|
||||
expect(lessOutput).toEqual(text);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -98,21 +126,32 @@ var testErrorSheet = function (sheet) {
|
||||
actualErrorElement = document.getElementById(id);
|
||||
return actualErrorElement !== null;
|
||||
}).then(function () {
|
||||
actualErrorMsg = actualErrorElement.innerText
|
||||
var innerText = (actualErrorElement.innerHTML
|
||||
.replace(/<h3>|<\/?p>|<a href="[^"]*">|<\/a>|<ul>|<\/?pre( class="?[^">]*"?)?>|<\/li>|<\/?label>/ig, "")
|
||||
.replace(/<\/h3>/ig, " ")
|
||||
.replace(/<li>|<\/ul>|<br>/ig, "\n"))
|
||||
.replace(/&/ig, "&")
|
||||
// for IE8
|
||||
.replace(/\r\n/g, "\n")
|
||||
.replace(/\. \nin/, ". in");
|
||||
actualErrorMsg = innerText
|
||||
.replace(/\n\d+/g, function (lineNo) {
|
||||
return lineNo + " ";
|
||||
})
|
||||
.replace(/\n\s*in /g, " in ")
|
||||
.replace("\n\n", "\n")
|
||||
.replace(/\nStack Trace\n[\s\S]*/, "");
|
||||
.replace(/\n{2,}/g, "\n")
|
||||
.replace(/\nStack Trace\n[\s\S]*/i, "")
|
||||
.replace(/\n$/, "");
|
||||
errorFile
|
||||
.then(function (errorTxt) {
|
||||
errorTxt = errorTxt
|
||||
.replace("{path}", "")
|
||||
.replace("{pathrel}", "")
|
||||
.replace("{pathhref}", "http://localhost:8081/test/less/errors/")
|
||||
.replace("{404status}", " (404)");
|
||||
expect(errorTxt).toEqual(actualErrorMsg);
|
||||
.replace(/\{path\}/g, "")
|
||||
.replace(/\{pathrel\}/g, "")
|
||||
.replace(/\{pathhref\}/g, "http://localhost:8081/test/less/errors/")
|
||||
.replace(/\{404status\}/g, " (404)")
|
||||
.replace(/\{node\}.*\{\/node\}/g, "")
|
||||
.replace(/\n$/, "");
|
||||
expect(actualErrorMsg).toEqual(errorTxt);
|
||||
if (errorTxt == actualErrorMsg) {
|
||||
actualErrorElement.style.display = "none";
|
||||
}
|
||||
@@ -139,12 +178,13 @@ var testErrorSheetConsole = function (sheet) {
|
||||
errorFile
|
||||
.then(function (errorTxt) {
|
||||
errorTxt
|
||||
.replace("{path}", "")
|
||||
.replace("{pathrel}", "")
|
||||
.replace("{pathhref}", "http://localhost:8081/browser/less/")
|
||||
.replace("{404status}", " (404)")
|
||||
.replace(/\{path\}/g, "")
|
||||
.replace(/\{pathrel\}/g, "")
|
||||
.replace(/\{pathhref\}/g, "http://localhost:8081/browser/less/")
|
||||
.replace(/\{404status\}/g, " (404)")
|
||||
.replace(/\{node\}.*\{\/node\}/g, "")
|
||||
.trim();
|
||||
expect(errorTxt).toEqual(actualErrorMsg);
|
||||
expect(actualErrorMsg).toEqual(errorTxt);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -154,11 +194,13 @@ var loadFile = function (href) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', href, true);
|
||||
request.onload = function (e) {
|
||||
resolve(request.response.replace(/\r/g, ""));
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
resolve(request.responseText.replace(/\r/g, ""));
|
||||
}
|
||||
};
|
||||
request.send();
|
||||
request.send(null);
|
||||
});
|
||||
};
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
.modify {
|
||||
my-url: url("http://localhost:8081/test/browser/less/b.png");
|
||||
}
|
||||
.gray-gradient {
|
||||
background: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20viewBox%3D%220%200%201%201%22%20preserveAspectRatio%3D%22none%22%3E%3ClinearGradient%20id%3D%22gradient%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%25%22%20y1%3D%220%25%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E%3Cstop%20offset%3D%220%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220%22%2F%3E%3Cstop%20offset%3D%2260%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.05%22%2F%3E%3Cstop%20offset%3D%2270%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.1%22%2F%3E%3Cstop%20offset%3D%2273%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.15%22%2F%3E%3Cstop%20offset%3D%2275%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.2%22%2F%3E%3Cstop%20offset%3D%2280%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.25%22%2F%3E%3Cstop%20offset%3D%2285%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.3%22%2F%3E%3Cstop%20offset%3D%2288%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.35%22%2F%3E%3Cstop%20offset%3D%2290%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.4%22%2F%3E%3Cstop%20offset%3D%2295%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.45%22%2F%3E%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%23999999%22%20stop-opacity%3D%220.5%22%2F%3E%3C%2FlinearGradient%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22url(%23gradient)%22%20%2F%3E%3C%2Fsvg%3E');
|
||||
}
|
||||
@font-face {
|
||||
src: url("/fonts/garamond-pro.ttf");
|
||||
src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/fonts.svg#MyGeometricModern) format("svg");
|
||||
@@ -48,7 +51,7 @@
|
||||
uri: url('http://localhost:8081/test/data/data-uri-fail.png');
|
||||
}
|
||||
#svg-functions {
|
||||
background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#000000"/><stop offset="100%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
|
||||
background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#000000"/><stop offset="3%" stop-color="#ffa500"/><stop offset="100%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
|
||||
background-image: url('data:image/svg+xml,<?xml version="1.0" ?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none"><linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="1%" stop-color="#c4c4c4"/><stop offset="3%" stop-color="#ffa500"/><stop offset="5%" stop-color="#008000"/><stop offset="95%" stop-color="#ffffff"/></linearGradient><rect x="0" y="0" width="1" height="1" fill="url(#gradient)" /></svg>');
|
||||
background-image: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20viewBox%3D%220%200%201%201%22%20preserveAspectRatio%3D%22none%22%3E%3ClinearGradient%20id%3D%22gradient%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%25%22%20y1%3D%220%25%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E%3Cstop%20offset%3D%220%25%22%20stop-color%3D%22%23000000%22%2F%3E%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%23ffffff%22%2F%3E%3C%2FlinearGradient%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22url(%23gradient)%22%20%2F%3E%3C%2Fsvg%3E');
|
||||
background-image: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20viewBox%3D%220%200%201%201%22%20preserveAspectRatio%3D%22none%22%3E%3ClinearGradient%20id%3D%22gradient%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%25%22%20y1%3D%220%25%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E%3Cstop%20offset%3D%220%25%22%20stop-color%3D%22%23000000%22%2F%3E%3Cstop%20offset%3D%223%25%22%20stop-color%3D%22%23ffa500%22%2F%3E%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%23ffffff%22%2F%3E%3C%2FlinearGradient%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22url(%23gradient)%22%20%2F%3E%3C%2Fsvg%3E');
|
||||
background-image: url('data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20%3F%3E%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20version%3D%221.1%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20viewBox%3D%220%200%201%201%22%20preserveAspectRatio%3D%22none%22%3E%3ClinearGradient%20id%3D%22gradient%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%25%22%20y1%3D%220%25%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E%3Cstop%20offset%3D%221%25%22%20stop-color%3D%22%23c4c4c4%22%2F%3E%3Cstop%20offset%3D%223%25%22%20stop-color%3D%22%23ffa500%22%2F%3E%3Cstop%20offset%3D%225%25%22%20stop-color%3D%22%23008000%22%2F%3E%3Cstop%20offset%3D%2295%25%22%20stop-color%3D%22%23ffffff%22%2F%3E%3C%2FlinearGradient%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%221%22%20height%3D%221%22%20fill%3D%22url(%23gradient)%22%20%2F%3E%3C%2Fsvg%3E');
|
||||
}
|
||||
|
||||
391
test/browser/jasmine-jsreporter.js
Normal file
391
test/browser/jasmine-jsreporter.js
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
This file is part of the Jasmine JSReporter project from Ivan De Marino.
|
||||
|
||||
Copyright (C) 2011-2014 Ivan De Marino <http://ivandemarino.me>
|
||||
Copyright (C) 2014 Alex Treppass <http://alextreppass.co.uk>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL IVAN DE MARINO BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
(function (jasmine) {
|
||||
|
||||
if (!jasmine) {
|
||||
throw new Error("[Jasmine JSReporter] 'Jasmine' library not found");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Jasmine JSReporter for Jasmine 1.x
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Calculate elapsed time, in Seconds.
|
||||
* @param startMs Start time in Milliseconds
|
||||
* @param finishMs Finish time in Milliseconds
|
||||
* @return Elapsed time in Seconds */
|
||||
function elapsedSec (startMs, finishMs) {
|
||||
return (finishMs - startMs) / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round an amount to the given number of Digits.
|
||||
* If no number of digits is given, than '2' is assumed.
|
||||
* @param amount Amount to round
|
||||
* @param numOfDecDigits Number of Digits to round to. Default value is '2'.
|
||||
* @return Rounded amount */
|
||||
function round (amount, numOfDecDigits) {
|
||||
numOfDecDigits = numOfDecDigits || 2;
|
||||
return Math.round(amount * Math.pow(10, numOfDecDigits)) / Math.pow(10, numOfDecDigits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new array which contains only the failed items.
|
||||
* @param items Items which will be filtered
|
||||
* @returns {Array} of failed items */
|
||||
function failures (items) {
|
||||
var fs = [], i, v;
|
||||
for (i = 0; i < items.length; i += 1) {
|
||||
v = items[i];
|
||||
if (!v.passed_) {
|
||||
fs.push(v);
|
||||
}
|
||||
}
|
||||
return fs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect information about a Suite, recursively, and return a JSON result.
|
||||
* @param suite The Jasmine Suite to get data from
|
||||
*/
|
||||
function getSuiteData (suite) {
|
||||
var suiteData = {
|
||||
description : suite.description,
|
||||
durationSec : 0,
|
||||
specs: [],
|
||||
suites: [],
|
||||
passed: true
|
||||
},
|
||||
specs = suite.specs(),
|
||||
suites = suite.suites(),
|
||||
i, ilen;
|
||||
|
||||
// Loop over all the Suite's Specs
|
||||
for (i = 0, ilen = specs.length; i < ilen; ++i) {
|
||||
suiteData.specs[i] = {
|
||||
description : specs[i].description,
|
||||
durationSec : specs[i].durationSec,
|
||||
passed : specs[i].results().passedCount === specs[i].results().totalCount,
|
||||
skipped : specs[i].results().skipped,
|
||||
passedCount : specs[i].results().passedCount,
|
||||
failedCount : specs[i].results().failedCount,
|
||||
totalCount : specs[i].results().totalCount,
|
||||
failures: failures(specs[i].results().getItems())
|
||||
};
|
||||
suiteData.passed = !suiteData.specs[i].passed ? false : suiteData.passed;
|
||||
suiteData.durationSec += suiteData.specs[i].durationSec;
|
||||
}
|
||||
|
||||
// Loop over all the Suite's sub-Suites
|
||||
for (i = 0, ilen = suites.length; i < ilen; ++i) {
|
||||
suiteData.suites[i] = getSuiteData(suites[i]); //< recursive population
|
||||
suiteData.passed = !suiteData.suites[i].passed ? false : suiteData.passed;
|
||||
suiteData.durationSec += suiteData.suites[i].durationSec;
|
||||
}
|
||||
|
||||
// Rounding duration numbers to 3 decimal digits
|
||||
suiteData.durationSec = round(suiteData.durationSec, 4);
|
||||
|
||||
return suiteData;
|
||||
}
|
||||
|
||||
var JSReporter = function () {
|
||||
};
|
||||
|
||||
JSReporter.prototype = {
|
||||
reportRunnerStarting: function (runner) {
|
||||
// Nothing to do
|
||||
},
|
||||
|
||||
reportSpecStarting: function (spec) {
|
||||
// Start timing this spec
|
||||
spec.startedAt = new Date();
|
||||
},
|
||||
|
||||
reportSpecResults: function (spec) {
|
||||
// Finish timing this spec and calculate duration/delta (in sec)
|
||||
spec.finishedAt = new Date();
|
||||
// If the spec was skipped, reportSpecStarting is never called and spec.startedAt is undefined
|
||||
spec.durationSec = spec.startedAt ? elapsedSec(spec.startedAt.getTime(), spec.finishedAt.getTime()) : 0;
|
||||
},
|
||||
|
||||
reportSuiteResults: function (suite) {
|
||||
// Nothing to do
|
||||
},
|
||||
|
||||
reportRunnerResults: function (runner) {
|
||||
var suites = runner.suites(),
|
||||
i, j, ilen;
|
||||
|
||||
// Attach results to the "jasmine" object to make those results easy to scrap/find
|
||||
jasmine.runnerResults = {
|
||||
suites: [],
|
||||
durationSec : 0,
|
||||
passed : true
|
||||
};
|
||||
|
||||
// Loop over all the Suites
|
||||
for (i = 0, ilen = suites.length, j = 0; i < ilen; ++i) {
|
||||
if (suites[i].parentSuite === null) {
|
||||
jasmine.runnerResults.suites[j] = getSuiteData(suites[i]);
|
||||
// If 1 suite fails, the whole runner fails
|
||||
jasmine.runnerResults.passed = !jasmine.runnerResults.suites[j].passed ? false : jasmine.runnerResults.passed;
|
||||
// Add up all the durations
|
||||
jasmine.runnerResults.durationSec += jasmine.runnerResults.suites[j].durationSec;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
// Decorate the 'jasmine' object with getters
|
||||
jasmine.getJSReport = function () {
|
||||
if (jasmine.runnerResults) {
|
||||
return jasmine.runnerResults;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
jasmine.getJSReportAsString = function () {
|
||||
return JSON.stringify(jasmine.getJSReport());
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// export public
|
||||
jasmine.JSReporter = JSReporter;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Jasmine JSReporter for Jasmine 2.0
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
Simple timer implementation
|
||||
*/
|
||||
var Timer = function () {};
|
||||
|
||||
Timer.prototype.start = function () {
|
||||
this.startTime = new Date().getTime();
|
||||
return this;
|
||||
};
|
||||
|
||||
Timer.prototype.elapsed = function () {
|
||||
if (this.startTime == null) {
|
||||
return -1;
|
||||
}
|
||||
return new Date().getTime() - this.startTime;
|
||||
};
|
||||
|
||||
/*
|
||||
Utility methods
|
||||
*/
|
||||
var _extend = function (obj1, obj2) {
|
||||
for (var prop in obj2) {
|
||||
obj1[prop] = obj2[prop];
|
||||
}
|
||||
return obj1;
|
||||
};
|
||||
var _clone = function (obj) {
|
||||
if (obj !== Object(obj)) {
|
||||
return obj;
|
||||
}
|
||||
return _extend({}, obj);
|
||||
};
|
||||
|
||||
jasmine.JSReporter2 = function () {
|
||||
this.specs = {};
|
||||
this.suites = {};
|
||||
this.rootSuites = [];
|
||||
this.suiteStack = [];
|
||||
|
||||
// export methods under jasmine namespace
|
||||
jasmine.getJSReport = this.getJSReport;
|
||||
jasmine.getJSReportAsString = this.getJSReportAsString;
|
||||
};
|
||||
|
||||
var JSR = jasmine.JSReporter2.prototype;
|
||||
|
||||
// Reporter API methods
|
||||
// --------------------
|
||||
|
||||
JSR.suiteStarted = function (suite) {
|
||||
suite = this._cacheSuite(suite);
|
||||
// build up suite tree as we go
|
||||
suite.specs = [];
|
||||
suite.suites = [];
|
||||
suite.passed = true;
|
||||
suite.parentId = this.suiteStack.slice(this.suiteStack.length - 1)[0];
|
||||
if (suite.parentId) {
|
||||
this.suites[suite.parentId].suites.push(suite);
|
||||
} else {
|
||||
this.rootSuites.push(suite.id);
|
||||
}
|
||||
this.suiteStack.push(suite.id);
|
||||
suite.timer = new Timer().start();
|
||||
};
|
||||
|
||||
JSR.suiteDone = function (suite) {
|
||||
suite = this._cacheSuite(suite);
|
||||
suite.duration = suite.timer.elapsed();
|
||||
suite.durationSec = suite.duration / 1000;
|
||||
this.suiteStack.pop();
|
||||
|
||||
// maintain parent suite state
|
||||
var parent = this.suites[suite.parentId];
|
||||
if (parent) {
|
||||
parent.passed = parent.passed && suite.passed;
|
||||
}
|
||||
|
||||
// keep report representation clean
|
||||
delete suite.timer;
|
||||
delete suite.id;
|
||||
delete suite.parentId;
|
||||
delete suite.fullName;
|
||||
};
|
||||
|
||||
JSR.specStarted = function (spec) {
|
||||
spec = this._cacheSpec(spec);
|
||||
spec.timer = new Timer().start();
|
||||
// build up suites->spec tree as we go
|
||||
spec.suiteId = this.suiteStack.slice(this.suiteStack.length - 1)[0];
|
||||
this.suites[spec.suiteId].specs.push(spec);
|
||||
};
|
||||
|
||||
JSR.specDone = function (spec) {
|
||||
spec = this._cacheSpec(spec);
|
||||
|
||||
spec.duration = spec.timer.elapsed();
|
||||
spec.durationSec = spec.duration / 1000;
|
||||
|
||||
spec.skipped = spec.status === 'pending';
|
||||
spec.passed = spec.skipped || spec.status === 'passed';
|
||||
|
||||
spec.totalCount = spec.passedExpectations.length + spec.failedExpectations.length;
|
||||
spec.passedCount = spec.passedExpectations.length;
|
||||
spec.failedCount = spec.failedExpectations.length;
|
||||
spec.failures = [];
|
||||
|
||||
for (var i = 0, j = spec.failedExpectations.length; i < j; i++) {
|
||||
var fail = spec.failedExpectations[i];
|
||||
spec.failures.push({
|
||||
type: 'expect',
|
||||
expected: fail.expected,
|
||||
passed: false,
|
||||
message: fail.message,
|
||||
matcherName: fail.matcherName,
|
||||
trace: {
|
||||
stack: fail.stack
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// maintain parent suite state
|
||||
var parent = this.suites[spec.suiteId];
|
||||
if (spec.failed) {
|
||||
parent.failingSpecs.push(spec);
|
||||
}
|
||||
parent.passed = parent.passed && spec.passed;
|
||||
|
||||
// keep report representation clean
|
||||
delete spec.timer;
|
||||
delete spec.totalExpectations;
|
||||
delete spec.passedExpectations;
|
||||
delete spec.suiteId;
|
||||
delete spec.fullName;
|
||||
delete spec.id;
|
||||
delete spec.status;
|
||||
delete spec.failedExpectations;
|
||||
};
|
||||
|
||||
JSR.jasmineDone = function () {
|
||||
this._buildReport();
|
||||
};
|
||||
|
||||
JSR.getJSReport = function () {
|
||||
if (jasmine.jsReport) {
|
||||
return jasmine.jsReport;
|
||||
}
|
||||
};
|
||||
|
||||
JSR.getJSReportAsString = function () {
|
||||
if (jasmine.jsReport) {
|
||||
return JSON.stringify(jasmine.jsReport);
|
||||
}
|
||||
};
|
||||
|
||||
// Private methods
|
||||
// ---------------
|
||||
|
||||
JSR._haveSpec = function (spec) {
|
||||
return this.specs[spec.id] != null;
|
||||
};
|
||||
|
||||
JSR._cacheSpec = function (spec) {
|
||||
var existing = this.specs[spec.id];
|
||||
if (existing == null) {
|
||||
existing = this.specs[spec.id] = _clone(spec);
|
||||
} else {
|
||||
_extend(existing, spec);
|
||||
}
|
||||
return existing;
|
||||
};
|
||||
|
||||
JSR._haveSuite = function (suite) {
|
||||
return this.suites[suite.id] != null;
|
||||
};
|
||||
|
||||
JSR._cacheSuite = function (suite) {
|
||||
var existing = this.suites[suite.id];
|
||||
if (existing == null) {
|
||||
existing = this.suites[suite.id] = _clone(suite);
|
||||
} else {
|
||||
_extend(existing, suite);
|
||||
}
|
||||
return existing;
|
||||
};
|
||||
|
||||
JSR._buildReport = function () {
|
||||
var overallDuration = 0;
|
||||
var overallPassed = true;
|
||||
var overallSuites = [];
|
||||
|
||||
for (var i = 0, j = this.rootSuites.length; i < j; i++) {
|
||||
var suite = this.suites[this.rootSuites[i]];
|
||||
overallDuration += suite.duration;
|
||||
overallPassed = overallPassed && suite.passed;
|
||||
overallSuites.push(suite);
|
||||
}
|
||||
|
||||
jasmine.jsReport = {
|
||||
passed: overallPassed,
|
||||
durationSec: overallDuration / 1000,
|
||||
suites: overallSuites
|
||||
};
|
||||
};
|
||||
|
||||
})(jasmine);
|
||||
@@ -0,0 +1,5 @@
|
||||
@import "svg-gradient-mixin.less";
|
||||
|
||||
.gray-gradient {
|
||||
.gradient-mixin(#999);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.gradient-mixin(@color) {
|
||||
background: svg-gradient(to bottom,
|
||||
fade(@color, 0%) 0%,
|
||||
fade(@color, 5%) 60%,
|
||||
fade(@color, 10%) 70%,
|
||||
fade(@color, 15%) 73%,
|
||||
fade(@color, 20%) 75%,
|
||||
fade(@color, 25%) 80%,
|
||||
fade(@color, 30%) 85%,
|
||||
fade(@color, 35%) 88%,
|
||||
fade(@color, 40%) 90%,
|
||||
fade(@color, 45%) 95%,
|
||||
fade(@color, 50%) 100%
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "imports/urls.less";
|
||||
@import "http://localhost:8081/test/browser/less/imports/urls2.less";
|
||||
@import "http://localhost:8081/test/browser/less/nested-gradient-with-svg-gradient/mixin-consumer.less";
|
||||
@font-face {
|
||||
src: url("/fonts/garamond-pro.ttf");
|
||||
src: local(Futura-Medium),
|
||||
|
||||
@@ -10,34 +10,42 @@ var less = {logLevel: 4, errorReporting: "console"};
|
||||
var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
|
||||
testSheets = [];
|
||||
|
||||
// IE 8-10 does not support less in style tags
|
||||
if (window.navigator.userAgent.indexOf("MSIE") >= 0) {
|
||||
testFiles.length = 0;
|
||||
}
|
||||
|
||||
// setup style tags with less and link tags pointing to expected css output
|
||||
|
||||
for (var i = 0; i < testFiles.length; i++) {
|
||||
var file = testFiles[i],
|
||||
lessPath = '/test/less/' + file + '.less',
|
||||
cssPath = '/test/css/' + file + '.css',
|
||||
lessStyle = document.createElement('style'),
|
||||
cssLink = document.createElement('link'),
|
||||
lessText = '@import "' + lessPath + '";';
|
||||
var file = testFiles[i],
|
||||
lessPath = '/test/less/' + file + '.less',
|
||||
cssPath = '/test/css/' + file + '.css',
|
||||
lessStyle = document.createElement('style'),
|
||||
cssLink = document.createElement('link'),
|
||||
lessText = '@import "' + lessPath + '";';
|
||||
|
||||
lessStyle.type = 'text/less';
|
||||
lessStyle.id = file;
|
||||
lessStyle.href = file;
|
||||
lessStyle.type = 'text/less';
|
||||
lessStyle.id = file;
|
||||
lessStyle.href = file;
|
||||
|
||||
if (lessStyle.styleSheet) {
|
||||
lessStyle.styleSheet.cssText = lessText;
|
||||
} else {
|
||||
lessStyle.innerHTML = lessText;
|
||||
}
|
||||
if (lessStyle.styleSheet === undefined) {
|
||||
lessStyle.appendChild(document.createTextNode(lessText));
|
||||
}
|
||||
|
||||
cssLink.rel = 'stylesheet';
|
||||
cssLink.type = 'text/css';
|
||||
cssLink.href = cssPath;
|
||||
cssLink.id = 'expected-' + file;
|
||||
cssLink.rel = 'stylesheet';
|
||||
cssLink.type = 'text/css';
|
||||
cssLink.href = cssPath;
|
||||
cssLink.id = 'expected-' + file;
|
||||
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
|
||||
head.appendChild(lessStyle);
|
||||
head.appendChild(cssLink);
|
||||
testSheets[i] = lessStyle;
|
||||
}
|
||||
head.appendChild(lessStyle);
|
||||
|
||||
if (lessStyle.styleSheet) {
|
||||
lessStyle.styleSheet.cssText = lessText;
|
||||
}
|
||||
|
||||
head.appendChild(cssLink);
|
||||
testSheets[i] = lessStyle;
|
||||
}
|
||||
|
||||
@@ -2,4 +2,3 @@ var less = {
|
||||
strictUnits: true,
|
||||
strictMath: true,
|
||||
logLevel: 4 };
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
describe("less.js error tests", function() {
|
||||
testLessErrorsInDocument();
|
||||
testLessErrorsInDocument();
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user