Merge branch 'grunt-wip'

This commit is contained in:
Luke Page
2013-09-09 08:02:30 +01:00
50 changed files with 1612 additions and 537 deletions

14
.gitignore vendored
View File

@@ -1,10 +1,20 @@
node_modules
# OS and IDE
.emacs*
*.flymake
*~
.#*
.idea
*.sublime-*
# npm
node_modules
npm-debug.log
# project-specific
tmp
test/browser/less.js
test/browser/test-runner-*.htm
test/sourcemaps/*.map
test/sourcemaps/*.css
# grunt
.grunt

View File

@@ -1,5 +0,0 @@
benchmark/
build/
dist/
node_modules/
test/browser/

View File

@@ -3,7 +3,7 @@
> We welcome feature requests and bug reports. Please read these guidelines before submitting one.
<span class="warning">**Words that begin with the at sign (`@`) must be wrapped in backticks!** </span>. as a courtesy to avoid sending notifications to any user that might have the `@username` being referenced. Remember, usernames start with the at sign.
<span class="warning">**Words that begin with the at sign (`@`) must be wrapped in backticks!** </span>. As a courtesy to avoid sending notifications to any user that might have the `@username` being referenced, please remember that GitHub usernames also start with the at sign. If you don't wrap them in backticks, users will get unintended notifications from you.
GitHub has other great markdown features as well, [go here to learn more about them](https://help.github.com/articles/github-flavored-markdown).

293
Gruntfile.js Normal file
View File

@@ -0,0 +1,293 @@
'use strict';
module.exports = function(grunt) {
// Report the elapsed execution time of tasks.
require('time-grunt')(grunt);
// Project configuration.
grunt.initConfig({
// Metadata required for build.
build: grunt.file.readYAML('build/build.yml'),
pkg: grunt.file.readJSON('package.json'),
meta: {
license: '<%= _.pluck(pkg.licenses, "type").join(", ") %>',
copyright: 'Copyright (c) 2009-<%= grunt.template.today("yyyy") %>',
banner:
'/* \n' +
' * LESS - <%= pkg.description %> v<%= pkg.version %> \n' +
' * http://lesscss.org \n' +
' * \n' +
' * <%= meta.copyright %>, <%= pkg.author.name %> <<%= pkg.author.email %>> \n' +
' * Licensed under the <%= meta.license %> License. \n' +
' * \n' +
' * @licence \n' +
' */ \n\n'
},
shell: {
options: {stdout: true, failOnError: true},
test: {
command: 'node test/less-test.js'
},
benchmark: {
command: 'node benchmark/less-benchmark.js'
},
"browsertest-server": {
command: 'node node_modules/http-server/bin/http-server . -p 8088'
},
"sourcemap-test": {
command: [
'node bin/lessc --source-map --source-map-inline test/less/import.less test/sourcemaps/import.css',
'node bin/lessc --source-map --source-map-inline test/less/sourcemaps/basic.less test/sourcemaps/basic.css',
'node node_modules/http-server/bin/http-server test/sourcemaps -p 8084'].join('&&')
}
},
concat: {
options: {
stripBanners: 'all',
banner: '<%= meta.banner %>\n\n(function (window, undefined) {',
footer: '\n})(window);'
},
// Browser versions
browsertest: {
src: ['<%= build.browser %>'],
dest: 'test/browser/less.js'
},
alpha: {
src: ['<%= build.browser %>'],
dest: 'dist/less-<%= pkg.version %>-alpha.js'
},
beta: {
src: ['<%= build.browser %>'],
dest: 'dist/less-<%= pkg.version %>-beta.js'
},
stable: {
src: ['<%= build.browser %>'],
dest: 'dist/less-<%= pkg.version %>.js'
},
// Rhino
rhino: {
options: {
banner: '/* LESS.js v<%= pkg.version %> RHINO | <%= meta.copyright %>, <%= pkg.author.name %> <<%= pkg.author.email %>> */\n\n',
footer: '' // override task-level footer
},
src: ['<%= build.rhino %>'],
dest: 'dist/less-rhino-<%= pkg.version %>.js'
},
// Generate readme
readme: {
// override task-level banner and footer
options: {process: true, banner: '', footer: ''},
src: ['build/README.md'],
dest: 'README.md'
}
},
uglify: {
options: {
banner: '<%= meta.banner %>',
mangle: true
},
alpha: {
src: ['<%= concat.alpha.src %>'],
dest: 'dist/less-<%= pkg.version %>-alpha.min.js'
},
beta: {
src: ['<%= concat.beta.src %>'],
dest: 'dist/less-<%= pkg.version %>-beta.min.js'
},
stable: {
src: ['<%= concat.browser.src %>'],
dest: 'dist/less-<%= pkg.version %>.min.js'
}
},
jshint: {
options: {jshintrc: '.jshintrc'},
files: {
src: [
'Gruntfile.js',
'lib/**/*.js'
]
}
},
connect: {
server: {
options: {
port: 8081
}
}
},
jasmine: {
options: {
// version: '2.0.0-rc2',
keepRunner: true,
host: 'http://localhost:8081/',
vendor: ['test/browser/common.js', 'test/browser/less.js'],
template: 'test/browser/test-runner-template.tmpl'
},
main: {
// src is used to build list of less files to compile
src: ['test/less/*.less', '!test/less/javascript.less', '!test/less/urls.less'],
options: {
helpers: 'test/browser/runner-main-options.js',
specs: 'test/browser/runner-main-spec.js',
outfile: 'tmp/browser/test-runner-main.html'
}
},
legacy: {
src: ['test/less/legacy/*.less'],
options: {
helpers: 'test/browser/runner-legacy-options.js',
specs: 'test/browser/runner-legacy-spec.js',
outfile: 'tmp/browser/test-runner-legacy.html'
}
},
errors: {
src: ['test/less/errors/*.less', '!test/less/errors/javascript-error.less'],
options: {
timeout: 20000,
helpers: 'test/browser/runner-errors-options.js',
specs: 'test/browser/runner-errors-spec.js',
outfile: 'tmp/browser/test-runner-errors.html'
}
},
noJsErrors: {
src: ['test/less/no-js-errors/*.less'],
options: {
helpers: 'test/browser/runner-no-js-errors-options.js',
specs: 'test/browser/runner-no-js-errors-spec.js',
outfile: 'tmp/browser/test-runner-no-js-errors.html'
}
},
browser: {
src: ['test/browser/less/*.less'],
options: {
helpers: 'test/browser/runner-browser-options.js',
specs: 'test/browser/runner-browser-spec.js',
outfile: 'tmp/browser/test-runner-browser.html'
}
},
relativeUrls: {
src: ['test/browser/less/relative-urls/*.less'],
options: {
helpers: 'test/browser/runner-relative-urls-options.js',
specs: 'test/browser/runner-relative-urls-spec.js',
outfile: 'tmp/browser/test-runner-relative-urls.html'
}
},
rootpath: {
src: ['test/browser/less/rootpath/*.less'],
options: {
helpers: 'test/browser/runner-rootpath-options.js',
specs: 'test/browser/runner-rootpath-spec.js',
outfile: 'tmp/browser/test-runner-rootpath.html'
}
},
rootpathRelative: {
src: ['test/browser/less/rootpath-relative/*.less'],
options: {
helpers: 'test/browser/runner-rootpath-relative-options.js',
specs: 'test/browser/runner-rootpath-relative-spec.js',
outfile: 'tmp/browser/test-runner-rootpath-relative.html'
}
},
production: {
src: ['test/browser/less/production/*.less'],
options: {
helpers: 'test/browser/runner-production-options.js',
specs: 'test/browser/runner-production-spec.js',
outfile: 'tmp/browser/test-runner-production.html'
}
},
modifyVars: {
src: ['test/browser/less/modify-vars/*.less'],
options: {
helpers: 'test/browser/runner-modify-vars-options.js',
specs: 'test/browser/runner-modify-vars-spec.js',
outfile: 'tmp/browser/test-runner-modify-vars.html'
}
}
},
// Clean the version of less built for the tests
clean: {
test: ['test/browser/less.js', 'tmp'],
"sourcemap-test": ['test/sourcemaps/*.css', 'test/sourcemaps/*.map']
}
});
// Load these plugins to provide the necessary tasks
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
// Actually load this plugin's task(s).
grunt.loadTasks('build/tasks');
// by default, run tests
grunt.registerTask('default', [
'test'
]);
// Alpha
grunt.registerTask('alpha', [
'concat:alpha',
'uglify:alpha'
]);
// Beta
grunt.registerTask('beta', [
'concat:beta',
'uglify:beta'
]);
// Beta
grunt.registerTask('stable', [
'concat:stable',
'uglify:stable'
]);
// Run all browser tests
grunt.registerTask('browsertest', [
'browser',
'connect',
'jasmine'
]);
// setup a web server to run the browser tests in a browser rather than phantom
grunt.registerTask('browsertest-server', [
'shell:browsertest-server'
]);
// Create the browser version of less.js
grunt.registerTask('browser', [
'concat:browsertest'
]);
// Run all tests
grunt.registerTask('test', [
'clean',
'jshint',
'shell:test',
'browsertest'
]);
// generate a good test environment for testing sourcemaps
grunt.registerTask('sourcemap-test', [
'clean:sourcemap-test',
'shell:sourcemap-test'
]);
// Run benchmark
grunt.registerTask('benchmark', [
'shell:benchmark'
]);
// Readme.
grunt.registerTask('readme', [
'concat:readme'
]);
};

114
Makefile
View File

@@ -1,114 +0,0 @@
#
# Run all tests
#
test:
node test/less-test.js
#
# Run benchmark
#
benchmark:
node benchmark/less-benchmark.js
#
# Build less.js
#
SRC = lib/less
HEADER = build/header.js
VERSION = `cat package.json | grep version \
| grep -o '[0-9]\.[0-9]\.[0-9]\+'`
DIST = dist/less-${VERSION}.js
RHINO = dist/less-rhino-${VERSION}.js
DIST_MIN = dist/less-${VERSION}.min.js
browser-prepare: DIST := test/browser/less.js
alpha: DIST := dist/less-${VERSION}-alpha.js
alpha: DIST_MIN := dist/less-${VERSION}-alpha.min.js
beta: DIST := dist/less-${VERSION}-beta.js
beta: DIST_MIN := dist/less-${VERSION}-beta.min.js
less:
@@mkdir -p dist
@@touch ${DIST}
@@cat ${HEADER} | sed s/@VERSION/${VERSION}/ > ${DIST}
@@echo "(function (window, undefined) {" >> ${DIST}
@@cat build/require.js\
build/browser-header.js\
${SRC}/parser.js\
${SRC}/functions.js\
${SRC}/colors.js\
${SRC}/tree.js\
${SRC}/tree/*.js\
${SRC}/env.js\
${SRC}/visitor.js\
${SRC}/import-visitor.js\
${SRC}/join-selector-visitor.js\
${SRC}/to-css-visitor.js\
${SRC}/extend-visitor.js\
${SRC}/browser.js\
build/amd.js >> ${DIST}
@@echo "})(window);" >> ${DIST}
@@echo ${DIST} built.
browser-prepare: less
node test/browser-test-prepare.js
browser-test: browser-prepare
phantomjs test/browser/phantom-runner.js
browser-test-server: browser-prepare
phantomjs test/browser/phantom-runner.js --no-tests
jshint:
node_modules/.bin/jshint --config ./.jshintrc .
test-sourcemaps:
node bin/lessc --source-map --source-map-inline test/less/import.less test/sourcemaps/import.css
node bin/lessc --source-map --source-map-inline test/less/sourcemaps/basic.less test/sourcemaps/basic.css
node node_modules/http-server/bin/http-server test/sourcemaps -p 8083
rhino:
@@mkdir -p dist
@@touch ${RHINO}
@@cat build/require-rhino.js\
build/rhino-header.js\
${SRC}/parser.js\
${SRC}/env.js\
${SRC}/visitor.js\
${SRC}/import-visitor.js\
${SRC}/join-selector-visitor.js\
${SRC}/to-css-visitor.js\
${SRC}/extend-visitor.js\
${SRC}/functions.js\
${SRC}/colors.js\
${SRC}/tree/*.js\
${SRC}/tree.js\
${SRC}/rhino.js > ${RHINO}
@@echo ${RHINO} built.
min: less
@@echo minifying...
@@uglifyjs ${DIST} > ${DIST_MIN}
@@echo ${DIST_MIN} built.
alpha: min
beta: min
alpha-release: alpha
git add dist/*.js
git commit -m "Update alpha ${VERSION}"
dist: min rhino
git add dist/*
git commit -a -m "(dist) build ${VERSION}"
git archive master --prefix=less/ -o less-${VERSION}.tar.gz
npm publish less-${VERSION}.tar.gz
stable:
npm tag less@${VERSION} stable
.PHONY: test benchmark

341
README.md
View File

@@ -1,20 +1,335 @@
less.js
=======
# [Less.js v1.5.0-b1](http://lesscss.org)
The **dynamic** stylesheet language.
<http://lesscss.org>
about
-----
> The **dynamic** stylesheet language. [http://lesscss.org](http://lesscss.org).
This is the JavaScript, and now official, stable version of LESS.
For more information on the language and usage visit [lesscss.org](http://lesscss.org). More information also available [in our wiki](https://github.com/less/less.js/wiki)
license
-------
## Getting Started
See `LICENSE` file.
Options for adding Less.js to your project:
> Copyright (c) 2009-2013 Alexis Sellier & The Core Less Team
* 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`
## Feature Highlights
LESS extends CSS with dynamic features such as:
* [nesting](#nesting)
* [variables](#variables)
* [operations](#operations)
* [mixins](#mixins)
* [extend](#extend) (selector inheritance)
To learn about the many other features Less.js has to offer please visit [http://lesscss.org](http://lesscss.org) and [the Less.js wiki][wiki]
### Examples
#### nesting
Take advantage of nesting to make code more readable and maintainable. This:
```less
.nav > li > a {
border: 1px solid #f5f5f5;
&:hover {
border-color: #ddd;
}
}
```
renders to:
```css
.nav > li > a {
border: 1px solid #f5f5f5;
}
.nav > li > a:hover {
border-color: #ddd;
}
```
#### variables
Updated commonly used values from a single location.
```less
// Variables ("inline" comments like this can be used)
@link-color: #428bca; // appears as "sea blue"
/* Or "block comments" that span
multiple lines, like this */
a {
color: @link-color; // use the variable in styles
}
```
Variables can also be used in `@import` statements, URLs, selector names, and more.
#### operations
Continuing with the same example above, we can use our variables even easier to maintain with _operations_, which enables the use of addition, subraction, multiplication and division in your styles:
```less
// Variables
@link-color: #428bca;
@link-color-hover: darken(@link-color, 10%);
// Styles
a {
color: @link-color;
}
a:hover {
color: @link-color-hover;
}
```
renders to:
```css
a {
color: #428bca;
}
a:hover {
color: #3071a9;
}
```
#### mixins
##### "implicit" mixins
Mixins enable you to apply the styles of one selector inside another selector like this:
```less
// Any "regular" class...
.link {
color: @link-color;
}
a {
font-weight: bold;
.link; // ...can be used as an "implicit" mixin
}
```
renders to:
```css
.link {
color: #428bca;
}
a {
font-weight: bold;
color: #428bca;
}
```
So any selector can be an "implicit mixin". We'll show you a DRYer way to do this below.
##### parametric mixins
Mixins can also accept parameters:
```less
// Transition mixin
.transition(@transition) {
-webkit-transition: @transition;
-moz-transition: @transition;
-o-transition: @transition;
transition: @transition;
}
```
used like this:
```less
a {
font-weight: bold;
color: @link-color;
.transition(color .2s ease-in-out);
// Hover state
&:hover {
color: @link-color-hover;
}
}
```
renders to:
```css
a {
font-weight: bold;
color: #428bca;
-webkit-transition: color 0.2s ease-in-out;
-moz-transition: color 0.2s ease-in-out;
-o-transition: color 0.2s ease-in-out;
transition: color 0.2s ease-in-out;
}
a:hover {
color: #3071a9;
}
```
#### extend
The `extend` feature can be thought of as the _inverse_ of mixins. It accomplishes the goal of "borrowing styles", but rather than copying all the rules of _Selector A_ over to _Selector B_, `extend` copies the name of the _inheriting selector_ (_Selector B_) over to the _extending selector_ (_Selector A_). So continuing with the example used for [mixins](#mixins) above, extend works like this:
```less
.link {
color: @link-color;
}
a:extend(.link) {
font-weight: bold;
}
// Can also be written as
a {
&:extend(.link);
font-weight: bold;
}
```
renders to:
```css
.link, a {
color: #428bca;
}
```
## Usage
### Compiling and Parsing
Invoke the compiler from node:
```javascript
var less = require('less');
less.render('.class { width: (1 + 1) }', function (e, css) {
console.log(css);
});
```
Outputs:
```css
.class {
width: 2;
}
```
You may also manually invoke the parser and compiler:
```javascript
var parser = new(less.Parser);
parser.parse('.class { width: (1 + 1) }', function (err, tree) {
if (err) { return console.error(err) }
console.log(tree.toCSS());
});
```
### Configuration
You may also pass options to the compiler:
```javascript
var parser = new(less.Parser)({
paths: ['.', './src/less'], // Specify search paths for @import directives
filename: 'style.less' // Specify a filename, for better error messages
});
parser.parse('.class { width: (1 + 1) }', function (e, tree) {
tree.toCSS({ compress: true }); // Minify CSS output
});
```
## More information
For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org) or [the less wiki][wiki].
Here are other resources for using Less.js:
* [stackoverflow.com][so] is a great place to get answers about Less.
* [node.js tools](https://github.com/less/less.js/wiki/Converting-LESS-to-CSS) for converting Less to CSS
* [GUI compilers for Less](https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js)
* [Less.js Issues][issues] for reporting bugs
## 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/).
### 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].
### Development
#### Install Less.js
Start by either [downloading this project][download] manually, or in the command line:
```shell
git clone https://github.com/less/less.js.git "less"
```
and then `cd less`.
#### Install dependencies
To install all the dependencies for less development, run:
```shell
npm install
```
If you haven't run grunt before, install grunt-cli globally so you can just run `grunt`
```shell
npm install grunt-cli -g
```
You should now be able to build Less.js, run tests, benchmarking, and other tasks listed in the Gruntfile.
## Using Less.js Grunt
Tests, benchmarking and building is done using Grunt `~0.4.1`. If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to install and use Grunt plugins, which are necessary for development with Less.js.
The Less.js [Gruntfile](Gruntfile.js) is configured with the following "convenience tasks" :
#### test - `grunt`
Runs jshint, nodeunit and headless jasmine tests using [phantomjs](http://code.google.com/p/phantomjs/). You must have phantomjs installed for the jasmine tests to run.
#### test - `grunt benchmark`
Runs the benchmark suite.
#### build for testing browser - 'grunt browser'
This builds less.js and puts it in 'test/browser/less.js'
#### build - `grunt stable | grunt beta | grunt alpha`
Builds Less.js from from the `/lib/less` source files. This is done by the developer releasing a new release, do not do this if you are creating a pull request.
#### readme - `grunt readme`
Build the README file from [a template](build/README.md) to ensure that metadata is up-to-date and (more likely to be) correct.
Please review the [Gruntfile](Gruntfile.js) to become acquainted with the other available tasks.
**Please note** that if you have any issues installing dependencies or running any of the Gruntfile commands, please make sure to uninstall any previous versions, both in the local node_modules directory, and clear your global npm cache, and then try running `npm install` again. After that if you still have issues, please let us know about it so we can help.
## Release History
See the [changelog](CHANGELOG)
## [License](LICENSE)
Copyright (c) 2009-2013 [Alexis Sellier](http://cloudhead.io/) & The Core Less Team
Licensed under the [Apache License](LICENSE).
[so]: http://stackoverflow.com/questions/tagged/twitter-bootstrap+less "StackOverflow.com"
[issues]: https://github.com/less/less.js/issues "GitHub Issues for Less.js"
[wiki]: https://github.com/less/less.js/wiki "The official wiki for Less.js"
[download]: https://github.com/less/less.js/zipball/master "Download Less.js"

View File

@@ -16,18 +16,17 @@ fs.readFile(file, 'utf8', function (e, data) {
start = new(Date);
new(less.Parser)({ optimization: 2 }).parse(data, function (err, tree) {
end = new(Date);
end = new Date();
total = end - start;
sys.puts("Parsing: " +
total + " ms (" +
parseInt(1000 / total *
data.length / 1024) + " KB\/s)");
Number(1000 / total * data.length / 1024) + " KB\/s)");
start = new(Date);
start = new Date();
css = tree.toCSS();
end = new(Date);
end = new Date();
sys.puts("Generation: " + (end - start) + " ms (" +
parseInt(1000 / (end - start) *
@@ -36,7 +35,7 @@ fs.readFile(file, 'utf8', function (e, data) {
total += end - start;
sys.puts("Total: " + total + "ms (" +
parseInt(1000 / total * data.length / 1024) + " KB/s)");
Number(1000 / total * data.length / 1024) + " KB/s)");
if (err) {
less.writeError(err);

335
build/README.md Normal file
View File

@@ -0,0 +1,335 @@
# [Less.js v<%= pkg.version %>](http://lesscss.org)
> The **dynamic** stylesheet language. [http://lesscss.org](http://lesscss.org).
This is the JavaScript, and now official, stable version of LESS.
## Getting Started
Options for adding Less.js to your project:
* 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`
## Feature Highlights
LESS extends CSS with dynamic features such as:
* [nesting](#nesting)
* [variables](#variables)
* [operations](#operations)
* [mixins](#mixins)
* [extend](#extend) (selector inheritance)
To learn about the many other features Less.js has to offer please visit [http://lesscss.org](http://lesscss.org) and [the Less.js wiki][wiki]
### Examples
#### nesting
Take advantage of nesting to make code more readable and maintainable. This:
```less
.nav > li > a {
border: 1px solid #f5f5f5;
&:hover {
border-color: #ddd;
}
}
```
renders to:
```css
.nav > li > a {
border: 1px solid #f5f5f5;
}
.nav > li > a:hover {
border-color: #ddd;
}
```
#### variables
Updated commonly used values from a single location.
```less
// Variables ("inline" comments like this can be used)
@link-color: #428bca; // appears as "sea blue"
/* Or "block comments" that span
multiple lines, like this */
a {
color: @link-color; // use the variable in styles
}
```
Variables can also be used in `@import` statements, URLs, selector names, and more.
#### operations
Continuing with the same example above, we can use our variables even easier to maintain with _operations_, which enables the use of addition, subraction, multiplication and division in your styles:
```less
// Variables
@link-color: #428bca;
@link-color-hover: darken(@link-color, 10%);
// Styles
a {
color: @link-color;
}
a:hover {
color: @link-color-hover;
}
```
renders to:
```css
a {
color: #428bca;
}
a:hover {
color: #3071a9;
}
```
#### mixins
##### "implicit" mixins
Mixins enable you to apply the styles of one selector inside another selector like this:
```less
// Any "regular" class...
.link {
color: @link-color;
}
a {
font-weight: bold;
.link; // ...can be used as an "implicit" mixin
}
```
renders to:
```css
.link {
color: #428bca;
}
a {
font-weight: bold;
color: #428bca;
}
```
So any selector can be an "implicit mixin". We'll show you a DRYer way to do this below.
##### parametric mixins
Mixins can also accept parameters:
```less
// Transition mixin
.transition(@transition) {
-webkit-transition: @transition;
-moz-transition: @transition;
-o-transition: @transition;
transition: @transition;
}
```
used like this:
```less
a {
font-weight: bold;
color: @link-color;
.transition(color .2s ease-in-out);
// Hover state
&:hover {
color: @link-color-hover;
}
}
```
renders to:
```css
a {
font-weight: bold;
color: #428bca;
-webkit-transition: color 0.2s ease-in-out;
-moz-transition: color 0.2s ease-in-out;
-o-transition: color 0.2s ease-in-out;
transition: color 0.2s ease-in-out;
}
a:hover {
color: #3071a9;
}
```
#### extend
The `extend` feature can be thought of as the _inverse_ of mixins. It accomplishes the goal of "borrowing styles", but rather than copying all the rules of _Selector A_ over to _Selector B_, `extend` copies the name of the _inheriting selector_ (_Selector B_) over to the _extending selector_ (_Selector A_). So continuing with the example used for [mixins](#mixins) above, extend works like this:
```less
.link {
color: @link-color;
}
a:extend(.link) {
font-weight: bold;
}
// Can also be written as
a {
&:extend(.link);
font-weight: bold;
}
```
renders to:
```css
.link, a {
color: #428bca;
}
```
## Usage
### Compiling and Parsing
Invoke the compiler from node:
```javascript
var less = require('less');
less.render('.class { width: (1 + 1) }', function (e, css) {
console.log(css);
});
```
Outputs:
```css
.class {
width: 2;
}
```
You may also manually invoke the parser and compiler:
```javascript
var parser = new(less.Parser);
parser.parse('.class { width: (1 + 1) }', function (err, tree) {
if (err) { return console.error(err) }
console.log(tree.toCSS());
});
```
### Configuration
You may also pass options to the compiler:
```javascript
var parser = new(less.Parser)({
paths: ['.', './src/less'], // Specify search paths for @import directives
filename: 'style.less' // Specify a filename, for better error messages
});
parser.parse('.class { width: (1 + 1) }', function (e, tree) {
tree.toCSS({ compress: true }); // Minify CSS output
});
```
## More information
For general information on the language, configuration options or usage visit [lesscss.org](http://lesscss.org) or [the less wiki][wiki].
Here are other resources for using Less.js:
* [stackoverflow.com][so] is a great place to get answers about Less.
* [node.js tools](https://github.com/less/less.js/wiki/Converting-LESS-to-CSS) for converting Less to CSS
* [GUI compilers for Less](https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js)
* [Less.js Issues][issues] for reporting bugs
## 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/).
### 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].
### Development
#### Install Less.js
Start by either [downloading this project][download] manually, or in the command line:
```shell
git clone https://github.com/less/less.js.git "less"
```
and then `cd less`.
#### Install dependencies
To install all the dependencies for less development, run:
```shell
npm install
```
If you haven't run grunt before, install grunt-cli globally so you can just run `grunt`
```shell
npm install grunt-cli -g
```
You should now be able to build Less.js, run tests, benchmarking, and other tasks listed in the Gruntfile.
## Using Less.js Grunt
Tests, benchmarking and building is done using Grunt `<%= pkg.devDependencies.grunt %>`. If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to install and use Grunt plugins, which are necessary for development with Less.js.
The Less.js [Gruntfile](Gruntfile.js) is configured with the following "convenience tasks" :
#### test - `grunt`
Runs jshint, nodeunit and headless jasmine tests using [phantomjs](http://code.google.com/p/phantomjs/). You must have phantomjs installed for the jasmine tests to run.
#### test - `grunt benchmark`
Runs the benchmark suite.
#### build for testing browser - 'grunt browser'
This builds less.js and puts it in 'test/browser/less.js'
#### build - `grunt stable | grunt beta | grunt alpha`
Builds Less.js from from the `/lib/less` source files. This is done by the developer releasing a new release, do not do this if you are creating a pull request.
#### readme - `grunt readme`
Build the README file from [a template](build/README.md) to ensure that metadata is up-to-date and (more likely to be) correct.
Please review the [Gruntfile](Gruntfile.js) to become acquainted with the other available tasks.
**Please note** that if you have any issues installing dependencies or running any of the Gruntfile commands, please make sure to uninstall any previous versions, both in the local node_modules directory, and clear your global npm cache, and then try running `npm install` again. After that if you still have issues, please let us know about it so we can help.
## Release History
See the [changelog](CHANGELOG)
## [License](LICENSE)
Copyright (c) 2009-<%= grunt.template.today("yyyy") %> [Alexis Sellier](http://cloudhead.io/) & The Core Less Team
Licensed under the [Apache License](LICENSE).
[so]: http://stackoverflow.com/questions/tagged/twitter-bootstrap+less "StackOverflow.com"
[issues]: https://github.com/less/less.js/issues "GitHub Issues for Less.js"
[wiki]: https://github.com/less/less.js/wiki "The official wiki for Less.js"
[download]: https://github.com/less/less.js/zipball/master "Download Less.js"

147
build/build.yml Normal file
View File

@@ -0,0 +1,147 @@
###
# NOTICE:
# this file is specifically for controlling
# paths for Less.js source files, as well as
# the order in which source files are
# concatenated.
#
# Please do not add paths for anything else
# to this file. All other paths for testing,
# benchmarking and so on should be controlled
# in the Gruntfile.
###
# Less.js Lib
lib: lib/less
# =================================
# General
# =================================
prepend:
browser: ['build/require.js', 'build/browser-header.js']
rhino: build/require-rhino.js
append:
amd: build/amd.js
browser: <%= build.lib %>/browser.js
rhino: <%= build.lib %>/rhino.js
# =================================
# Core less files
# =================================
# <%= build.less.* %>
less:
parser : <%= build.lib %>/parser.js
functions : <%= build.lib %>/functions.js
colors : <%= build.lib %>/colors.js
tree : <%= build.lib %>/tree.js
treedir : <%= build.lib %>/tree/*.js # glob all files in ./lib/less/tree directory
env : <%= build.lib %>/env.js
visitor : <%= build.lib %>/visitor.js
import_visitor : <%= build.lib %>/import-visitor.js
join : <%= build.lib %>/join-selector-visitor.js
to_css_visitor : <%= build.lib %>/to-css-visitor.js
extend_visitor : <%= build.lib %>/extend-visitor.js
browser : <%= build.lib %>/browser.js
source_map_output: <%= build.lib %>/source-map-output.js
# =================================
# Browser build
# =================================
# <%= build.browser %>
browser:
# prepend utils
- <%= build.prepend.browser %>
# core
- <%= build.less.parser %>
- <%= build.less.functions %>
- <%= build.less.colors %>
- <%= build.less.tree %>
- <%= build.less.treedir %> # glob all files
- <%= build.less.env %>
- <%= build.less.visitor %>
- <%= build.less.import_visitor %>
- <%= build.less.join %>
- <%= build.less.to_css_visitor %>
- <%= build.less.extend_visitor %>
- <%= build.less.source_map_output %>
# append browser-specific code
- <%= build.append.browser %>
- <%= build.append.amd %>
# =================================
# Rhino build
# =================================
# <%= build.rhino %>
rhino:
# prepend utils
- <%= build.prepend.rhino %>
# core
- <%= build.less.parser %>
- <%= build.less.env %>
- <%= build.less.visitor %>
- <%= build.less.import_visitor %>
- <%= build.less.join %>
- <%= build.less.to_css_visitor %>
- <%= build.less.extend_visitor %>
- <%= build.less.functions %>
- <%= build.less.colors %>
- <%= build.less.tree %>
- <%= build.less.treedir %> # glob all files
- <%= build.less.source_map_output %>
# append rhino-specific code
- <%= build.append.rhino %>
# =================================
# Tree files
# =================================
# <%= build.tree %>
# Technically listing the array out this way isn't
# necessary since we can glob the files in alphabetical
# order anyway. But this gives you control over the order
# the files are used, and allows targeting of individual
# files directly in the Gruntfile. But be we can just
# remove this if files can be concatenated in any order.
tree:
- <%= build.lib %>/tree/alpha.js
- <%= build.lib %>/tree/anonymous.js
- <%= build.lib %>/tree/assignment.js
- <%= build.lib %>/tree/call.js
- <%= build.lib %>/tree/color.js
- <%= build.lib %>/tree/comment.js
- <%= build.lib %>/tree/condition.js
- <%= build.lib %>/tree/dimension.js
- <%= build.lib %>/tree/directive.js
- <%= build.lib %>/tree/element.js
- <%= build.lib %>/tree/expression.js
- <%= build.lib %>/tree/extend.js
- <%= build.lib %>/tree/import.js
- <%= build.lib %>/tree/javascript.js
- <%= build.lib %>/tree/keyword.js
- <%= build.lib %>/tree/media.js
- <%= build.lib %>/tree/mixin.js
- <%= build.lib %>/tree/negative.js
- <%= build.lib %>/tree/operation.js
- <%= build.lib %>/tree/paren.js
- <%= build.lib %>/tree/quoted.js
- <%= build.lib %>/tree/rule.js
- <%= build.lib %>/tree/ruleset.js
- <%= build.lib %>/tree/selector.js
- <%= build.lib %>/tree/unicode-descriptor.js
- <%= build.lib %>/tree/url.js
- <%= build.lib %>/tree/value.js
- <%= build.lib %>/tree/variable.js

View File

@@ -1,9 +0,0 @@
/*
* LESS - Leaner CSS v@VERSION
* http://lesscss.org
*
* Copyright (c) 2009-2013, Alexis Sellier
* Licensed under the Apache 2.0 License.
*
* @licence
*/

1
build/tasks/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# Reserved for specialized Less.js tasks.

View File

@@ -3,7 +3,10 @@
"version": "1.5.0-b1",
"description": "Leaner CSS",
"homepage": "http://lesscss.org",
"author": "Alexis Sellier <self@cloudhead.net>",
"author": {
"name": "Alexis Sellier",
"email": "self@cloudhead.net"
},
"contributors": [
"The Core Less Team"
],
@@ -14,6 +17,12 @@
"type": "git",
"url": "https://github.com/less/less.js.git"
},
"licenses": [
{
"type": "Apache v2",
"url": "https://github.com/less/less.js/blob/master/LICENSE"
}
],
"bin": {
"lessc": "./bin/lessc"
},
@@ -28,8 +37,7 @@
"node": ">=0.4.2"
},
"scripts": {
"pretest": "make jshint",
"test": "make test"
"test": "grunt test"
},
"optionalDependencies": {
"mime": "1.2.x",
@@ -40,9 +48,18 @@
},
"devDependencies": {
"diff": "~1.0",
"jshint": "~2.1.4",
"http-server": "~0.5.5"
},
"grunt": "~0.4.1",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-connect": "~0.3.0",
"grunt-contrib-jasmine": "~0.5.1",
"grunt-contrib-jshint": "~0.6.0",
"grunt-contrib-uglify": "~0.2.2",
"grunt-shell": "~0.3.1",
"http-server": "~0.5.5",
"matchdep": "~0.1.2",
"time-grunt": "~0.1.1"
},
"keywords": [
"compile less",
"css nesting",
@@ -67,11 +84,5 @@
"stylesheet",
"variables in css",
"css less"
],
"licenses": [
{
"type": "Apache v2",
"url": "https://github.com/less/less.js/blob/master/LICENSE"
}
]
}
}

View File

@@ -1,48 +0,0 @@
var path = require('path'),
fs = require('fs');
var readDirFilesSync = function(dir, regex, callback) {
fs.readdirSync(dir).forEach(function (file) {
if (! regex.test(file)) { return; }
callback(file);
});
};
var createTestRunnerPage = function(dir, exclude, testSuiteName, dir2) {
var output = '<html><head>\n';
readDirFilesSync(path.join("test", dir, 'less', dir2 || ""), /\.less$/, function (file) {
var name = path.basename(file, '.less'),
id = (dir ? dir + '-' : "") + 'less-' + (dir2 ? dir2 + "-" : "") + name;
if (exclude && name.match(exclude)) { return; }
output += '<link id="original-less:' + id + '" rel="stylesheet/less" type="text/css" href="/' + path.join(dir, 'less', dir2 || "", name).replace("\\", "/") + '.less' +'" />\n';
output += '<link id="expected-less:' + id + '" rel="stylesheet" type="text/css" href="/' + path.join(dir, 'css', dir2 || "", name).replace("\\", "/") + '.css' + '" />\n';
});
output += String(fs.readFileSync(path.join('test/browser', 'template.htm'))).replace("{runner-name}", testSuiteName);
fs.writeFileSync(path.join('test/browser', 'test-runner-'+testSuiteName+'.htm'), output);
};
var removeFiles = function(dir, regex) {
readDirFilesSync(dir, regex, function(file) {
fs.unlinkSync(path.join(dir, file), function() {
console.log("Failed to delete " + file);
});
});
};
removeFiles("test/browser", /test-runner-[a-zA-Z-]*\.htm$/);
createTestRunnerPage("", /javascript|urls/, "main");
createTestRunnerPage("", null, "legacy", "legacy");
createTestRunnerPage("", /javascript/, "errors", "errors");
createTestRunnerPage("", null, "no-js-errors", "no-js-errors");
createTestRunnerPage("browser", null, "console-errors", "console-errors");
createTestRunnerPage("browser", null, "browser");
createTestRunnerPage("browser", null, "relative-urls", "relative-urls");
createTestRunnerPage("browser", null, "rootpath", "rootpath");
createTestRunnerPage("browser", null, "rootpath-relative", "rootpath-relative");
createTestRunnerPage("browser", null, "production");
createTestRunnerPage("browser", null, "modify-vars", "modify-vars");

View File

@@ -1,16 +1,24 @@
/*if not async then phantomjs fails to run the webserver and the test concurrently*/
var less = { async: true, strictMath: true };
/* record log messages for testing */
// var logAllIds = function() {
// var allTags = document.head.getElementsByTagName('style');
// var ids = [];
// for (var tg = 0; tg < allTags.length; tg++) {
// var tag = allTags[tg];
// if (tag.id) {
// console.log(tag.id);
// }
// }
// };
var logMessages = [],
realConsoleLog = console.log;
realConsoleLog = console.log;
console.log = function(msg) {
logMessages.push(msg);
realConsoleLog.call(console, msg);
logMessages.push(msg);
realConsoleLog.call(console, msg);
};
var testLessEqualsInDocument = function() {
testLessInDocument(testSheet);
testLessInDocument(testSheet);
};
var testLessErrorsInDocument = function(isConsole) {
@@ -18,68 +26,97 @@ var testLessErrorsInDocument = function(isConsole) {
};
var testLessInDocument = function(testFunc) {
var links = document.getElementsByTagName('link'),
typePattern = /^text\/(x-)?less$/;
var links = document.getElementsByTagName('link'),
typePattern = /^text\/(x-)?less$/;
for (var i = 0; i < links.length; i++) {
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
(links[i].type.match(typePattern)))) {
testFunc(links[i]);
}
for (var i = 0; i < links.length; i++) {
if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
(links[i].type.match(typePattern)))) {
testFunc(links[i]);
}
}
};
var testSheet = function(sheet) {
it(sheet.id + " should match the expected output", function() {
var lessOutputId = sheet.id.replace("original-", ""),
expectedOutputId = "expected-" + lessOutputId,
lessOutput = document.getElementById(lessOutputId).innerText,
expectedOutputHref = document.getElementById(expectedOutputId).href,
expectedOutput = loadFile(expectedOutputHref);
it(sheet.id + " should match the expected output", function() {
var lessOutputId = sheet.id.replace("original-", ""),
expectedOutputId = "expected-" + lessOutputId,
lessOutputObj,
lessOutput,
expectedOutputHref = document.getElementById(expectedOutputId).href,
expectedOutput = loadFile(expectedOutputHref);
waitsFor(function() {
return expectedOutput.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
// use sheet to do testing
expect(expectedOutput.text).toEqual(lessOutput);
});
// Browser spec generates less on the fly, so we need to loose control
waitsFor(function() {
lessOutputObj = document.getElementById(lessOutputId);
// the type condition is necessary because of inline browser tests
return lessOutputObj !== null && lessOutputObj.type === "text/css";
}, "generation of " + lessOutputId + "", 700);
runs(function() {
lessOutput = lessOutputObj.innerText;
});
waitsFor(function() {
return expectedOutput.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
// use sheet to do testing
expect(expectedOutput.text).toEqual(lessOutput);
});
});
};
//TODO: do it cleaner - the same way as in css
function extractId(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)
}
var testErrorSheet = function(sheet) {
it(sheet.id + " should match an error", function() {
var lessHref = sheet.href,
id = sheet.id.replace(/^original-less:/, "less-error-message:"),
errorHref = lessHref.replace(/.less$/, ".txt"),
errorFile = loadFile(errorHref),
actualErrorElement = document.getElementById(id),
actualErrorMsg;
it(sheet.id + " should match an error", function() {
var lessHref = sheet.href,
id = "less-error-message:" + extractId(lessHref),
// id = sheet.id.replace(/^original-less:/, "less-error-message:"),
errorHref = lessHref.replace(/.less$/, ".txt"),
errorFile = loadFile(errorHref),
actualErrorElement,
actualErrorMsg;
describe("the error", function() {
expect(actualErrorElement).not.toBe(null);
});
actualErrorMsg = actualErrorElement.innerText
.replace(/\n\d+/g, function(lineNo) { return lineNo + " "; })
.replace(/\n\s*in /g, " in ")
.replace("\n\n", "\n");
// Less.js sets 10ms timer in order to add error message on top of page.
waitsFor(function() {
actualErrorElement = document.getElementById(id);
return actualErrorElement !== null;
}, "error message was not generated", 70);
waitsFor(function() {
return errorFile.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
var errorTxt = errorFile.text
.replace("{path}", "")
.replace("{pathrel}", "")
.replace("{pathhref}", "http://localhost:8081/less/errors/")
.replace("{404status}", " (404)");
expect(errorTxt).toEqual(actualErrorMsg);
if (errorTxt == actualErrorMsg) {
actualErrorElement.style.display = "none";
}
runs(function() {
actualErrorMsg = actualErrorElement.innerText
.replace(/\n\d+/g, function(lineNo) {
return lineNo + " ";
})
.replace(/\n\s*in /g, " in ")
.replace("\n\n", "\n");
});
waitsFor(function() {
return errorFile.loaded;
}, "failed to load expected outout", 10000);
runs(function() {
var errorTxt = errorFile.text
.replace("{path}", "")
.replace("{pathrel}", "")
.replace("{pathhref}", "http://localhost:8081/test/less/errors/")
.replace("{404status}", " (404)");
expect(errorTxt).toEqual(actualErrorMsg);
if (errorTxt == actualErrorMsg) {
actualErrorElement.style.display = "none";
}
});
});
};
@@ -115,20 +152,23 @@ var testErrorSheetConsole = function(sheet) {
.replace("{404status}", " (404)")
.trim();
expect(errorTxt).toEqual(actualErrorMsg);
});
});
});
};
var loadFile = function(href) {
var request = new XMLHttpRequest(),
response = { loaded: false, text: ""};
request.open('GET', href, true);
request.onload = function(e) {
response.text = request.response.replace(/\r/g, "");
response.loaded = true;
}
request.send();
return response;
var request = new XMLHttpRequest(),
response = {
loaded: false,
text: ""
};
request.open('GET', href, true);
request.onload = function(e) {
response.text = request.response.replace(/\r/g, "");
response.loaded = true;
};
request.send();
return response;
};
(function() {
@@ -154,7 +194,7 @@ var loadFile = function(href) {
function execJasmine() {
setTimeout(function() {
jasmineEnv.execute();
jasmineEnv.execute();
}, 3000);
}

View File

@@ -1,20 +1,20 @@
@import "http://localhost:8081/browser/less/imports/modify-this.css";
@import "http://localhost:8081/browser/less/imports/modify-again.css";
@import "http://localhost:8081/test/browser/less/imports/modify-this.css";
@import "http://localhost:8081/test/browser/less/imports/modify-again.css";
.modify {
my-url: url("http://localhost:8081/browser/less/imports/a.png");
my-url: url("http://localhost:8081/test/browser/less/imports/a.png");
}
.modify {
my-url: url("http://localhost:8081/browser/less/imports/b.png");
my-url: url("http://localhost:8081/test/browser/less/imports/b.png");
}
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium), url(http://localhost:8081/browser/less/relative-urls/fonts.svg#MyGeometricModern) format("svg");
src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/relative-urls/fonts.svg#MyGeometricModern) format("svg");
}
#shorthands {
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#misc {
background-image: url(http://localhost:8081/browser/less/relative-urls/images/image.jpg);
background-image: url(http://localhost:8081/test/browser/less/relative-urls/images/image.jpg);
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
@@ -28,8 +28,8 @@
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
.comma-delimited {
background: url(http://localhost:8081/browser/less/relative-urls/bg.jpg) no-repeat, url(http://localhost:8081/browser/less/relative-urls/bg.png) repeat-x top left, url(http://localhost:8081/browser/less/relative-urls/bg);
background: url(http://localhost:8081/test/browser/less/relative-urls/bg.jpg) no-repeat, url(http://localhost:8081/test/browser/less/relative-urls/bg.png) repeat-x top left, url(http://localhost:8081/test/browser/less/relative-urls/bg);
}
.values {
url: url('http://localhost:8081/browser/less/relative-urls/Trebuchet');
url: url('http://localhost:8081/test/browser/less/relative-urls/Trebuchet');
}

View File

@@ -1,20 +1,20 @@
@import "http://localhost:8081/browser/less/modify-this.css";
@import "http://localhost:8081/browser/less/modify-again.css";
@import "http://localhost:8081/test/browser/less/modify-this.css";
@import "http://localhost:8081/test/browser/less/modify-again.css";
.modify {
my-url: url("http://localhost:8081/browser/less/a.png");
my-url: url("http://localhost:8081/test/browser/less/a.png");
}
.modify {
my-url: url("http://localhost:8081/browser/less/b.png");
my-url: url("http://localhost:8081/test/browser/less/b.png");
}
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium), url(http://localhost:8081/browser/less/fonts.svg#MyGeometricModern) format("svg");
src: local(Futura-Medium), url(http://localhost:8081/test/browser/less/fonts.svg#MyGeometricModern) format("svg");
}
#shorthands {
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#misc {
background-image: url(http://localhost:8081/browser/less/images/image.jpg);
background-image: url(http://localhost:8081/test/browser/less/images/image.jpg);
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
@@ -28,23 +28,23 @@
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
.comma-delimited {
background: url(http://localhost:8081/browser/less/bg.jpg) no-repeat, url(http://localhost:8081/browser/less/bg.png) repeat-x top left, url(http://localhost:8081/browser/less/bg);
background: url(http://localhost:8081/test/browser/less/bg.jpg) no-repeat, url(http://localhost:8081/test/browser/less/bg.png) repeat-x top left, url(http://localhost:8081/test/browser/less/bg);
}
.values {
url: url('http://localhost:8081/browser/less/Trebuchet');
url: url('http://localhost:8081/test/browser/less/Trebuchet');
}
#data-uri {
uri: url('http://localhost:8081/data/image.jpg');
uri: url('http://localhost:8081/test/data/image.jpg');
}
#data-uri-guess {
uri: url('http://localhost:8081/data/image.jpg');
uri: url('http://localhost:8081/test/data/image.jpg');
}
#data-uri-ascii {
uri-1: url('http://localhost:8081/data/page.html');
uri-2: url('http://localhost:8081/data/page.html');
uri-1: url('http://localhost:8081/test/data/page.html');
uri-2: url('http://localhost:8081/test/data/page.html');
}
#data-uri-toobig {
uri: url('http://localhost:8081/data/data-uri-fail.png');
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>');

View File

@@ -1,5 +1,5 @@
@import ".././imports/urls.less";
@import "http://localhost:8081/browser/less/imports/urls2.less";
@import "http://localhost:8081/test/browser/less/imports/urls2.less";
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium),

View File

@@ -1,5 +1,5 @@
@import "../imports/urls.less";
@import "http://localhost:8081/browser/less/imports/urls2.less";
@import "http://localhost:8081/test/browser/less/imports/urls2.less";
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium),

View File

@@ -1,5 +1,5 @@
@import "../imports/urls.less";
@import "http://localhost:8081/browser/less/imports/urls2.less";
@import "http://localhost:8081/test/browser/less/imports/urls2.less";
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium),

View File

@@ -1,5 +1,5 @@
@import "imports/urls.less";
@import "http://localhost:8081/browser/less/imports/urls2.less";
@import "http://localhost:8081/test/browser/less/imports/urls2.less";
@font-face {
src: url("/fonts/garamond-pro.ttf");
src: local(Futura-Medium),

View File

@@ -2,31 +2,35 @@ var webpage = require('webpage');
var server = require('webserver').create();
var system = require('system');
var fs = require('fs');
var host, port = 8081;
var host;
var port = 8081;
var listening = server.listen(port, function (request, response) {
//console.log("Requested "+request.url);
var filename = ("test/" + request.url.slice(1)).replace(/[\\\/]/g, fs.separator);
if (!fs.exists(filename) || !fs.isFile(filename)) {
response.statusCode = 404;
response.write("<html><head></head><body><h1>File Not Found</h1><h2>File:"+filename+"</h2></body></html>");
response.close();
return;
}
var listening = server.listen(port, function(request, response) {
//console.log("Requested " + request.url);
// we set the headers here
response.statusCode = 200;
response.headers = {"Cache": "no-cache", "Content-Type": "text/html"};
response.write(fs.read(filename));
var filename = ("test/" + request.url.slice(1)).replace(/[\\\/]/g, fs.separator);
if (!fs.exists(filename) || !fs.isFile(filename)) {
response.statusCode = 404;
response.write("<html><head></head><body><h1>File Not Found</h1><h2>File:" + filename + "</h2></body></html>");
response.close();
return;
}
// we set the headers here
response.statusCode = 200;
response.headers = {
"Cache": "no-cache",
"Content-Type": "text/html"
};
response.write(fs.read(filename));
response.close();
});
if (!listening) {
console.log("could not create web server listening on port " + port);
phantom.exit();
console.log("could not create web server listening on port " + port);
phantom.exit();
}
/**
@@ -42,100 +46,105 @@ if (!listening) {
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
* @param timeOutErrorMessage the error message if time out occurs
*/
function waitFor(testFx, onReady, timeOutMillis, timeOutErrorMessage) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10001, //< Default Max Timeout is 10s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log(timeOutErrorMessage || "'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 100ms
};
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10001, //< Default Max Timeout is 10s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if (!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log(timeOutErrorMessage || "'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100); //< repeat check every 100ms
}
function testPage(url) {
var page = webpage.create();
page.open(url, function (status) {
if (status !== "success") {
console.log("Unable to access network - " + status);
phantom.exit();
} else {
waitFor(function(){
return page.evaluate(function(){
return document.body && document.body.querySelector &&
document.body.querySelector('.symbolSummary .pending') === null &&
document.body.querySelector('.results') !== null;
});
}, function(){
page.onConsoleMessage = function (msg) {
console.log(msg);
};
var exitCode = page.evaluate(function(){
console.log('');
console.log(document.body.querySelector('.description').innerText);
var list = document.body.querySelectorAll('.results > #details > .specDetail.failed');
if (list && list.length > 0) {
console.log('');
console.log(list.length + ' test(s) FAILED:');
for (var i = 0; i < list.length; ++i) {
var el = list[i],
desc = el.querySelector('.description'),
msg = el.querySelector('.resultMessage.fail');
console.log('');
console.log(desc.innerText);
console.log(msg.innerText);
console.log('');
}
return 1;
} else {
console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText);
return 0;
}
});
testFinished(exitCode);
},
10000, // 10 second timeout
"Test failed waiting for jasmine results on page: " + url);
}
});
var page = webpage.create();
page.open(url, function(status) {
if (status !== "success") {
console.log("Unable to access network - " + status);
phantom.exit();
} else {
waitFor(function() {
return page.evaluate(function() {
return document.body && document.body.querySelector &&
document.body.querySelector('.symbolSummary .pending') === null &&
document.body.querySelector('.results') !== null;
});
}, function() {
page.onConsoleMessage = function(msg) {
console.log(msg);
};
var exitCode = page.evaluate(function() {
console.log('');
console.log(document.body.querySelector('.description').innerText);
var list = document.body.querySelectorAll('.results > #details > .specDetail.failed');
if (list && list.length > 0) {
console.log('');
console.log(list.length + ' test(s) FAILED:');
for (var i = 0; i < list.length; ++i) {
var el = list[i],
desc = el.querySelector('.description'),
msg = el.querySelector('.resultMessage.fail');
console.log('');
console.log(desc.innerText);
console.log(msg.innerText);
console.log('');
}
return 1;
} else {
console.log(document.body.querySelector('.alert > .passingAlert.bar').innerText);
return 0;
}
});
testFinished(exitCode);
},
10000, // 10 second timeout
"Test failed waiting for jasmine results on page: " + url);
}
});
}
function scanDirectory(path, regex) {
var files = [];
fs.list(path).forEach(function (file) {
if (file.match(regex)) {
files.push(file);
}
});
return files;
};
var files = [];
fs.list(path).forEach(function(file) {
if (file.match(regex)) {
files.push(file);
}
});
return files;
}
var totalTests = 0,
totalFailed = 0,
totalDone = 0;
totalFailed = 0,
totalDone = 0;
function testFinished(failed) {
if (failed) { totalFailed++; }
totalDone++;
if (totalDone === totalTests) { phantom.exit(totalFailed > 0 ? 1 : 0); }
if (failed) {
totalFailed++;
}
totalDone++;
if (totalDone === totalTests) {
phantom.exit(totalFailed > 0 ? 1 : 0);
}
}
if (system.args.length != 2 && system.args[1] != "--no-tests") {
var files = scanDirectory("test/browser/", /^test-runner-.+\.htm$/);
totalTests = files.length;
console.log("found " + files.length + " tests");
files.forEach(function(file) {
testPage("http://localhost:8081/browser/" + file);
});
var files = scanDirectory("test/browser/", /^test-runner-.+\.htm$/);
totalTests = files.length;
console.log("found " + files.length + " tests");
files.forEach(function(file) {
testPage("http://localhost:8081/browser/" + file);
});
}

View File

@@ -0,0 +1,42 @@
var less = {};
// There originally run inside describe method. However, since they have not
// been inside it, they run at jasmine compile time (not runtime). It all
// worked cause less.js was in async mode and custom phantom runner had
// different setup then grunt-contrib-jasmine. They have been created before
// less.js run, even as they have been defined in spec.
// test inline less in style tags by grabbing an assortment of less files and doing `@import`s
var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
testSheets = [];
// 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 + '";';
lessStyle.type = 'text/less';
lessStyle.id = file;
lessStyle.href = file;
if (lessStyle.styleSheet) {
lessStyle.styleSheet.cssText = lessText;
} else {
lessStyle.innerHTML = lessText;
}
cssLink.rel = 'stylesheet';
cssLink.type = 'text/css';
cssLink.href = cssPath;
cssLink.id = 'expected-' + file;
var head = document.getElementsByTagName('head')[0];
head.appendChild(lessStyle);
head.appendChild(cssLink);
testSheets[i] = lessStyle;
}

View File

@@ -0,0 +1,12 @@
describe("less.js browser behaviour", function() {
testLessEqualsInDocument();
it("has some log messages", function() {
expect(logMessages.length).toBeGreaterThan(0);
});
for (var i = 0; i < testFiles.length; i++) {
var sheet = testSheets[i];
testSheet(sheet);
}
});

View File

@@ -1,42 +0,0 @@
describe("less.js browser behaviour", function() {
testLessEqualsInDocument();
it("has some log messages", function() {
expect(logMessages.length).toBeGreaterThan(0);
});
// test inline less in style tags by grabbing an assortment of less files and doing `@import`s
var testFiles = ['charsets', 'colors', 'comments', 'css-3', 'strings', 'media', 'mixins'],
testSheets = [];
// 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 = '/less/' + file + '.less',
cssPath = '/css/' + file + '.css',
lessStyle = document.createElement('style'),
cssLink = document.createElement('link'),
lessText = '@import "' + lessPath + '";';
lessStyle.type = 'text/less';
lessStyle.id = file;
if (lessStyle.styleSheet) {
lessStyle.styleSheet.cssText = lessText;
} else {
lessStyle.innerHTML = lessText;
}
cssLink.rel = 'stylesheet';
cssLink.type = 'text/css';
cssLink.href = cssPath;
cssLink.id = 'expected-' + file;
var head = document.getElementsByTagName('head')[0];
head.appendChild(lessStyle);
head.appendChild(cssLink);
testSheets[i] = lessStyle;
}
for (var i = 0; i < testFiles.length; i++) {
var sheet = testSheets[i];
testSheet(sheet);
}
});

View File

@@ -0,0 +1,3 @@
var less = {};
less.strictUnits = true;

View File

@@ -0,0 +1,4 @@
describe("less.js error tests", function() {
testLessErrorsInDocument();
});

View File

@@ -1,5 +0,0 @@
less.strictUnits = true;
describe("less.js error tests", function() {
testLessErrorsInDocument();
});

View File

@@ -0,0 +1,4 @@
var less = {};
less.strictMath = false;
less.strictUnits = false;

View File

@@ -1,6 +1,3 @@
less.strictMath = false;
less.strictUnits = false;
describe("less.js legacy tests", function() {
testLessEqualsInDocument();
});
});

View File

@@ -0,0 +1,15 @@
var less = {};
less.strictMath = true;
less.functions = {
add: function(a, b) {
return new(less.tree.Dimension)(a.value + b.value);
},
increment: function(a) {
return new(less.tree.Dimension)(a.value + 1);
},
_color: function(str) {
if (str.value === "evil red") {
return new(less.tree.Color)("600");
}
}
};

View File

@@ -0,0 +1,3 @@
describe("less.js main tests", function() {
testLessEqualsInDocument();
});

View File

@@ -1,15 +0,0 @@
less.functions = {
add: function (a, b) {
return new(less.tree.Dimension)(a.value + b.value);
},
increment: function (a) {
return new(less.tree.Dimension)(a.value + 1);
},
_color: function (str) {
if (str.value === "evil red") { return new(less.tree.Color)("600") }
}
};
describe("less.js main tests", function() {
testLessEqualsInDocument();
});

View File

@@ -0,0 +1,2 @@
/* exported less */
var less = {};

View File

@@ -0,0 +1,42 @@
var alreadyRun = false;
describe("less.js modify vars", function() {
beforeEach(function() {
// simulating "setUp" or "beforeAll" method
var lessOutputObj;
if (alreadyRun)
return;
alreadyRun = true;
// wait until the sheet is compiled first time
waitsFor(function() {
lessOutputObj = document.getElementById("less:test-less-simple");
return lessOutputObj !== null;
}, "first generation of less:test-less-simple", 7000);
// modify variables
runs(function() {
lessOutputObj.type = "not compiled yet";
less.modifyVars({
var1: "green",
var2: "purple"
});
});
// wait until variables are modified
waitsFor(function() {
lessOutputObj = document.getElementById("less:test-less-simple");
return lessOutputObj !== null && lessOutputObj.type === "text/css";
}, "second generation of less:test-less-simple", 7000);
});
testLessEqualsInDocument();
it("Should log only 2 XHR requests", function() {
var xhrLogMessages = logMessages.filter(function(item) {
return (/XHR: Getting '/).test(item);
});
expect(xhrLogMessages.length).toEqual(2);
});
});

View File

@@ -1,14 +0,0 @@
setTimeout(function(){
less.modifyVars({var1: "green", var2: "purple"});
}, 1000);
describe("less.js modify vars", function() {
testLessEqualsInDocument();
it("Should log only 2 XHR requests", function() {
var xhrLogMessages = logMessages.filter(function(item) {
return /XHR: Getting '/.test(item);
})
expect(xhrLogMessages.length).toEqual(2);
});
});

View File

@@ -0,0 +1,4 @@
var less = {};
less.strictUnits = true;
less.javascriptEnabled = false;

View File

@@ -0,0 +1,4 @@
describe("less.js javascript disabled error tests", function() {
testLessErrorsInDocument();
});

View File

@@ -1,6 +0,0 @@
less.strictUnits = true;
less.javascriptEnabled = false;
describe("less.js javascript disabled error tests", function() {
testLessErrorsInDocument();
});

View File

@@ -0,0 +1,3 @@
var less = {};
less.env = "production";

View File

@@ -1,7 +1,5 @@
less.env = "production";
describe("less.js production behaviour", function() {
it("doesn't log any messages", function() {
expect(logMessages.length).toEqual(0);
});
});
});

View File

@@ -0,0 +1,3 @@
var less = {};
less.relativeUrls = true;

View File

@@ -1,4 +1,3 @@
less.relativeUrls = true;
describe("less.js browser test - relative url's", function() {
testLessEqualsInDocument();
});
});

View File

@@ -0,0 +1,3 @@
var less = {};
less.rootpath = "https://www.github.com/";

View File

@@ -0,0 +1,4 @@
var less = {};
less.rootpath = "https://www.github.com/cloudhead/less.js/";
less.relativeUrls = true;

View File

@@ -1,5 +1,3 @@
less.rootpath = "https://www.github.com/cloudhead/less.js/";
less.relativeUrls = true;
describe("less.js browser test - rootpath and relative url's", function() {
testLessEqualsInDocument();
});
});

View File

@@ -1,4 +1,3 @@
less.rootpath = "https://www.github.com/";
describe("less.js browser test - rootpath url's", function() {
testLessEqualsInDocument();
});
});

View File

@@ -1,11 +0,0 @@
<script src="/browser/jasmine.js" type="text/javascript"></script>
<script src="/browser/jasmine-html.js" type="text/javascript"></script>
<script src="/browser/common.js" type="text/javascript"></script>
<script src="/browser/es5.js" type="text/javascript"></script>
<script src="/browser/runner-{runner-name}.js" type="text/javascript"></script>
<script src="/browser/less.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="/browser/jasmine.css" />
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner</title>
<!-- generate script tags for tests -->
<% var generateScriptTags = function(allScripts) { allScripts.forEach(function(script){ %>
<script src="<%= script %>"></script>
<% }); }; %>
<!-- for each test, generate CSS/LESS link tags -->
<% scripts.src.forEach(function(fullLessName) {
var pathParts = fullLessName.split('/');
var fullCssName = fullLessName.replace(/less/g, 'css');
var lessName = pathParts[pathParts.length - 1];
var name = lessName.split('.')[0]; %>
<!-- the tags to be generated -->
<link id="original-less:test-less-<%= name %>" title="test-less-<%= name %>" rel="stylesheet/less" type="text/css" href="<%= fullLessName %>">
<link id="expected-less:test-less-<%= name %>" rel="stylesheet" type="text/css" href="<%= fullCssName %>">
<% }); %>
<!-- generate grunt-contrib-jasmine link tags -->
<% css.forEach(function(style){ %>
<link rel="stylesheet" type="text/css" href="<%= style %>">
<% }) %>
<!-- inital grunt-contrib-jasmine scripts -->
<% generateScriptTags([].concat(scripts.polyfills, scripts.jasmine)); %>
<!-- Helpers - The less options -->
<% generateScriptTags(scripts.helpers); %>
<!-- Vendor - less.js and common code -->
<% generateScriptTags(scripts.vendor); %>
<!-- Spec -->
<% generateScriptTags(scripts.specs); %>
<!-- final grunt-contrib-jasmine scripts -->
<% generateScriptTags([].concat(scripts.reporters, scripts.start)); %>
</head>
<body>
<!-- content -->
</body>
</html>

View File

@@ -26,7 +26,7 @@ less.tree.functions._color = function (str) {
if (str.value === "evil red") { return new(less.tree.Color)("600"); }
};
sys.puts("\n" + stylize("LESS", 'underline') + "\n");
console.log("\n" + stylize("LESS", 'underline') + "\n");
runTestSet({strictMath: true, relativeUrls: true, silent: true});
runTestSet({strictMath: true, strictUnits: true}, "errors/",
@@ -72,7 +72,6 @@ function testSourcemap(name, err, compiledLess, doReplacements, sourcemap) {
} else {
difference("FAIL", expectedSourcemap, sourcemap);
}
sys.puts("");
});
}
@@ -94,7 +93,6 @@ function testErrors(name, err, compiledLess, doReplacements) {
difference("FAIL", expectedErr, errMessage);
}
}
sys.puts("");
});
}
@@ -120,16 +118,19 @@ function checkGlobalLeaks() {
function runTestSet(options, foldername, verifyFunction, nameModifier, doReplacements, getFilename) {
foldername = foldername || "";
if(!doReplacements)
if(!doReplacements) {
doReplacements = globalReplacements;
}
fs.readdirSync(path.join('test/less/', foldername)).forEach(function (file) {
if (! /\.less/.test(file)) { return; }
var name = foldername + path.basename(file, '.less');
if (oneTestOnly && name !== oneTestOnly) { return; }
if (oneTestOnly && name !== oneTestOnly) {
return;
}
totalTests++;
if (options.sourceMap) {
@@ -163,14 +164,12 @@ function runTestSet(options, foldername, verifyFunction, nameModifier, doReplace
} else {
difference("FAIL", css, less);
}
sys.puts("");
});
});
});
}
function diff(left, right) {
sys.puts("");
require('diff').diffLines(left, right).forEach(function(item) {
if(item.added || item.removed) {
var text = item.value.replace("\n", String.fromCharCode(182) + "\n");
@@ -179,16 +178,17 @@ function diff(left, right) {
sys.print(item.value);
}
});
console.log("");
}
function fail(msg) {
sys.print(stylize(msg, 'red'));
console.error(stylize(msg, 'red'));
failedTests++;
endTest();
}
function difference(msg, left, right) {
sys.print(stylize(msg, 'yellow'));
console.warn(stylize(msg, 'yellow'));
failedTests++;
diff(left, right);
@@ -196,7 +196,7 @@ function difference(msg, left, right) {
}
function ok(msg) {
sys.print(stylize(msg, 'green'));
console.log(stylize(msg, 'green'));
passedTests++;
endTest();
}
@@ -204,21 +204,20 @@ function ok(msg) {
function endTest() {
var leaked = checkGlobalLeaks();
if (failedTests + passedTests === totalTests) {
sys.puts("");
sys.puts("");
console.log("");
if (failedTests > 0) {
sys.print(failedTests);
sys.print(stylize(" Failed", "red"));
sys.print(", " + passedTests + " passed");
console.error(failedTests + stylize(" Failed", "red") + ", " + passedTests + " passed");
} else {
sys.print(stylize("All Passed ", "green"));
sys.print(passedTests + " run");
console.log(stylize("All Passed ", "green") + passedTests + " run");
}
if (leaked.length > 0) {
sys.puts("");
sys.puts("");
sys.print(stylize("Global leak detected: ", "red") + leaked.join(', '));
sys.print("\n");
console.log("");
console.warn(stylize("Global leak detected: ", "red") + leaked.join(', '));
}
if (leaked.length || failedTests) {
//process.exit(1);
process.on('exit', function() { process.reallyExit(1) });
}
}
}

View File

@@ -1 +0,0 @@
{"version":3,"file":"sourcemaps/basic.css","sources":["testweb/sourcemaps/basic.less"],"names":[],"mappings":"AAMG;EACD,YAAA;EAJA,UAAA;EAWA,iBAAA;EALA,WAAA;EACA,mBAAA;;AAJC,EASC;AATD,EASM;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;EAGH,UAAA;;AALJ;AAAK;AAUA;EATL,iBAAA;;AADA,EAEE;AAFG,EAEH;AAFF,EAEO;AAFF,EAEE;AAQF,OARH;AAQG,OARE;EACL,gBAAA;;AACE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFN;AAEE,EAFF,GAEM,KAFD;AAEH,EAFF,GAEM,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFN;AAEE,EAFG,GAEC,KAFD;AAEH,EAFG,GAEC,KAFD;AAQF,OARH,GAQG,UARH;AAQG,OARH,GAEM,KAFN;AAQG,OARH,GAQG,UARE;AAQF,OARH,GAEM,KAFD;AAEH,EAFF,GAQG,UARH;AAEE,EAFF,GAQG,UARE;AAQF,OARE,GAQF,UARH;AAQG,OARE,GAEC,KAFN;AAQG,OARE,GAQF,UARE;AAQF,OARE,GAEC,KAFD;AAEH,EAFG,GAQF,UARH;AAEE,EAFG,GAQF,UARE;EAGH,UAAA;;AAKC;EACL,WAAA"}