From c332674c9033a702d1000311f53481b84dbaaa60 Mon Sep 17 00:00:00 2001 From: Rasika Date: Mon, 18 May 2015 17:41:37 +0530 Subject: [PATCH] adding jaggery app --- .../jaggeryapps/iot/api/device-api.jag | 59 + .../jaggeryapps/iot/api/user-api.jag | 30 +- .../jaggeryapps/iot/config/config.json | 19 +- .../jaggeryapps/iot/config/dc-props.js | 37 + .../repository/jaggeryapps/iot/config/init.js | 3 +- .../repository/jaggeryapps/iot/jaggery.conf | 10 +- .../jaggeryapps/iot/layouts/enrollment.hbs | 29 + .../jaggeryapps/iot/layouts/fluid.hbs | 22 + .../jaggeryapps/iot/layouts/nav-fluid.hbs | 50 + .../repository/jaggeryapps/iot/lib/acs.jag | 49 + .../jaggeryapps/iot/lib/fuse-router.js | 210 + .../repository/jaggeryapps/iot/lib/fuse.jag | 29 + .../repository/jaggeryapps/iot/lib/fuse.js | 417 + .../jaggeryapps/iot/lib/handlebars-helpers.js | 194 + .../jaggeryapps/iot/lib/handlebars-v2.0.0.js | 3079 +++++ .../jaggeryapps/iot/lib/less-rhino-1.7.5.js | 10209 ++++++++++++++++ .../repository/jaggeryapps/iot/lib/login.jag | 2 +- .../repository/jaggeryapps/iot/lib/logout.jag | 2 +- .../jaggeryapps/iot/modules/constants.js | 7 +- .../jaggeryapps/iot/modules/download.js | 68 + .../jaggeryapps/iot/modules/pinch.min.js | 27 + .../jaggeryapps/iot/modules/user.js | 62 +- .../jaggeryapps/iot/modules/utility.js | 24 +- .../jaggeryapps/iot/pages/add-user.hbs | 9 - .../jaggeryapps/iot/pages/arduino.hbs | 8 + .../jaggeryapps/iot/pages/index.hbs | 15 +- .../jaggeryapps/iot/pages/login.hbs | 3 +- .../jaggeryapps/iot/pages/users.hbs | 9 - .../jaggeryapps/iot/sketch/arduino-sketch.hbs | 30 + .../src/repository/jaggeryapps/iot/test.jag | 12 - .../iot/units/add-user/add-user.hbs | 56 - .../iot/units/add-user/add-user.js | 26 - .../iot/units/add-user/add-user.json | 3 - .../iot/units/add-user/public/js/bottomJs.js | 91 - .../jaggeryapps/iot/units/appbar/appbar.hbs | 191 +- .../jaggeryapps/iot/units/appbar/appbar.js | 31 +- .../iot/units/appbar/public/images/logo.png | Bin 0 -> 3902 bytes .../jaggeryapps/iot/units/arduino/arduino.hbs | 32 + .../jaggeryapps/iot/units/arduino/arduino.js | 4 + .../iot/units/arduino/arduino.json | 3 + .../arduino/public/images/arduino-board.png | Bin 0 -> 555166 bytes .../jaggeryapps/iot/units/login/login.hbs | 22 + .../jaggeryapps/iot/units/login/login.js | 10 + .../jaggeryapps/iot/units/login/login.json | 3 + .../iot/units/login/public/js/login-box.js | 13 + .../showcase/public/images/arduino-uno.png | Bin 0 -> 9862 bytes .../showcase/public/images/banner-img.png | Bin 0 -> 34331 bytes .../showcase/public/images/beagle-bone.png | Bin 0 -> 10433 bytes .../units/showcase/public/images/intel.png | Bin 0 -> 5068 bytes .../showcase/public/images/raspberry-pi.png | Bin 0 -> 8006 bytes .../public/scripts/validate-register.js | 143 + .../iot/units/showcase/showcase.hbs | 75 + .../iot/units/showcase/showcase.json | 3 + .../units/theme/public/css/bootstrap.min.css | 6671 ++++++++++ .../units/theme/public/css/custom-theme.css | 38 + .../iot/units/theme/public/css/main.css | 433 + .../public/fonts/OpenSans-Bold-webfont.eot | Bin 0 -> 30858 bytes .../public/fonts/OpenSans-Bold-webfont.svg | 251 + .../public/fonts/OpenSans-Bold-webfont.ttf | Bin 0 -> 30680 bytes .../public/fonts/OpenSans-Bold-webfont.woff | Bin 0 -> 19788 bytes .../fonts/OpenSans-BoldItalic-webfont.eot | Bin 0 -> 34166 bytes .../fonts/OpenSans-BoldItalic-webfont.svg | 251 + .../fonts/OpenSans-BoldItalic-webfont.ttf | Bin 0 -> 33960 bytes .../fonts/OpenSans-BoldItalic-webfont.woff | Bin 0 -> 21940 bytes .../fonts/OpenSans-ExtraBold-webfont.eot | Bin 0 -> 30602 bytes .../fonts/OpenSans-ExtraBold-webfont.svg | 251 + .../fonts/OpenSans-ExtraBold-webfont.ttf | Bin 0 -> 30404 bytes .../fonts/OpenSans-ExtraBold-webfont.woff | Bin 0 -> 19972 bytes .../OpenSans-ExtraBoldItalic-webfont.eot | Bin 0 -> 33758 bytes .../OpenSans-ExtraBoldItalic-webfont.svg | 251 + .../OpenSans-ExtraBoldItalic-webfont.ttf | Bin 0 -> 33532 bytes .../OpenSans-ExtraBoldItalic-webfont.woff | Bin 0 -> 21824 bytes .../public/fonts/OpenSans-Italic-webfont.eot | Bin 0 -> 34798 bytes .../public/fonts/OpenSans-Italic-webfont.svg | 251 + .../public/fonts/OpenSans-Italic-webfont.ttf | Bin 0 -> 34612 bytes .../public/fonts/OpenSans-Italic-webfont.woff | Bin 0 -> 22416 bytes .../public/fonts/OpenSans-Light-webfont.eot | Bin 0 -> 29794 bytes .../public/fonts/OpenSans-Light-webfont.svg | 252 + .../public/fonts/OpenSans-Light-webfont.ttf | Bin 0 -> 29612 bytes .../public/fonts/OpenSans-Light-webfont.woff | Bin 0 -> 19396 bytes .../fonts/OpenSans-LightItalic-webfont.eot | Bin 0 -> 34578 bytes .../fonts/OpenSans-LightItalic-webfont.svg | 252 + .../fonts/OpenSans-LightItalic-webfont.ttf | Bin 0 -> 34368 bytes .../fonts/OpenSans-LightItalic-webfont.woff | Bin 0 -> 22444 bytes .../public/fonts/OpenSans-Regular-webfont.eot | Bin 0 -> 29934 bytes .../public/fonts/OpenSans-Regular-webfont.svg | 252 + .../public/fonts/OpenSans-Regular-webfont.ttf | Bin 0 -> 29744 bytes .../fonts/OpenSans-Regular-webfont.woff | Bin 0 -> 19624 bytes .../fonts/OpenSans-Semibold-webfont.eot | Bin 0 -> 30350 bytes .../fonts/OpenSans-Semibold-webfont.svg | 251 + .../fonts/OpenSans-Semibold-webfont.ttf | Bin 0 -> 30156 bytes .../fonts/OpenSans-Semibold-webfont.woff | Bin 0 -> 19736 bytes .../fonts/OpenSans-SemiboldItalic-webfont.eot | Bin 0 -> 34866 bytes .../fonts/OpenSans-SemiboldItalic-webfont.svg | 251 + .../fonts/OpenSans-SemiboldItalic-webfont.ttf | Bin 0 -> 34644 bytes .../OpenSans-SemiboldItalic-webfont.woff | Bin 0 -> 22332 bytes .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20335 bytes .../fonts/glyphicons-halflings-regular.svg | 229 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 41280 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23320 bytes .../units/theme/public/images/content-bg.png | Bin 0 -> 11641 bytes .../units/theme/public/js/bootstrap.min.js | 7 + .../units/theme/public/js/html5shiv.min.js | 7 + .../iot/units/theme/public/js/jquery.min.js | 4 + .../iot/units/theme/public/js/respond.min.js | 5 + .../iot/units/theme/public/js/toggle.js | 8 + .../units/theme/public/scripts/interface.js | 67 + .../units/theme/public/scripts/register.js | 143 + .../jaggeryapps/iot/units/theme/theme.hbs | 23 + .../jaggeryapps/iot/units/theme/theme.json | 3 + 110 files changed, 25207 insertions(+), 374 deletions(-) create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/api/device-api.jag create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/config/dc-props.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/layouts/enrollment.hbs create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/layouts/fluid.hbs create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/layouts/nav-fluid.hbs create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/lib/acs.jag create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/lib/fuse-router.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/lib/fuse.jag create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/lib/fuse.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/lib/handlebars-helpers.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/lib/handlebars-v2.0.0.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/lib/less-rhino-1.7.5.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/modules/download.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/modules/pinch.min.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iot/pages/add-user.hbs create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/pages/arduino.hbs delete mode 100644 modules/distribution/src/repository/jaggeryapps/iot/pages/users.hbs create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/sketch/arduino-sketch.hbs delete mode 100644 modules/distribution/src/repository/jaggeryapps/iot/test.jag delete mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.hbs delete mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.json delete mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/add-user/public/js/bottomJs.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/appbar/public/images/logo.png create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/arduino/arduino.hbs create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/arduino/arduino.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/arduino/arduino.json create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/arduino/public/images/arduino-board.png create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/login/login.hbs create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/login/login.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/login/login.json create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/login/public/js/login-box.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/showcase/public/images/arduino-uno.png create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/showcase/public/images/banner-img.png create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/showcase/public/images/beagle-bone.png create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/showcase/public/images/intel.png create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/showcase/public/images/raspberry-pi.png create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/showcase/public/scripts/validate-register.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/showcase/showcase.hbs create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/showcase/showcase.json create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/css/bootstrap.min.css create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/css/custom-theme.css create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/css/main.css create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Bold-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Bold-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Bold-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Bold-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-BoldItalic-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-BoldItalic-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-BoldItalic-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-BoldItalic-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-ExtraBold-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-ExtraBold-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-ExtraBold-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-ExtraBold-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-ExtraBoldItalic-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-ExtraBoldItalic-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-ExtraBoldItalic-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-ExtraBoldItalic-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Italic-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Italic-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Italic-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Italic-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Light-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Light-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Light-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Light-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-LightItalic-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-LightItalic-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-LightItalic-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-LightItalic-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Regular-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Regular-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Regular-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Regular-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Semibold-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Semibold-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Semibold-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-Semibold-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-SemiboldItalic-webfont.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-SemiboldItalic-webfont.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-SemiboldItalic-webfont.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/OpenSans-SemiboldItalic-webfont.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/glyphicons-halflings-regular.eot create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/glyphicons-halflings-regular.svg create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/glyphicons-halflings-regular.ttf create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/fonts/glyphicons-halflings-regular.woff create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/images/content-bg.png create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/js/bootstrap.min.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/js/html5shiv.min.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/js/jquery.min.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/js/respond.min.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/js/toggle.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/scripts/interface.js create mode 100644 modules/distribution/src/repository/jaggeryapps/iot/units/theme/public/scripts/register.js create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/theme.hbs create mode 100755 modules/distribution/src/repository/jaggeryapps/iot/units/theme/theme.json diff --git a/modules/distribution/src/repository/jaggeryapps/iot/api/device-api.jag b/modules/distribution/src/repository/jaggeryapps/iot/api/device-api.jag new file mode 100644 index 00000000..c19231e4 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/api/device-api.jag @@ -0,0 +1,59 @@ +<% +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var uri = request.getRequestURI(); +var uriMatcher = new URIMatcher(String(uri)); + +var log = new Log("api/device-api.jag"); + +var constants = require("/modules/constants.js"); +var dcProps = require('/config/dc-props.js').config(); +var downloadModule = require("/modules/download.js").downloadModule; +var utility = require("/modules/utility.js").utility; + +var result; + +if (uriMatcher.match("/{context}/api/device/sketch/")) { + sketchType = request.getParameter("type"); + log.error("#################"); + log.error(sketchType); + if(!sketchType){log.error("Sketch Type is empty");} + + var user = session.get(constants.USER_SESSION_KEY); + if(!user){ + response.sendRedirect(dcProps.appContext + "login?#login-required"); + exit(); + } + + if(sketchType=="arduino"){ + downloadModule.downloadSketch("arduino-sketch.hbs",response, {"{{self.publicURL}}":"####"}); + }else if(sketchType=="raspberry"){ + downloadModule.downloadSketch("raspberry-sketch.hbs",response, {"{{self.publicURL}}":"####"}); + }else{ + log.error("Sketch type `"+sketchType+"` not found"); + response.sendRedirect(uri + "?#sketch-failed"); + exit(); + } +} + +// returning the result. +if (result) { + print(result); +} +%> diff --git a/modules/distribution/src/repository/jaggeryapps/iot/api/user-api.jag b/modules/distribution/src/repository/jaggeryapps/iot/api/user-api.jag index 5ea7c919..31d1d0f9 100644 --- a/modules/distribution/src/repository/jaggeryapps/iot/api/user-api.jag +++ b/modules/distribution/src/repository/jaggeryapps/iot/api/user-api.jag @@ -23,9 +23,8 @@ var uriMatcher = new URIMatcher(String(uri)); var log = new Log("api/user-api.jag"); var constants = require("/modules/constants.js"); -var mdmProps = require('/config/mdm-props.js').config(); +var dcProps = require('/config/dc-props.js').config(); var userModule = require("/modules/user.js").userModule; -var deviceModule = require("/modules/device.js").deviceModule; var utility = require("/modules/utility.js").utility; var result; @@ -41,31 +40,16 @@ if (uriMatcher.match("/{context}/api/user/login/")) { utility.insertAppPermissions(userModule, "login"); response.sendRedirect(constants.WEB_APP_CONTEXT); }, function() { - response.sendRedirect(mdmProps.appContext + "login?#auth-failed"); + response.sendRedirect(dcProps.appContext + "login?#auth-failed"); }); } catch (e) { - log.error("Exception occurred while a user tried to login to MDM", e); - response.sendRedirect(mdmProps.appContext + "login?#error"); + log.error("Exception occurred while a user tried to login to DC", e); + response.sendRedirect(dcProps.appContext + "login?#error"); } } else if (uriMatcher.match("/{context}/api/user/logout/")){ userModule.logout(function() { - response.sendRedirect(mdmProps.appContext + "login"); + response.sendRedirect(dcProps.appContext + "login"); }); -} else if (uriMatcher.match("/{context}/api/user/devices/")) { - if (userModule.isAuthorized("/permission/device-mgt/user/devices/list")) { - carbonUser = session.get(constants.USER_SESSION_KEY); - result = deviceModule.listDevicesForUser(carbonUser.username); - } else { - response.sendError(403); - } -} else if (uriMatcher.match("/{context}/api/users/{username}/invite")) { - if (userModule.isAuthorized("/permission/device-mgt/admin/users/invite")) { - elements = uriMatcher.elements(); - username = elements.username; - userModule.inviteUser(username); - } else { - response.sendError(403); - } } else if (uriMatcher.match("/{context}/api/users/add")) { if (userModule.isAuthorized("/permission/device-mgt/admin/users/add")) { addUserFormData = request.getContent(); @@ -84,7 +68,7 @@ if (uriMatcher.match("/{context}/api/user/login/")) { try { result = userModule.addUser(username, firstname, lastname, emailAddress, userRoles); } catch (e) { - log.error("Exception occurred while trying to add a user to MDM User Store", e); + log.error("Exception occurred while trying to add a user to DC User Store", e); // http status code 400 refers to - Bad request. result = 400; } @@ -99,7 +83,7 @@ if (uriMatcher.match("/{context}/api/user/login/")) { try { result = userModule.removeUser(username); } catch (e) { - log.error("Exception occurred while trying to remove a user from MDM User Store", e); + log.error("Exception occurred while trying to remove a user from DC User Store", e); // http status code 400 refers to - Bad request. result = 400; } diff --git a/modules/distribution/src/repository/jaggeryapps/iot/config/config.json b/modules/distribution/src/repository/jaggeryapps/iot/config/config.json index 56d43076..95a917df 100644 --- a/modules/distribution/src/repository/jaggeryapps/iot/config/config.json +++ b/modules/distribution/src/repository/jaggeryapps/iot/config/config.json @@ -1,27 +1,16 @@ { - "appContext" : "/mdm/", + "appContext" : "/iot/", "apiContext" : "api", "httpsURL": "%https.ip%", "httpURL": "%http.ip%", - "iOSConfigRoot": "%https.ip%/ios/", - "device": { - "ios": { - "location": "%http.ip%/mdm/public/asset-download-agent/asset/ios-agent.ipa", - "bundleid": "org.wso2.carbon.mdm.mobileservices.ios.agent.iOSMDMAgent", - "version": "1.0", - "appname": "MDM Agent" - } - }, - "androidAgentApp" : "android-agent.apk", - "windowsConfigRoot": "http://10.10.10.198:9763/mdm-windows-api/services/federated/bst/authentication", "ssoConfiguration": { "enabled": false, - "issuer": "mdm", - "appName": "mdm", + "issuer": "iot", + "appName": "iot", "identityProviderURL": "%https.ip%/sso/samlsso.jag", "responseSigningEnabled": "true", "keyStorePassword": "wso2carbon", "identityAlias": "wso2carbon", "keyStoreName": "/repository/resources/security/wso2carbon.jks" } -} +} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/config/dc-props.js b/modules/distribution/src/repository/jaggeryapps/iot/config/dc-props.js new file mode 100644 index 00000000..7911f514 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/config/dc-props.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var config = function () { + var conf = application.get("PINCH_CONFIG"); + if (!conf) {//if not in cache + var pinch = require('/modules/pinch.min.js').pinch; + var server = require('carbon').server; + var config = require('/config/config.json'); + pinch(config, /^/, function (path, key, value) { + if ((typeof value === 'string') && value.indexOf('%https.ip%') > -1) { + return value.replace('%https.ip%', server.address("https")); + } else if ((typeof value === 'string') && value.indexOf('%http.ip%') > -1) { + return value.replace('%http.ip%', server.address("http")); + } + return value; + }); + application.put("PINCH_CONFIG", config);//caching + conf = config; + } + return conf; +}; \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/config/init.js b/modules/distribution/src/repository/jaggeryapps/iot/config/init.js index 05ca53df..d492ae79 100644 --- a/modules/distribution/src/repository/jaggeryapps/iot/config/init.js +++ b/modules/distribution/src/repository/jaggeryapps/iot/config/init.js @@ -17,9 +17,10 @@ */ var carbonModule = require("carbon"); +var dcProps = require('/config/dc-props.js').config(); var carbonServer = new carbonModule.server.Server({ tenanted: true, - url: mdmProps.httpsURL + '/admin' + url: dcProps.httpsURL + '/admin' }); application.put("carbonServer", carbonServer); var userModule = require("/modules/user.js").userModule; diff --git a/modules/distribution/src/repository/jaggeryapps/iot/jaggery.conf b/modules/distribution/src/repository/jaggeryapps/iot/jaggery.conf index 28d4612a..d7f4f3bb 100755 --- a/modules/distribution/src/repository/jaggeryapps/iot/jaggery.conf +++ b/modules/distribution/src/repository/jaggeryapps/iot/jaggery.conf @@ -1,16 +1,16 @@ { - "displayName": "Device Cloud", + "displayName": "Fuse Sample", "logLevel": "info", "initScripts": ["/config/init.js"], "urlMappings": [ - { - "url" : "/testb/*", - "path" : "test.jag" - }, { "url" : "/test/*", "path" : "test/testExecutor.jag" }, + { + "url": "/api/device/*", + "path": "/api/device-api.jag" + }, { "url": "/api/user/*", "path": "/api/user-api.jag" diff --git a/modules/distribution/src/repository/jaggeryapps/iot/layouts/enrollment.hbs b/modules/distribution/src/repository/jaggeryapps/iot/layouts/enrollment.hbs new file mode 100755 index 00000000..388ca0d4 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/layouts/enrollment.hbs @@ -0,0 +1,29 @@ + + + + + + + {{ defineZone "title"}} + + {{ defineZone "topLibCss"}} + {{ defineZone "topCss"}} + + +
+ +
+
+ +
+
+ + {{ defineZone "body"}} +
+ {{ defineZone "bottomjquery" }} + {{ defineZone "bottomLibJs" }} + {{ defineZone "bottomJs" }} + + diff --git a/modules/distribution/src/repository/jaggeryapps/iot/layouts/fluid.hbs b/modules/distribution/src/repository/jaggeryapps/iot/layouts/fluid.hbs new file mode 100755 index 00000000..fa8f2cb9 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/layouts/fluid.hbs @@ -0,0 +1,22 @@ + + + + + + + + {{ defineZone "title"}} + {{ defineZone "topLibCss"}} + {{ defineZone "topCss"}} + + +
+ + {{ defineZone "header"}} + + {{ defineZone "body"}} +
+ {{ defineZone "bottomLibJs" }} + {{ defineZone "bottomJs" }} + + diff --git a/modules/distribution/src/repository/jaggeryapps/iot/layouts/nav-fluid.hbs b/modules/distribution/src/repository/jaggeryapps/iot/layouts/nav-fluid.hbs new file mode 100755 index 00000000..00278772 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/layouts/nav-fluid.hbs @@ -0,0 +1,50 @@ + + + + + {{ defineZone "title"}} + {{ defineZone "topCss"}} + + + + +
+
+ +
+
+ {{ defineZone "left"}} +
+ +
+ {{ defineZone "content"}} +
+
+ +
+ +
+ + + + diff --git a/modules/distribution/src/repository/jaggeryapps/iot/lib/acs.jag b/modules/distribution/src/repository/jaggeryapps/iot/lib/acs.jag new file mode 100644 index 00000000..147c20a2 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/lib/acs.jag @@ -0,0 +1,49 @@ +<% +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var dataConfig = require('/config/dc-props.js').config(); +var sso = require('/modules/sso.js').sso; +var constants = require('/modules/constants.js'); +var carbonModule = require("carbon"); +var log = new Log(); +var keyStoreParams = { + keyStoreName: dataConfig.ssoConfiguration.keyStoreName, + keyStorePassword: dataConfig.ssoConfiguration.keyStorePassword, + identityAlias: dataConfig.ssoConfiguration.identityAlias +}; +sso.configure(dataConfig.ssoConfiguration.issuer, + dataConfig.ssoConfiguration.appName, + keyStoreParams, dataConfig.ssoConfiguration.identityProviderURL); +sso.acs( + function(loggedInUser) { + var carbonUser = carbonModule.server.tenantUser(loggedInUser); + session.put(constants.USER_SESSION_KEY, carbonUser); + var username = carbonUser.username; + if(log.isDebugEnabled()){ + log.debug("User logged in: "+username); + } + response.sendRedirect(dataConfig.appContext); + }, function() { + if(log.isDebugEnabled()){ + log.debug("User logged out"); + } + response.sendRedirect(dataConfig.appContext); + } +); +%> \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse-router.js b/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse-router.js new file mode 100644 index 00000000..7aab77d7 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse-router.js @@ -0,0 +1,210 @@ +//public function declarations +var route; + +(function () { + + //public + /** + * front controller entity point. acts as the main function for every request. + */ + route = function () { + //lets assume URL looks like https://my.domain.com/app/{one}/{two}/{three}/{four} + var uri = request.getRequestURI(); // = app/{one}/{two}/{three}/{four} + var parts = splitFirst(uri); + fuseState.appName = parts.head; + var path = parts.tail; // = /{one}/{two}/{three}/{four} + var handled = false; + + parts = splitFirst(path); + if (parts.head == 'public') { // {one} == 'public' + parts = splitFirst(parts.tail); + if (splitFirst(parts.tail).head == 'less') { // {three} == 'less' + handled = renderLess(parts.head, parts.tail); // renderLess({two},{three}/{four}) + } else { + handled = renderStatic(parts.head, parts.tail); + } + } else { + handled = renderPage(path); + if (!handled) { + handled = renderUnit(path); + } + } + + if (!handled) { + response.sendError(404, 'Requested resource not found'); + } + + }; + + + //private + var log = new Log('fuse.router'); + + var getMime = function (path) { + var index = path.lastIndexOf('.') + 1; + var knowMime = { + 'js': 'application/javascript', + 'html': 'text/html', + 'htm': 'text/html', + 'woff': 'application/x-font-woff', + "png": "image/png", + "css": "text/css", + "hbs": "text/x-handlebars-template", + "apk": "application/vnd.android.package-archive", + "ipa": "application/octet-stream" + }; + var mime; + if (index >= 0) { + mime = knowMime[path.substr(index)]; + } + return mime || 'text/plain'; + }; + + /** + * '/a/b/c/d' -> {'a','b/c/d'} + * @param path URI part, should start with '/' + * @returns {{head: string, tail: string}} + */ + var splitFirst = function (path) { + var firstSlashPos = path.indexOf('/', 1); + var head = path.substring(1, firstSlashPos); + var tail = path.substring(firstSlashPos); + return {head: head, tail: tail}; + }; + + /** + * @param str + * @param prefix + * @returns {boolean} true iif str starts with prefix + */ + var startsWith = function (str, prefix) { + return (str.lastIndexOf(prefix, 0) === 0); + }; + + var renderStatic = function (unit, path) { + log.debug('[' + requestId + '] for unit "' + unit + '" a request received for a static file "' + path + '"'); + var staticFile = fuse.getFile(unit, 'public' + path); + if (staticFile.isExists() && !staticFile.isDirectory()) { + response.addHeader('Content-type', getMime(path)); + response.addHeader('Cache-Control', 'public,max-age=12960000'); + staticFile.open('r'); + var stream = staticFile.getStream(); + print(stream); + staticFile.close(); + return true; + } + return false; + }; + + var renderPage = function (path) { + var jagFile; + if (path.indexOf('/', path.length - 1) !== -1) { + jagFile = new File('/pages' + path + 'index.jag'); + } else { + jagFile = new File('/pages' + path + '.jag'); + } + if (jagFile.isExists()) { + include(jagFile.getPath()); + return true; + }else{ + return false; + } + }; + + var renderUnit = function (path) { + var mainUnit = null; + var matchedUnits = fuse.getMatchedUnitDefinitions(); + fuse.addDependencies(matchedUnits); + var zones = fuseState.zones; + + // A map of maps. this is used to ensure same zone is not render twice in to same definition. + // zonesAdded = { titleZone : { zoneFromA : true, zoneFromB : true } } + var zonesAdded = {}; + + for (var i = 0; i < matchedUnits.length; i++) { + var definition = matchedUnits[i]; + + for (var j = 0; j < definition.zones.length; j++) { + var zone = definition.zones[j]; + if (!zones[zone.name]) { + zones[zone.name] = []; + zonesAdded[zone.name] = {}; + } + var zoneKey = zone.origin + ':' + zone.name; // temp unique key to identify zone form a given unit. + if (!zonesAdded[zone.name][zoneKey]) { + var zoneInfo = {unitName: definition.name}; + if (zone.origin != definition.name) { + zoneInfo.originUnitName = zone.origin; + } + zones[zone.name].push(zoneInfo); + zonesAdded[zone.name][zoneKey] = true; + } + } + + } + + var layout = fuseState.layout; + if (layout !== null) { + log.debug( + '[' + requestId + '] request for "' + path + '" will be rendered using layout "' + + layout + '" (defined in "' + mainUnit + '") and zones ' + + stringify(zones) + ); + + var output = handlebars.Handlebars.compileFile(fuse.getLayoutPath(layout))({}); + response.addHeader('Content-type', 'text/html'); + print(output); + return true; + } else { + log.debug( + '[' + requestId + '] request for "' + path + '" will can\'t be rendered, since no layout is defined' + + 'in any of the units ' + stringify(zones)); + return false; + } + }; + + function fileToString(path) { + } + + /** + * convert less file to css and print to output. add '?nocache=true' to force regenerate. + * @param unit name of the unit + * @param path the path to the less file relative to unit root (should start with slash) + * @returns {boolean} is successfully rendered. + */ + function renderLess(unit, path) { + //TODO: fix - incorrect less files makes it respond the old less even if it is nocahce. + log.debug('[' + requestId + '] for unit "' + unit + '" a request received for a less file "' + path + '"'); + var cacheKey = '/tmp/cached_' + unit + path.replace(/[^\w\.-]/g, '_'); + fuseState.currentUnit = unit; + var cachedCss = new File(cacheKey); + + //TODO: move this check to caller function ?? + if (fuseDebug || request.getParameter('nocache') == 'true' || !cachedCss.isExists()) { + var parts = splitFirst(path); + var lessPath = '/public/less' + parts.tail.replace(/\.css$/, '') + '.less'; + var lessFile = fuse.getFile(unit, lessPath); + + if (lessFile.isExists()) { + var x = require('less-rhino-1.7.5.js'); + x.compile([lessFile.getPath(), cacheKey]); + log.debug('[' + requestId + '] for unit "' + unit + '" request for "' + path + '" is cached as "' + cacheKey + '"'); + } + } + + + if (cachedCss.isExists()) { + response.addHeader('Content-type', 'text/css'); + response.addHeader('Cache-Control', 'public,max-age=12960000'); + cachedCss.open('r'); + var stream = cachedCss.getStream(); + print(stream); + cachedCss.close(); + return true; + } + return false; + + } + + +})(); diff --git a/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse.jag b/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse.jag new file mode 100644 index 00000000..597c9970 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse.jag @@ -0,0 +1,29 @@ +<% + +//global object to pass request stat among fuse framework files. +var fuseState = { + zones: {}, + appName: '', + zoneStack: [], + currentUnit: null +}; + +var requestId = function makeId() { + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (var i = 0; i < 5; i++) + text += possible.charAt(Math.floor(Math.random() * possible.length)); + + return text; +}(); +var fuseDebug = false; +//var fuseDebug = true; + +var handlebars = require('handlebars-helpers.js'); +var fuseRouter = require('fuse-router.js'); +var fuse = require('fuse.js'); + + +fuseRouter.route(); + +%> diff --git a/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse.js b/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse.js new file mode 100644 index 00000000..f802f4be --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/lib/fuse.js @@ -0,0 +1,417 @@ +//public function declarations +var getHbsFile, getFile, toRelativePath, cleanupAncestors, + getUnitPath, getMatchedUnitDefinitions, getZoneDefinition, getUnitDefinition, + getUnitDefinitions, getLayoutPath; + +(function () { + //private + var log = new Log('fuse.core'); + var lookUpTable = null; + var definitions = null; + + var initLookUp = function (definitions) { + if (lookUpTable === null) { + lookUpTable = {}; + for (var i = 0; i < definitions.length; i++) { + var definition = definitions[i]; + lookUpTable[definition.name] = i; + } + } + }; + + var isMatched = function (definition, layout) { + var urlMatch = function (pattern) { + var uriMatcher = new URIMatcher(request.getRequestURI()); + return Boolean(uriMatcher.match('/{appName}' + pattern)); + }; + var permission = function (permissionStr) { + var carbonModule = require("carbon"); + var carbonServer = application.get("carbonServer"); + var carbonUser = session.get("USER"); + if (carbonUser) { + var userManager = new carbonModule.user.UserManager(carbonServer, carbonUser.tenantId); + var user = new carbonModule.user.User(userManager, carbonUser.username); + return user.isAuthorized(permissionStr, "ui.execute"); + } + return false; + }; + var config = {'theme': 'default'}; + var predicateStr = definition.definition.predicate; + if (predicateStr) { + var js = 'function(config,urlMatch,permission,layout){ return ' + predicateStr + ';}'; + return Boolean(eval(js)(config, urlMatch,permission, layout ? layout : NaN)); + } + return false; + }; + + var getAncestorModels = function (unit) { + var unitModel = getUnitDefinition(unit); + var ancestors = [unitModel]; + var parentName; + while ((parentName = unitModel.definition.extends) != null) { + unitModel = getUnitDefinition(parentName); + ancestors.push(unitModel); + } + return ancestors; + }; + + addDependencies = function (unitModels) { + var resolved = {}; + for (var i = 0; i < unitModels.length; i++) { + resolved[unitModels[i].name] = true; + } + + for (i = 0; i < unitModels.length; i++) { + var unitModel = unitModels[i]; + var dependencies = unitModel.definition.dependencies; + if (dependencies) { + for (var j = 0; j < dependencies.length; j++) { + var dependencyName = dependencies[j]; + unitModels.push(getUnitDefinition(dependencyName)); + resolved[dependencyName] = true; + } + } + } + + + }; + + + //public + getMatchedUnitDefinitions = function () { + //TODO: return map not list + var unitDefinitions = getUnitDefinitions(); + var matched = []; + var unMatched = []; + var layout = null; + var mainUnit = null; + + var addToMatched = function (model) { + matched.push(model); + if (model.layout) { + if (layout == null) { + layout = model.layout; + mainUnit = model.name; + } else { + log.warn( + '[' + requestId + '] multiple layouts ' + mainUnit + ':' + + layout + ' vs ' + model.name + ':' + model.layout + ); + } + } + }; + + + // first pass + for (var i = 0; i < unitDefinitions.length; i++) { + var unitDefinition = unitDefinitions[i]; + if (isMatched(unitDefinition)) { + addToMatched(unitDefinition); + } else { + unMatched.push(unitDefinition); + } + } + + fuseState.layout = layout; + + // second pass : we have to do this two passes since we don't know the layout + // first time around + if (layout) { + for (i = 0; i < unMatched.length; i++) { + unitDefinition = unMatched[i]; + if (isMatched(unitDefinition, layout)) { + addToMatched(unitDefinition) + } + } + } + + + var toDelete = []; + + for (i = 0; i < matched.length; i++) { + var ancestors = getAncestorModels(matched[i].name); + for (var j = 1; j < ancestors.length; j++) { + var ancestor = ancestors[j]; + toDelete.push(ancestor.name); + } + } + + + for (i = matched.length - 1; i >= 0; i--) { + //log.info(matched[i].name); + if (toDelete.indexOf(matched[i].name) >= 0) { + matched.splice(i, 1); + } + } + + return matched; + }; + + getUnitDefinition = function (unit) { + var definitions = getUnitDefinitions(); + initLookUp(definitions); + var model = definitions[lookUpTable[unit]]; + if (!model) { + log.warn('[' + requestId + '] unit "' + unit + '" does not exits'); + throw '[' + requestId + '] unit "' + unit + '" does not exits'; + } + return model; + }; + + var flattenAllInheritance = function (unitModels) { + var hasFlattend = {}; + for (var i = 0; i < unitModels.length; i++) { + var model = unitModels[i]; + if (!hasFlattend[model]) { + var ancestors = getAncestorModels(model.name); + for (var j = ancestors.length - 1; j >= 1; j--) { + flattenInheritance(ancestors[j], ancestors[j - 1]); + } + } + } + }; + + var flattenInheritance = function (parent, child) { + var parentZones = parent.zones; + for (var i = 0; i < parentZones.length; i++) { + var parentZone = parentZones[i]; + child.zones.push(parentZone); + } + }; + + getUnitDefinitions = function () { + if (definitions !== null) { + return definitions; + } else { + definitions = []; + } + + var unitDirs = new File('/units').listFiles(); + for (var i = 0; i < unitDirs.length; i++) { + var unitDir = unitDirs[i]; + if (unitDir.isDirectory()) { + + var unitName = unitDir.getName(); + var unitModel = { + name: unitName, + path: unitDir.getPath() + }; + + // unit definition is read form is the .json file. + // if doesn't exits it will be an empty json. + var definitionFile = new File(fuse.getUnitPath(unitName) + '/' + unitName + '.json'); + if (definitionFile.isExists() && !definitionFile.isDirectory()) { + var path = definitionFile.getPath(); + log.debug('[' + requestId + '] reading file "' + path + '"'); + unitModel.definition = require(path); + } else { + log.warn('[' + requestId + '] for unit "' + unitName + '", unable to find a definition file'); + unitModel.definition = {}; + } + + // add the information derived by parsing hbs file to the same model + var hbsMetadata = getHbsMetadata(unitModel); + unitModel.zones = hbsMetadata.zones; + if (hbsMetadata.layout) { + unitModel.layout = hbsMetadata.layout; + } + + definitions.push(unitModel); + } + } + + addPageUnitDefinitions(definitions); + + initLookUp(definitions); + flattenAllInheritance(definitions); + + return definitions; + }; + + addPageUnitDefinitions = function (unitModels, dir) { + var pageFiles = new File(dir || '/pages').listFiles(); + for (var i = 0; i < pageFiles.length; i++) { + var pageFile = pageFiles[i]; + var fileName = pageFile.getName(); + if (pageFile.isDirectory()) { + addPageUnitDefinitions(unitModels, pageFile.getPath()) + } else if (fileName.indexOf('.hbs', fileName.length - 4) !== -1) { // File name ends with '.hbs' + + var isLeaf = true; + //path relative to app root + var relativePath = pageFile.getPath() + .substring(6 + pageFile.getPath().indexOf('/pages/'), pageFile.getPath().length - 4); + + if (relativePath.match(/\/index$/)) { + relativePath = relativePath.replace(/\/index$/, ''); + var parentFile = new File(pageFile.getPath().substr(0, pageFile.getPath().lastIndexOf('/'))); + var hasSiblings = parentFile.listFiles().length != 1; + if (hasSiblings) { + isLeaf = false; + } + } + + //this will be used as a name for the virtual unit, useful for debugging purposes. + var unitName = (relativePath == '' ? 'index' : relativePath.substr(1).replace(/\//, '-') ) + '-page'; + + var predicate = "urlMatch('" + relativePath + "')"; + // leaf is page that can handle multiple URLs. in this case it should have a wildcard at end. + // but since our current matcher doesn't support {/wildcard*} patten, "OR" ( || ) is used + if (isLeaf) { + predicate += " || urlMatch('" + relativePath + "/{+wildcard}')"; + } + var unitModel = { + name: unitName, + + path: pageFile.getPath(), + definition: {predicate: predicate} + }; + var hbsMetadata = getHbsMetadata(unitModel); + unitModel.zones = hbsMetadata.zones; + if (hbsMetadata.layout) { + unitModel.layout = hbsMetadata.layout; + } + + unitModels.push(unitModel); + } + } + }; + + + getLayoutPath = function (layout) { + return '/layouts/' + layout + '.hbs'; + }; + + getHbsFile = function (unit) { + // we determining if it's page unit or a proper unit + // by checking if path ends with '.hbs' + // TODO: improve getFile to do include this logic + if (unit.path.indexOf('.hbs', unit.path.length - 4) !== -1) { + return new File(unit.path); + } else { + return new File(unit.path + '/' + unit.name + '.hbs'); + } + }; + + var getHbsMetadata = function (unit) { + var zoneDef = {'zones': []}; + var hbsFile = getHbsFile(unit); + if (!hbsFile.isExists()) { + return zoneDef; + } + var output = handlebars.Handlebars.compileFile(hbsFile)({}); + var zonesAndLayouts = output.trim().split(/\s+/gm); + for (var i = 0; i < zonesAndLayouts.length; i++) { + var name = zonesAndLayouts[i]; + if (name.lastIndexOf('zone_', 0) === 0) { + zoneDef.zones.push({name: name.substr(5), origin: unit.name}); + } else if (name.lastIndexOf('layout_', 0) === 0) { + zoneDef.layout = name.substr(7); + } + } + return zoneDef; + }; + + + getUnitPath = function (unit) { + return '/units/' + unit; + }; + + cleanupAncestors = function (units) { + var toDelete = {}; + var len = units.length; + for (var i = 0; i < len; i++) { + var unit = units[i]; + if (!toDelete[unit]) { + var ancestors = getAncestorModels(unit.name); + for (var j = 1; j < ancestors.length; j++) { + toDelete[ancestors[j].name] = unit; + } + } + } + while (len--) { + if (toDelete[units[len]]) { + log.debug( + '[' + requestId + '] unit "' + units[len] + + '" is overridden by "' + toDelete[units[len]] + '"' + ); + units.splice(len, 1); + } + } + }; + + toRelativePath = function (path) { + var start = 0; + if (path.lastIndexOf('/units/', 0) == 0) { + start = 7; // len('/units/') + } + var slashPos = path.indexOf('/', 7); + return { + unit: path.substring(start, slashPos), + path: path.substr(slashPos) + } + }; + + /** + * Get a file inside a unit by relative path. if the file is not available in the given unit, + * the closest ancestor's file will be returned. if an optional suffix is used the relative path is + * calculated as ( path + < unit name > + opt_suffix ). if no such a file exists a returned file object will + * point to provided unit's non-existing file location (not to any ancestors). + * + * @param unitName name of the unit + * @param path path relative to unit root. + * @param opt_suffix + * @returns {File} + */ + getFile = function (unitName, path, opt_suffix) { + var slashPath = ((path[0] === '/') ? '' : '/') + path; + var selfFileName = ''; + var fileName = ''; + if (opt_suffix) { + selfFileName = unitName + opt_suffix; + slashPath = slashPath + ((slashPath[slashPath.length - 1] === '/') ? '' : '/'); + } + + //TODO: remove this hack that makes in page-unit, any file is same + var unitDef = getUnitDefinition(unitName); + if (unitDef.path.indexOf('.hbs', unitDef.path.length - 4) !== -1) { + if (opt_suffix.indexOf('.hbs', opt_suffix.length - 4) !== -1) { + return new File(unitDef.path); + } else { + return new File(unitDef.path.replace(/.hbs$/, opt_suffix)); + } + } + + var selfFile = new File(getUnitPath(unitName) + slashPath + selfFileName); + if (selfFile.isExists()) { + log.debug( + '[' + requestId + '] for unit "' + unitName + '" file resolved : "' + + slashPath + selfFileName + '" -> "' + selfFile.getPath() + '"' + ); + + return selfFile; + } + + var ancestors = getAncestorModels(unitName); + for (var i = 1; i < ancestors.length; i++) { + var ancestorName = ancestors[i].name; + if (opt_suffix) { + fileName = ancestorName + opt_suffix; + } + var file = new File(getUnitPath(ancestorName) + slashPath + fileName); + if (file.isExists()) { + log.debug( + '[' + requestId + '] for unit "' + unitName + '" file resolved : "' + + slashPath + selfFileName + '" -> "' + file.getPath() + '"' + ); + return file; + } + } + log.debug( + '[' + requestId + '] for unit "' + unitName + '" (non-excising) file resolved : "' + + slashPath + selfFileName + '" -> "' + selfFile.getPath() + '"' + ); + return selfFile; + }; + +})(); \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/lib/handlebars-helpers.js b/modules/distribution/src/repository/jaggeryapps/iot/lib/handlebars-helpers.js new file mode 100644 index 00000000..3f1ed03c --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/lib/handlebars-helpers.js @@ -0,0 +1,194 @@ +var log = new Log('fuse.handlebars'); +//TODO: create a different set of helpers for init parsing + +var Handlebars = require('handlebars-v2.0.0.js').Handlebars; +var USER_SESSION_KEY = "USER"; +var getScope = function (unit,configs) { + var jsFile = fuse.getFile(unit, '', '.js'); + var templateConfigs = configs || {}; + var script; + var onRequestCb = function(){}; //Assume that onRequest function will not be defined by the user + var viewModel = {}; + var cbResult; + if (jsFile.isExists()) { + script = require(jsFile.getPath()); + //Eagerly make the viewModel the template configs + viewModel = templateConfigs; + //Check if the unit author has specified an onRequest + //callback + if(script.hasOwnProperty('onRequest')){ + script.app = { + url: '/' + fuseState.appName, + publicURL: '/' + fuseState.appName + '/public/' + unit, + "class": unit + '-unit' + }; + onRequestCb = script.onRequest; + cbResult = onRequestCb(templateConfigs); + log.info("passing configs to unit "+unit+" configs: "+stringify(templateConfigs)); + //If the execution does not yield an object we will print + //a warning as the unit author may have forgotten to return a data object + if(cbResult===undefined){ + cbResult = {}; //Give an empty data object + log.warn('[' + requestId + '] unit "' + unit + '" has a onRequest method which does not return a value.This may lead to the ' + +'unit not been rendered correctly.'); + } + viewModel = cbResult; + } + } + else{ + //If there is no script then the view should get the configurations + //passed in the unit call + viewModel = templateConfigs; + } + viewModel.app = { + url: '/' + fuseState.appName + }; + viewModel.self = { + publicURL: '/' + fuseState.appName + '/public/' + unit, + "class": unit + '-unit' + }; + return viewModel; +}; + +Handlebars.innerZones = []; +Handlebars.innerZonesFromUnit = null; + +Handlebars.registerHelper('defineZone', function (zoneName, zoneContent) { + var result = ''; + var zone = Handlebars.Utils.escapeExpression(zoneName); + fuseState.zoneStack.push(zone); + var unitsToRender = fuseState.zones[zone] || []; + + if (Handlebars.innerZones.length > 0) { + unitsToRender = fuseState.zones[Handlebars.innerZones[0]] || []; + } + + // if there is no one overriding, then display inline zone + if (zoneContent['fn'] && unitsToRender.length == 0) { + return zoneContent.fn(this).trim(); + } + + for (var i = 0; i < unitsToRender.length; i++) { + var unit = unitsToRender[i]; + if (Handlebars.innerZonesFromUnit == null || Handlebars.innerZonesFromUnit.unitName == unit.unitName) { + var template = fuse.getFile(unit.originUnitName || unit.unitName, '', '.hbs'); + log.debug('[' + requestId + '] for zone "' + zone + '" including template :"' + template.getPath() + '"'); + result += Handlebars.compileFile(template)(getScope(unit.unitName, zoneContent.data.root)); + } + } + + // we go to inner zones if result is empty, what we should really do it + // if matched zone is fully made of sub-zones. this is a hack to + // make it easy to implement. + if (result.trim().length == 0 && zoneContent['fn']) { + Handlebars.innerZones.push(zoneName); + for (i = 0; i < unitsToRender.length; i++) { + unit = unitsToRender[i]; + Handlebars.innerZonesFromUnit = unit; + result += zoneContent.fn(this).trim(); + Handlebars.innerZonesFromUnit = null; + } + Handlebars.innerZones.pop(); + return result; + } + + fuseState.zoneStack.pop(); + return new Handlebars.SafeString(result); +}); + +Handlebars.registerHelper('zone', function (zoneName, zoneContent) { + var currentZone = fuseState.zoneStack[fuseState.zoneStack.length - 1]; + if (currentZone == null) { + return 'zone_' + zoneName + ' '; + } + + // if it's exact zone match or if any in inner zone matches we render zone. + // this second condition is a hack. what we should really do is to keep another stack, + // and only match with the peek of that stack and always fill it with next in innerZone stack. + if (zoneName == currentZone || Handlebars.innerZones.indexOf(zoneName) >= 0) { + return zoneContent.fn(this).trim(); + } else { + return ''; + } +}); + +Handlebars.registerHelper('layout', function (layoutName) { + var currentZone = fuseState.zoneStack[fuseState.zoneStack.length - 1]; + if (currentZone == null) { + return 'layout_' + layoutName; + } else { + return ''; + } +}); + +Handlebars.registerHelper('authorized', function () { + var currentZone = fuseState.zoneStack[fuseState.zoneStack.length - 1]; + if (currentZone == null) { + return ''; + } else { + var loggedUser = session.get(USER_SESSION_KEY); + if(loggedUser == null){ + response.sendRedirect("/"+ fuseState.appName + "/login"); + exit(); + } + } +}); + +Handlebars.registerHelper('unit', function (unitName,options) { + var unitDef = fuse.getUnitDefinition(unitName); + var baseUnit = null; + var templateConfigs = options.hash || {}; + for (var i = 0; i < unitDef.zones.length; i++) { + var zone = unitDef.zones[i]; + if (zone.name == 'main') { + baseUnit = zone.origin; + } else { + var golbalZone = fuseState.zones[zone.name]; + if (!golbalZone) { + fuseState.zones[zone.name] = [{"unitName": unitName}]; + } else { + fuseState.zones[zone.name].push({"unitName": unitName}); + } + } + } + if (baseUnit == null) { + log.error('unit does not have a main zone'); + } + //TODO warn when unspecified decencies are included. + fuseState.zoneStack.push('main'); + var template = fuse.getFile(baseUnit, '', '.hbs'); + log.info('[' + requestId + '] including "' + baseUnit + '"'+" with configs "+stringify(templateConfigs)); + var result = new Handlebars.SafeString(Handlebars.compileFile(template)(getScope(baseUnit,templateConfigs))); + fuseState.zoneStack.pop(); + return result; +}); + +Handlebars.compileFile = function (file) { + //TODO: remove this overloaded argument + var f = (typeof file === 'string') ? new File(file) : file; + + if (!Handlebars.cache) { + Handlebars.cache = {}; + } + + if (Handlebars.cache[f.getPath()] != null) { + return Handlebars.cache[f.getPath()]; + } + + f.open('r'); + log.debug('[' + requestId + '] reading file "' + f.getPath() + '"'); + var content = f.readAll().trim(); + f.close(); + var compiled = Handlebars.compile(content); + Handlebars.cache[f.getPath()] = compiled; + return compiled; +}; +Handlebars.registerHelper('equal', function(lvalue, rvalue, options) { + if (arguments.length < 3) + throw new Error("Handlebars Helper equal needs 2 parameters"); + if( lvalue!=rvalue ) { + return options.inverse(this); + } else { + return options.fn(this); + } +}); diff --git a/modules/distribution/src/repository/jaggeryapps/iot/lib/handlebars-v2.0.0.js b/modules/distribution/src/repository/jaggeryapps/iot/lib/handlebars-v2.0.0.js new file mode 100644 index 00000000..f826bbfd --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/lib/handlebars-v2.0.0.js @@ -0,0 +1,3079 @@ +/*! + + handlebars v2.0.0 + +Copyright (C) 2011-2014 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license +*/ +/* exported Handlebars */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.Handlebars = root.Handlebars || factory(); + } +}(this, function () { +// handlebars/safe-string.js +var __module4__ = (function() { + "use strict"; + var __exports__; + // Build out our basic SafeString type + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = function() { + return "" + this.string; + }; + + __exports__ = SafeString; + return __exports__; +})(); + +// handlebars/utils.js +var __module3__ = (function(__dependency1__) { + "use strict"; + var __exports__ = {}; + /*jshint -W004 */ + var SafeString = __dependency1__; + + var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var badChars = /[&<>"'`]/g; + var possible = /[&<>"'`]/; + + function escapeChar(chr) { + return escape[chr]; + } + + function extend(obj /* , ...source */) { + for (var i = 1; i < arguments.length; i++) { + for (var key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + obj[key] = arguments[i][key]; + } + } + } + + return obj; + } + + __exports__.extend = extend;var toString = Object.prototype.toString; + __exports__.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + var isFunction = function(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + /* istanbul ignore next */ + if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + var isFunction; + __exports__.isFunction = isFunction; + /* istanbul ignore next */ + var isArray = Array.isArray || function(value) { + return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; + }; + __exports__.isArray = isArray; + + function escapeExpression(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof SafeString) { + return string.toString(); + } else if (string == null) { + return ""; + } else if (!string) { + return string + ''; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = "" + string; + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + } + + __exports__.escapeExpression = escapeExpression;function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + __exports__.isEmpty = isEmpty;function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; + } + + __exports__.appendContextPath = appendContextPath; + return __exports__; +})(__module4__); + +// handlebars/exception.js +var __module5__ = (function() { + "use strict"; + var __exports__; + + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + var line; + if (node && node.firstLine) { + line = node.firstLine; + + message += ' - ' + line + ':' + node.firstColumn; + } + + var tmp = Error.prototype.constructor.call(this, message); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } + + if (line) { + this.lineNumber = line; + this.column = node.firstColumn; + } + } + + Exception.prototype = new Error(); + + __exports__ = Exception; + return __exports__; +})(); + +// handlebars/base.js +var __module2__ = (function(__dependency1__, __dependency2__) { + "use strict"; + var __exports__ = {}; + var Utils = __dependency1__; + var Exception = __dependency2__; + + var VERSION = "2.0.0"; + __exports__.VERSION = VERSION;var COMPILER_REVISION = 6; + __exports__.COMPILER_REVISION = COMPILER_REVISION; + var REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '== 1.0.0-rc.3', + 3: '== 1.0.0-rc.4', + 4: '== 1.x.x', + 5: '== 2.0.0-alpha.x', + 6: '>= 2.0.0-beta.1' + }; + __exports__.REVISION_CHANGES = REVISION_CHANGES; + var isArray = Utils.isArray, + isFunction = Utils.isFunction, + toString = Utils.toString, + objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials) { + this.helpers = helpers || {}; + this.partials = partials || {}; + + registerDefaultHelpers(this); + } + + __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: logger, + log: log, + + registerHelper: function(name, fn) { + if (toString.call(name) === objectType) { + if (fn) { throw new Exception('Arg not supported with multiple helpers'); } + Utils.extend(this.helpers, name); + } else { + this.helpers[name] = fn; + } + }, + unregisterHelper: function(name) { + delete this.helpers[name]; + }, + + registerPartial: function(name, partial) { + if (toString.call(name) === objectType) { + Utils.extend(this.partials, name); + } else { + this.partials[name] = partial; + } + }, + unregisterPartial: function(name) { + delete this.partials[name]; + } + }; + + function registerDefaultHelpers(instance) { + instance.registerHelper('helperMissing', function(/* [args, ]options */) { + if(arguments.length === 1) { + // A missing field in a {{foo}} constuct. + return undefined; + } else { + // Someone is actually trying to call something, blow up. + throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'"); + } + }); + + instance.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse, + fn = options.fn; + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if (isArray(context)) { + if(context.length > 0) { + if (options.ids) { + options.ids = [options.name]; + } + + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + if (options.data && options.ids) { + var data = createFrame(options.data); + data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name); + options = {data: data}; + } + + return fn(context, options); + } + }); + + instance.registerHelper('each', function(context, options) { + if (!options) { + throw new Exception('Must pass iterator to #each'); + } + + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; + + var contextPath; + if (options.data && options.ids) { + contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + + if (isFunction(context)) { context = context.call(this); } + + if (options.data) { + data = createFrame(options.data); + } + + if(context && typeof context === 'object') { + if (isArray(context)) { + for(var j = context.length; i 0) { + throw new Exception("Invalid path: " + original, this); + } else if (part === "..") { + depth++; + depthString += '../'; + } else { + this.isScoped = true; + } + } else { + dig.push(part); + } + } + + this.original = original; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; + this.idName = depthString + this.string; + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + + this.stringModeValue = this.string; + }, + + PartialNameNode: function(name, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "PARTIAL_NAME"; + this.name = name.original; + }, + + DataNode: function(id, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "DATA"; + this.id = id; + this.stringModeValue = id.stringModeValue; + this.idName = '@' + id.stringModeValue; + }, + + StringNode: function(string, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "STRING"; + this.original = + this.string = + this.stringModeValue = string; + }, + + NumberNode: function(number, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "NUMBER"; + this.original = + this.number = number; + this.stringModeValue = Number(number); + }, + + BooleanNode: function(bool, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; + }, + + CommentNode: function(comment, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "comment"; + this.comment = comment; + + this.strip = { + inlineStandalone: true + }; + } + }; + + + // Must be exported as an object rather than the root of the module as the jison lexer + // most modify the object to operate properly. + __exports__ = AST; + return __exports__; +})(__module5__); + +// handlebars/compiler/parser.js +var __module9__ = (function() { + "use strict"; + var __exports__; + /* jshint ignore:start */ + /* istanbul ignore next */ + /* Jison generated parser */ + var handlebars = (function(){ + var parser = {trace: function trace() { }, + yy: {}, + symbols_: {"error":2,"root":3,"program":4,"EOF":5,"program_repetition0":6,"statement":7,"mustache":8,"block":9,"rawBlock":10,"partial":11,"CONTENT":12,"COMMENT":13,"openRawBlock":14,"END_RAW_BLOCK":15,"OPEN_RAW_BLOCK":16,"sexpr":17,"CLOSE_RAW_BLOCK":18,"openBlock":19,"block_option0":20,"closeBlock":21,"openInverse":22,"block_option1":23,"OPEN_BLOCK":24,"CLOSE":25,"OPEN_INVERSE":26,"inverseAndProgram":27,"INVERSE":28,"OPEN_ENDBLOCK":29,"path":30,"OPEN":31,"OPEN_UNESCAPED":32,"CLOSE_UNESCAPED":33,"OPEN_PARTIAL":34,"partialName":35,"param":36,"partial_option0":37,"partial_option1":38,"sexpr_repetition0":39,"sexpr_option0":40,"dataName":41,"STRING":42,"NUMBER":43,"BOOLEAN":44,"OPEN_SEXPR":45,"CLOSE_SEXPR":46,"hash":47,"hash_repetition_plus0":48,"hashSegment":49,"ID":50,"EQUALS":51,"DATA":52,"pathSegments":53,"SEP":54,"$accept":0,"$end":1}, + terminals_: {2:"error",5:"EOF",12:"CONTENT",13:"COMMENT",15:"END_RAW_BLOCK",16:"OPEN_RAW_BLOCK",18:"CLOSE_RAW_BLOCK",24:"OPEN_BLOCK",25:"CLOSE",26:"OPEN_INVERSE",28:"INVERSE",29:"OPEN_ENDBLOCK",31:"OPEN",32:"OPEN_UNESCAPED",33:"CLOSE_UNESCAPED",34:"OPEN_PARTIAL",42:"STRING",43:"NUMBER",44:"BOOLEAN",45:"OPEN_SEXPR",46:"CLOSE_SEXPR",50:"ID",51:"EQUALS",52:"DATA",54:"SEP"}, + productions_: [0,[3,2],[4,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[10,3],[14,3],[9,4],[9,4],[19,3],[22,3],[27,2],[21,3],[8,3],[8,3],[11,5],[11,4],[17,3],[17,1],[36,1],[36,1],[36,1],[36,1],[36,1],[36,3],[47,1],[49,3],[35,1],[35,1],[35,1],[41,2],[30,1],[53,3],[53,1],[6,0],[6,2],[20,0],[20,1],[23,0],[23,1],[37,0],[37,1],[38,0],[38,1],[39,0],[39,2],[40,0],[40,1],[48,1],[48,2]], + performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: yy.prepareProgram($$[$0-1].statements, true); return $$[$0-1]; + break; + case 2:this.$ = new yy.ProgramNode(yy.prepareProgram($$[$0]), {}, this._$); + break; + case 3:this.$ = $$[$0]; + break; + case 4:this.$ = $$[$0]; + break; + case 5:this.$ = $$[$0]; + break; + case 6:this.$ = $$[$0]; + break; + case 7:this.$ = new yy.ContentNode($$[$0], this._$); + break; + case 8:this.$ = new yy.CommentNode($$[$0], this._$); + break; + case 9:this.$ = new yy.RawBlockNode($$[$0-2], $$[$0-1], $$[$0], this._$); + break; + case 10:this.$ = new yy.MustacheNode($$[$0-1], null, '', '', this._$); + break; + case 11:this.$ = yy.prepareBlock($$[$0-3], $$[$0-2], $$[$0-1], $$[$0], false, this._$); + break; + case 12:this.$ = yy.prepareBlock($$[$0-3], $$[$0-2], $$[$0-1], $$[$0], true, this._$); + break; + case 13:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], yy.stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 14:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], yy.stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 15:this.$ = { strip: yy.stripFlags($$[$0-1], $$[$0-1]), program: $$[$0] }; + break; + case 16:this.$ = {path: $$[$0-1], strip: yy.stripFlags($$[$0-2], $$[$0])}; + break; + case 17:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], yy.stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 18:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], yy.stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 19:this.$ = new yy.PartialNode($$[$0-3], $$[$0-2], $$[$0-1], yy.stripFlags($$[$0-4], $$[$0]), this._$); + break; + case 20:this.$ = new yy.PartialNode($$[$0-2], undefined, $$[$0-1], yy.stripFlags($$[$0-3], $$[$0]), this._$); + break; + case 21:this.$ = new yy.SexprNode([$$[$0-2]].concat($$[$0-1]), $$[$0], this._$); + break; + case 22:this.$ = new yy.SexprNode([$$[$0]], null, this._$); + break; + case 23:this.$ = $$[$0]; + break; + case 24:this.$ = new yy.StringNode($$[$0], this._$); + break; + case 25:this.$ = new yy.NumberNode($$[$0], this._$); + break; + case 26:this.$ = new yy.BooleanNode($$[$0], this._$); + break; + case 27:this.$ = $$[$0]; + break; + case 28:$$[$0-1].isHelper = true; this.$ = $$[$0-1]; + break; + case 29:this.$ = new yy.HashNode($$[$0], this._$); + break; + case 30:this.$ = [$$[$0-2], $$[$0]]; + break; + case 31:this.$ = new yy.PartialNameNode($$[$0], this._$); + break; + case 32:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0], this._$), this._$); + break; + case 33:this.$ = new yy.PartialNameNode(new yy.NumberNode($$[$0], this._$)); + break; + case 34:this.$ = new yy.DataNode($$[$0], this._$); + break; + case 35:this.$ = new yy.IdNode($$[$0], this._$); + break; + case 36: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; + break; + case 37:this.$ = [{part: $$[$0]}]; + break; + case 38:this.$ = []; + break; + case 39:$$[$0-1].push($$[$0]); + break; + case 48:this.$ = []; + break; + case 49:$$[$0-1].push($$[$0]); + break; + case 52:this.$ = [$$[$0]]; + break; + case 53:$$[$0-1].push($$[$0]); + break; + } + }, + table: [{3:1,4:2,5:[2,38],6:3,12:[2,38],13:[2,38],16:[2,38],24:[2,38],26:[2,38],31:[2,38],32:[2,38],34:[2,38]},{1:[3]},{5:[1,4]},{5:[2,2],7:5,8:6,9:7,10:8,11:9,12:[1,10],13:[1,11],14:16,16:[1,20],19:14,22:15,24:[1,18],26:[1,19],28:[2,2],29:[2,2],31:[1,12],32:[1,13],34:[1,17]},{1:[2,1]},{5:[2,39],12:[2,39],13:[2,39],16:[2,39],24:[2,39],26:[2,39],28:[2,39],29:[2,39],31:[2,39],32:[2,39],34:[2,39]},{5:[2,3],12:[2,3],13:[2,3],16:[2,3],24:[2,3],26:[2,3],28:[2,3],29:[2,3],31:[2,3],32:[2,3],34:[2,3]},{5:[2,4],12:[2,4],13:[2,4],16:[2,4],24:[2,4],26:[2,4],28:[2,4],29:[2,4],31:[2,4],32:[2,4],34:[2,4]},{5:[2,5],12:[2,5],13:[2,5],16:[2,5],24:[2,5],26:[2,5],28:[2,5],29:[2,5],31:[2,5],32:[2,5],34:[2,5]},{5:[2,6],12:[2,6],13:[2,6],16:[2,6],24:[2,6],26:[2,6],28:[2,6],29:[2,6],31:[2,6],32:[2,6],34:[2,6]},{5:[2,7],12:[2,7],13:[2,7],16:[2,7],24:[2,7],26:[2,7],28:[2,7],29:[2,7],31:[2,7],32:[2,7],34:[2,7]},{5:[2,8],12:[2,8],13:[2,8],16:[2,8],24:[2,8],26:[2,8],28:[2,8],29:[2,8],31:[2,8],32:[2,8],34:[2,8]},{17:21,30:22,41:23,50:[1,26],52:[1,25],53:24},{17:27,30:22,41:23,50:[1,26],52:[1,25],53:24},{4:28,6:3,12:[2,38],13:[2,38],16:[2,38],24:[2,38],26:[2,38],28:[2,38],29:[2,38],31:[2,38],32:[2,38],34:[2,38]},{4:29,6:3,12:[2,38],13:[2,38],16:[2,38],24:[2,38],26:[2,38],28:[2,38],29:[2,38],31:[2,38],32:[2,38],34:[2,38]},{12:[1,30]},{30:32,35:31,42:[1,33],43:[1,34],50:[1,26],53:24},{17:35,30:22,41:23,50:[1,26],52:[1,25],53:24},{17:36,30:22,41:23,50:[1,26],52:[1,25],53:24},{17:37,30:22,41:23,50:[1,26],52:[1,25],53:24},{25:[1,38]},{18:[2,48],25:[2,48],33:[2,48],39:39,42:[2,48],43:[2,48],44:[2,48],45:[2,48],46:[2,48],50:[2,48],52:[2,48]},{18:[2,22],25:[2,22],33:[2,22],46:[2,22]},{18:[2,35],25:[2,35],33:[2,35],42:[2,35],43:[2,35],44:[2,35],45:[2,35],46:[2,35],50:[2,35],52:[2,35],54:[1,40]},{30:41,50:[1,26],53:24},{18:[2,37],25:[2,37],33:[2,37],42:[2,37],43:[2,37],44:[2,37],45:[2,37],46:[2,37],50:[2,37],52:[2,37],54:[2,37]},{33:[1,42]},{20:43,27:44,28:[1,45],29:[2,40]},{23:46,27:47,28:[1,45],29:[2,42]},{15:[1,48]},{25:[2,46],30:51,36:49,38:50,41:55,42:[1,52],43:[1,53],44:[1,54],45:[1,56],47:57,48:58,49:60,50:[1,59],52:[1,25],53:24},{25:[2,31],42:[2,31],43:[2,31],44:[2,31],45:[2,31],50:[2,31],52:[2,31]},{25:[2,32],42:[2,32],43:[2,32],44:[2,32],45:[2,32],50:[2,32],52:[2,32]},{25:[2,33],42:[2,33],43:[2,33],44:[2,33],45:[2,33],50:[2,33],52:[2,33]},{25:[1,61]},{25:[1,62]},{18:[1,63]},{5:[2,17],12:[2,17],13:[2,17],16:[2,17],24:[2,17],26:[2,17],28:[2,17],29:[2,17],31:[2,17],32:[2,17],34:[2,17]},{18:[2,50],25:[2,50],30:51,33:[2,50],36:65,40:64,41:55,42:[1,52],43:[1,53],44:[1,54],45:[1,56],46:[2,50],47:66,48:58,49:60,50:[1,59],52:[1,25],53:24},{50:[1,67]},{18:[2,34],25:[2,34],33:[2,34],42:[2,34],43:[2,34],44:[2,34],45:[2,34],46:[2,34],50:[2,34],52:[2,34]},{5:[2,18],12:[2,18],13:[2,18],16:[2,18],24:[2,18],26:[2,18],28:[2,18],29:[2,18],31:[2,18],32:[2,18],34:[2,18]},{21:68,29:[1,69]},{29:[2,41]},{4:70,6:3,12:[2,38],13:[2,38],16:[2,38],24:[2,38],26:[2,38],29:[2,38],31:[2,38],32:[2,38],34:[2,38]},{21:71,29:[1,69]},{29:[2,43]},{5:[2,9],12:[2,9],13:[2,9],16:[2,9],24:[2,9],26:[2,9],28:[2,9],29:[2,9],31:[2,9],32:[2,9],34:[2,9]},{25:[2,44],37:72,47:73,48:58,49:60,50:[1,74]},{25:[1,75]},{18:[2,23],25:[2,23],33:[2,23],42:[2,23],43:[2,23],44:[2,23],45:[2,23],46:[2,23],50:[2,23],52:[2,23]},{18:[2,24],25:[2,24],33:[2,24],42:[2,24],43:[2,24],44:[2,24],45:[2,24],46:[2,24],50:[2,24],52:[2,24]},{18:[2,25],25:[2,25],33:[2,25],42:[2,25],43:[2,25],44:[2,25],45:[2,25],46:[2,25],50:[2,25],52:[2,25]},{18:[2,26],25:[2,26],33:[2,26],42:[2,26],43:[2,26],44:[2,26],45:[2,26],46:[2,26],50:[2,26],52:[2,26]},{18:[2,27],25:[2,27],33:[2,27],42:[2,27],43:[2,27],44:[2,27],45:[2,27],46:[2,27],50:[2,27],52:[2,27]},{17:76,30:22,41:23,50:[1,26],52:[1,25],53:24},{25:[2,47]},{18:[2,29],25:[2,29],33:[2,29],46:[2,29],49:77,50:[1,74]},{18:[2,37],25:[2,37],33:[2,37],42:[2,37],43:[2,37],44:[2,37],45:[2,37],46:[2,37],50:[2,37],51:[1,78],52:[2,37],54:[2,37]},{18:[2,52],25:[2,52],33:[2,52],46:[2,52],50:[2,52]},{12:[2,13],13:[2,13],16:[2,13],24:[2,13],26:[2,13],28:[2,13],29:[2,13],31:[2,13],32:[2,13],34:[2,13]},{12:[2,14],13:[2,14],16:[2,14],24:[2,14],26:[2,14],28:[2,14],29:[2,14],31:[2,14],32:[2,14],34:[2,14]},{12:[2,10]},{18:[2,21],25:[2,21],33:[2,21],46:[2,21]},{18:[2,49],25:[2,49],33:[2,49],42:[2,49],43:[2,49],44:[2,49],45:[2,49],46:[2,49],50:[2,49],52:[2,49]},{18:[2,51],25:[2,51],33:[2,51],46:[2,51]},{18:[2,36],25:[2,36],33:[2,36],42:[2,36],43:[2,36],44:[2,36],45:[2,36],46:[2,36],50:[2,36],52:[2,36],54:[2,36]},{5:[2,11],12:[2,11],13:[2,11],16:[2,11],24:[2,11],26:[2,11],28:[2,11],29:[2,11],31:[2,11],32:[2,11],34:[2,11]},{30:79,50:[1,26],53:24},{29:[2,15]},{5:[2,12],12:[2,12],13:[2,12],16:[2,12],24:[2,12],26:[2,12],28:[2,12],29:[2,12],31:[2,12],32:[2,12],34:[2,12]},{25:[1,80]},{25:[2,45]},{51:[1,78]},{5:[2,20],12:[2,20],13:[2,20],16:[2,20],24:[2,20],26:[2,20],28:[2,20],29:[2,20],31:[2,20],32:[2,20],34:[2,20]},{46:[1,81]},{18:[2,53],25:[2,53],33:[2,53],46:[2,53],50:[2,53]},{30:51,36:82,41:55,42:[1,52],43:[1,53],44:[1,54],45:[1,56],50:[1,26],52:[1,25],53:24},{25:[1,83]},{5:[2,19],12:[2,19],13:[2,19],16:[2,19],24:[2,19],26:[2,19],28:[2,19],29:[2,19],31:[2,19],32:[2,19],34:[2,19]},{18:[2,28],25:[2,28],33:[2,28],42:[2,28],43:[2,28],44:[2,28],45:[2,28],46:[2,28],50:[2,28],52:[2,28]},{18:[2,30],25:[2,30],33:[2,30],46:[2,30],50:[2,30]},{5:[2,16],12:[2,16],13:[2,16],16:[2,16],24:[2,16],26:[2,16],28:[2,16],29:[2,16],31:[2,16],32:[2,16],34:[2,16]}], + defaultActions: {4:[2,1],44:[2,41],47:[2,43],57:[2,47],63:[2,10],70:[2,15],73:[2,45]}, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") + this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) + if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + /* Jison generated lexer */ + var lexer = (function(){ + var lexer = ({EOF:1, + parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + if (this.options.ranges) this.yylloc.range = [0,0]; + this.offset = 0; + return this; + }, + input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length-len-1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length-1); + this.matched = this.matched.substr(0, this.matched.length-1); + + if (lines.length-1) this.yylineno -= lines.length-1; + var r = this.yylloc.range; + + this.yylloc = {first_line: this.yylloc.first_line, + last_line: this.yylineno+1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more:function () { + this._more = true; + return this; + }, + less:function (n) { + this.unput(this.match.slice(n)); + }, + pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, + showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, + next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, + lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin:function begin(condition) { + this.conditionStack.push(condition); + }, + popState:function popState() { + return this.conditionStack.pop(); + }, + _currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, + topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, + pushState:function begin(condition) { + this.begin(condition); + }}); + lexer.options = {}; + lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end); + } + + + var YYSTATE=YY_START + switch($avoiding_name_collisions) { + case 0: + if(yy_.yytext.slice(-2) === "\\\\") { + strip(0,1); + this.begin("mu"); + } else if(yy_.yytext.slice(-1) === "\\") { + strip(0,1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if(yy_.yytext) return 12; + + break; + case 1:return 12; + break; + case 2: + this.popState(); + return 12; + + break; + case 3: + yy_.yytext = yy_.yytext.substr(5, yy_.yyleng-9); + this.popState(); + return 15; + + break; + case 4: return 12; + break; + case 5:strip(0,4); this.popState(); return 13; + break; + case 6:return 45; + break; + case 7:return 46; + break; + case 8: return 16; + break; + case 9: + this.popState(); + this.begin('raw'); + return 18; + + break; + case 10:return 34; + break; + case 11:return 24; + break; + case 12:return 29; + break; + case 13:this.popState(); return 28; + break; + case 14:this.popState(); return 28; + break; + case 15:return 26; + break; + case 16:return 26; + break; + case 17:return 32; + break; + case 18:return 31; + break; + case 19:this.popState(); this.begin('com'); + break; + case 20:strip(3,5); this.popState(); return 13; + break; + case 21:return 31; + break; + case 22:return 51; + break; + case 23:return 50; + break; + case 24:return 50; + break; + case 25:return 54; + break; + case 26:// ignore whitespace + break; + case 27:this.popState(); return 33; + break; + case 28:this.popState(); return 25; + break; + case 29:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 42; + break; + case 30:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 42; + break; + case 31:return 52; + break; + case 32:return 44; + break; + case 33:return 44; + break; + case 34:return 43; + break; + case 35:return 50; + break; + case 36:yy_.yytext = strip(1,2); return 50; + break; + case 37:return 'INVALID'; + break; + case 38:return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/,/^(?:[^\x00]*?(?=(\{\{\{\{\/)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{\{\{)/,/^(?:\}\}\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^\s*(~)?\}\})/,/^(?:\{\{(~)?\s*else\s*(~)?\}\})/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; + lexer.conditions = {"mu":{"rules":[6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[5],"inclusive":false},"raw":{"rules":[3,4],"inclusive":false},"INITIAL":{"rules":[0,1,38],"inclusive":true}}; + return lexer;})() + parser.lexer = lexer; + function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; + return new Parser; + })();__exports__ = handlebars; + /* jshint ignore:end */ + return __exports__; +})(); + +// handlebars/compiler/helpers.js +var __module10__ = (function(__dependency1__) { + "use strict"; + var __exports__ = {}; + var Exception = __dependency1__; + + function stripFlags(open, close) { + return { + left: open.charAt(2) === '~', + right: close.charAt(close.length-3) === '~' + }; + } + + __exports__.stripFlags = stripFlags; + function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) { + /*jshint -W040 */ + if (mustache.sexpr.id.original !== close.path.original) { + throw new Exception(mustache.sexpr.id.original + ' doesn\'t match ' + close.path.original, mustache); + } + + var inverse = inverseAndProgram && inverseAndProgram.program; + + var strip = { + left: mustache.strip.left, + right: close.strip.right, + + // Determine the standalone candiacy. Basically flag our content as being possibly standalone + // so our parent can determine if we actually are standalone + openStandalone: isNextWhitespace(program.statements), + closeStandalone: isPrevWhitespace((inverse || program).statements) + }; + + if (mustache.strip.right) { + omitRight(program.statements, null, true); + } + + if (inverse) { + var inverseStrip = inverseAndProgram.strip; + + if (inverseStrip.left) { + omitLeft(program.statements, null, true); + } + if (inverseStrip.right) { + omitRight(inverse.statements, null, true); + } + if (close.strip.left) { + omitLeft(inverse.statements, null, true); + } + + // Find standalone else statments + if (isPrevWhitespace(program.statements) + && isNextWhitespace(inverse.statements)) { + + omitLeft(program.statements); + omitRight(inverse.statements); + } + } else { + if (close.strip.left) { + omitLeft(program.statements, null, true); + } + } + + if (inverted) { + return new this.BlockNode(mustache, inverse, program, strip, locInfo); + } else { + return new this.BlockNode(mustache, program, inverse, strip, locInfo); + } + } + + __exports__.prepareBlock = prepareBlock; + function prepareProgram(statements, isRoot) { + for (var i = 0, l = statements.length; i < l; i++) { + var current = statements[i], + strip = current.strip; + + if (!strip) { + continue; + } + + var _isPrevWhitespace = isPrevWhitespace(statements, i, isRoot, current.type === 'partial'), + _isNextWhitespace = isNextWhitespace(statements, i, isRoot), + + openStandalone = strip.openStandalone && _isPrevWhitespace, + closeStandalone = strip.closeStandalone && _isNextWhitespace, + inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; + + if (strip.right) { + omitRight(statements, i, true); + } + if (strip.left) { + omitLeft(statements, i, true); + } + + if (inlineStandalone) { + omitRight(statements, i); + + if (omitLeft(statements, i)) { + // If we are on a standalone node, save the indent info for partials + if (current.type === 'partial') { + current.indent = (/([ \t]+$)/).exec(statements[i-1].original) ? RegExp.$1 : ''; + } + } + } + if (openStandalone) { + omitRight((current.program || current.inverse).statements); + + // Strip out the previous content node if it's whitespace only + omitLeft(statements, i); + } + if (closeStandalone) { + // Always strip the next node + omitRight(statements, i); + + omitLeft((current.inverse || current.program).statements); + } + } + + return statements; + } + + __exports__.prepareProgram = prepareProgram;function isPrevWhitespace(statements, i, isRoot) { + if (i === undefined) { + i = statements.length; + } + + // Nodes that end with newlines are considered whitespace (but are special + // cased for strip operations) + var prev = statements[i-1], + sibling = statements[i-2]; + if (!prev) { + return isRoot; + } + + if (prev.type === 'content') { + return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original); + } + } + function isNextWhitespace(statements, i, isRoot) { + if (i === undefined) { + i = -1; + } + + var next = statements[i+1], + sibling = statements[i+2]; + if (!next) { + return isRoot; + } + + if (next.type === 'content') { + return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original); + } + } + + // Marks the node to the right of the position as omitted. + // I.e. {{foo}}' ' will mark the ' ' node as omitted. + // + // If i is undefined, then the first child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitRight(statements, i, multiple) { + var current = statements[i == null ? 0 : i + 1]; + if (!current || current.type !== 'content' || (!multiple && current.rightStripped)) { + return; + } + + var original = current.string; + current.string = current.string.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), ''); + current.rightStripped = current.string !== original; + } + + // Marks the node to the left of the position as omitted. + // I.e. ' '{{foo}} will mark the ' ' node as omitted. + // + // If i is undefined then the last child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitLeft(statements, i, multiple) { + var current = statements[i == null ? statements.length - 1 : i - 1]; + if (!current || current.type !== 'content' || (!multiple && current.leftStripped)) { + return; + } + + // We omit the last node if it's whitespace only and not preceeded by a non-content node. + var original = current.string; + current.string = current.string.replace(multiple ? (/\s+$/) : (/[ \t]+$/), ''); + current.leftStripped = current.string !== original; + return current.leftStripped; + } + return __exports__; +})(__module5__); + +// handlebars/compiler/base.js +var __module8__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__) { + "use strict"; + var __exports__ = {}; + var parser = __dependency1__; + var AST = __dependency2__; + var Helpers = __dependency3__; + var extend = __dependency4__.extend; + + __exports__.parser = parser; + + var yy = {}; + extend(yy, Helpers, AST); + + function parse(input) { + // Just return if an already-compile AST was passed in. + if (input.constructor === AST.ProgramNode) { return input; } + + parser.yy = yy; + + return parser.parse(input); + } + + __exports__.parse = parse; + return __exports__; +})(__module9__, __module7__, __module10__, __module3__); + +// handlebars/compiler/compiler.js +var __module11__ = (function(__dependency1__, __dependency2__) { + "use strict"; + var __exports__ = {}; + var Exception = __dependency1__; + var isArray = __dependency2__.isArray; + + var slice = [].slice; + + function Compiler() {} + + __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + equals: function(other) { + var len = this.opcodes.length; + if (other.opcodes.length !== len) { + return false; + } + + for (var i = 0; i < len; i++) { + var opcode = this.opcodes[i], + otherOpcode = other.opcodes[i]; + if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) { + return false; + } + } + + // We know that length is the same between the two arrays because they are directly tied + // to the opcode behavior above. + len = this.children.length; + for (i = 0; i < len; i++) { + if (!this.children[i].equals(other.children[i])) { + return false; + } + } + + return true; + }, + + guid: 0, + + compile: function(program, options) { + this.opcodes = []; + this.children = []; + this.depths = {list: []}; + this.options = options; + this.stringParams = options.stringParams; + this.trackIds = options.trackIds; + + // These changes will propagate to the other compiler components + var knownHelpers = this.options.knownHelpers; + this.options.knownHelpers = { + 'helperMissing': true, + 'blockHelperMissing': true, + 'each': true, + 'if': true, + 'unless': true, + 'with': true, + 'log': true, + 'lookup': true + }; + if (knownHelpers) { + for (var name in knownHelpers) { + this.options.knownHelpers[name] = knownHelpers[name]; + } + } + + return this.accept(program); + }, + + accept: function(node) { + return this[node.type](node); + }, + + program: function(program) { + var statements = program.statements; + + for(var i=0, l=statements.length; i 0) { + varDeclarations += ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + for (var alias in this.aliases) { + if (this.aliases.hasOwnProperty(alias)) { + varDeclarations += ', ' + alias + '=' + this.aliases[alias]; + } + } + + var params = ["depth0", "helpers", "partials", "data"]; + + if (this.useDepths) { + params.push('depths'); + } + + // Perform a second pass over the output to merge content when possible + var source = this.mergeSource(varDeclarations); + + if (asObject) { + params.push(source); + + return Function.apply(this, params); + } else { + return 'function(' + params.join(',') + ') {\n ' + source + '}'; + } + }, + mergeSource: function(varDeclarations) { + var source = '', + buffer, + appendOnly = !this.forceBuffer, + appendFirst; + + for (var i = 0, len = this.source.length; i < len; i++) { + var line = this.source[i]; + if (line.appendToBuffer) { + if (buffer) { + buffer = buffer + '\n + ' + line.content; + } else { + buffer = line.content; + } + } else { + if (buffer) { + if (!source) { + appendFirst = true; + source = buffer + ';\n '; + } else { + source += 'buffer += ' + buffer + ';\n '; + } + buffer = undefined; + } + source += line + '\n '; + + if (!this.environment.isSimple) { + appendOnly = false; + } + } + } + + if (appendOnly) { + if (buffer || !source) { + source += 'return ' + (buffer || '""') + ';\n'; + } + } else { + varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer()); + if (buffer) { + source += 'return buffer + ' + buffer + ';\n'; + } else { + source += 'return buffer;\n'; + } + } + + if (varDeclarations) { + source = 'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n ') + source; + } + + return source; + }, + + // [blockValue] + // + // On stack, before: hash, inverse, program, value + // On stack, after: return value of blockHelperMissing + // + // The purpose of this opcode is to take a block of the form + // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and + // replace it on the stack with the result of properly + // invoking blockHelperMissing. + blockValue: function(name) { + this.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; + + var params = [this.contextName(0)]; + this.setupParams(name, 0, params); + + var blockName = this.popStack(); + params.splice(1, 0, blockName); + + this.push('blockHelperMissing.call(' + params.join(', ') + ')'); + }, + + // [ambiguousBlockValue] + // + // On stack, before: hash, inverse, program, value + // Compiler value, before: lastHelper=value of last found helper, if any + // On stack, after, if no lastHelper: same as [blockValue] + // On stack, after, if lastHelper: value + ambiguousBlockValue: function() { + this.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; + + // We're being a bit cheeky and reusing the options value from the prior exec + var params = [this.contextName(0)]; + this.setupParams('', 0, params, true); + + this.flushInline(); + + var current = this.topStack(); + params.splice(1, 0, current); + + this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }"); + }, + + // [appendContent] + // + // On stack, before: ... + // On stack, after: ... + // + // Appends the string value of `content` to the current buffer + appendContent: function(content) { + if (this.pendingContent) { + content = this.pendingContent + content; + } + + this.pendingContent = content; + }, + + // [append] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Coerces `value` to a String and appends it to the current buffer. + // + // If `value` is truthy, or 0, it is coerced into a string and appended + // Otherwise, the empty string is appended + append: function() { + // Force anything that is inlined onto the stack so we don't have duplication + // when we examine local + this.flushInline(); + var local = this.popStack(); + this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }'); + if (this.environment.isSimple) { + this.pushSource("else { " + this.appendToBuffer("''") + " }"); + } + }, + + // [appendEscaped] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Escape `value` and append it to the buffer + appendEscaped: function() { + this.aliases.escapeExpression = 'this.escapeExpression'; + + this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")")); + }, + + // [getContext] + // + // On stack, before: ... + // On stack, after: ... + // Compiler value, after: lastContext=depth + // + // Set the value of the `lastContext` compiler value to the depth + getContext: function(depth) { + this.lastContext = depth; + }, + + // [pushContext] + // + // On stack, before: ... + // On stack, after: currentContext, ... + // + // Pushes the value of the current context onto the stack. + pushContext: function() { + this.pushStackLiteral(this.contextName(this.lastContext)); + }, + + // [lookupOnContext] + // + // On stack, before: ... + // On stack, after: currentContext[name], ... + // + // Looks up the value of `name` on the current context and pushes + // it onto the stack. + lookupOnContext: function(parts, falsy, scoped) { + /*jshint -W083 */ + var i = 0, + len = parts.length; + + if (!scoped && this.options.compat && !this.lastContext) { + // The depthed query is expected to handle the undefined logic for the root level that + // is implemented below, so we evaluate that directly in compat mode + this.push(this.depthedLookup(parts[i++])); + } else { + this.pushContext(); + } + + for (; i < len; i++) { + this.replaceStack(function(current) { + var lookup = this.nameLookup(current, parts[i], 'context'); + // We want to ensure that zero and false are handled properly if the context (falsy flag) + // needs to have the special handling for these values. + if (!falsy) { + return ' != null ? ' + lookup + ' : ' + current; + } else { + // Otherwise we can use generic falsy handling + return ' && ' + lookup; + } + }); + } + }, + + // [lookupData] + // + // On stack, before: ... + // On stack, after: data, ... + // + // Push the data lookup operator + lookupData: function(depth, parts) { + /*jshint -W083 */ + if (!depth) { + this.pushStackLiteral('data'); + } else { + this.pushStackLiteral('this.data(data, ' + depth + ')'); + } + + var len = parts.length; + for (var i = 0; i < len; i++) { + this.replaceStack(function(current) { + return ' && ' + this.nameLookup(current, parts[i], 'data'); + }); + } + }, + + // [resolvePossibleLambda] + // + // On stack, before: value, ... + // On stack, after: resolved value, ... + // + // If the `value` is a lambda, replace it on the stack by + // the return value of the lambda + resolvePossibleLambda: function() { + this.aliases.lambda = 'this.lambda'; + + this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')'); + }, + + // [pushStringParam] + // + // On stack, before: ... + // On stack, after: string, currentContext, ... + // + // This opcode is designed for use in string mode, which + // provides the string value of a parameter along with its + // depth rather than resolving it immediately. + pushStringParam: function(string, type) { + this.pushContext(); + this.pushString(type); + + // If it's a subexpression, the string result + // will be pushed after this opcode. + if (type !== 'sexpr') { + if (typeof string === 'string') { + this.pushString(string); + } else { + this.pushStackLiteral(string); + } + } + }, + + emptyHash: function() { + this.pushStackLiteral('{}'); + + if (this.trackIds) { + this.push('{}'); // hashIds + } + if (this.stringParams) { + this.push('{}'); // hashContexts + this.push('{}'); // hashTypes + } + }, + pushHash: function() { + if (this.hash) { + this.hashes.push(this.hash); + } + this.hash = {values: [], types: [], contexts: [], ids: []}; + }, + popHash: function() { + var hash = this.hash; + this.hash = this.hashes.pop(); + + if (this.trackIds) { + this.push('{' + hash.ids.join(',') + '}'); + } + if (this.stringParams) { + this.push('{' + hash.contexts.join(',') + '}'); + this.push('{' + hash.types.join(',') + '}'); + } + + this.push('{\n ' + hash.values.join(',\n ') + '\n }'); + }, + + // [pushString] + // + // On stack, before: ... + // On stack, after: quotedString(string), ... + // + // Push a quoted version of `string` onto the stack + pushString: function(string) { + this.pushStackLiteral(this.quotedString(string)); + }, + + // [push] + // + // On stack, before: ... + // On stack, after: expr, ... + // + // Push an expression onto the stack + push: function(expr) { + this.inlineStack.push(expr); + return expr; + }, + + // [pushLiteral] + // + // On stack, before: ... + // On stack, after: value, ... + // + // Pushes a value onto the stack. This operation prevents + // the compiler from creating a temporary variable to hold + // it. + pushLiteral: function(value) { + this.pushStackLiteral(value); + }, + + // [pushProgram] + // + // On stack, before: ... + // On stack, after: program(guid), ... + // + // Push a program expression onto the stack. This takes + // a compile-time guid and converts it into a runtime-accessible + // expression. + pushProgram: function(guid) { + if (guid != null) { + this.pushStackLiteral(this.programExpression(guid)); + } else { + this.pushStackLiteral(null); + } + }, + + // [invokeHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // Pops off the helper's parameters, invokes the helper, + // and pushes the helper's return value onto the stack. + // + // If the helper is not found, `helperMissing` is called. + invokeHelper: function(paramSize, name, isSimple) { + this.aliases.helperMissing = 'helpers.helperMissing'; + + var nonHelper = this.popStack(); + var helper = this.setupHelper(paramSize, name); + + var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing'; + this.push('((' + lookup + ').call(' + helper.callParams + '))'); + }, + + // [invokeKnownHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // This operation is used when the helper is known to exist, + // so a `helperMissing` fallback is not required. + invokeKnownHelper: function(paramSize, name) { + var helper = this.setupHelper(paramSize, name); + this.push(helper.name + ".call(" + helper.callParams + ")"); + }, + + // [invokeAmbiguous] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of disambiguation + // + // This operation is used when an expression like `{{foo}}` + // is provided, but we don't know at compile-time whether it + // is a helper or a path. + // + // This operation emits more code than the other options, + // and can be avoided by passing the `knownHelpers` and + // `knownHelpersOnly` flags at compile-time. + invokeAmbiguous: function(name, helperCall) { + this.aliases.functionType = '"function"'; + this.aliases.helperMissing = 'helpers.helperMissing'; + this.useRegister('helper'); + + var nonHelper = this.popStack(); + + this.emptyHash(); + var helper = this.setupHelper(0, name, helperCall); + + var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); + + this.push( + '((helper = (helper = ' + helperName + ' || ' + nonHelper + ') != null ? helper : helperMissing' + + (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),' + + '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))'); + }, + + // [invokePartial] + // + // On stack, before: context, ... + // On stack after: result of partial invocation + // + // This operation pops off a context, invokes a partial with that context, + // and pushes the result of the invocation back. + invokePartial: function(name, indent) { + var params = [this.nameLookup('partials', name, 'partial'), "'" + indent + "'", "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"]; + + if (this.options.data) { + params.push("data"); + } else if (this.options.compat) { + params.push('undefined'); + } + if (this.options.compat) { + params.push('depths'); + } + + this.push("this.invokePartial(" + params.join(", ") + ")"); + }, + + // [assignToHash] + // + // On stack, before: value, ..., hash, ... + // On stack, after: ..., hash, ... + // + // Pops a value off the stack and assigns it to the current hash + assignToHash: function(key) { + var value = this.popStack(), + context, + type, + id; + + if (this.trackIds) { + id = this.popStack(); + } + if (this.stringParams) { + type = this.popStack(); + context = this.popStack(); + } + + var hash = this.hash; + if (context) { + hash.contexts.push("'" + key + "': " + context); + } + if (type) { + hash.types.push("'" + key + "': " + type); + } + if (id) { + hash.ids.push("'" + key + "': " + id); + } + hash.values.push("'" + key + "': (" + value + ")"); + }, + + pushId: function(type, name) { + if (type === 'ID' || type === 'DATA') { + this.pushString(name); + } else if (type === 'sexpr') { + this.pushStackLiteral('true'); + } else { + this.pushStackLiteral('null'); + } + }, + + // HELPERS + + compiler: JavaScriptCompiler, + + compileChildren: function(environment, options) { + var children = environment.children, child, compiler; + + for(var i=0, l=children.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + /* istanbul ignore next */ + if (!this.stackSlot) { + throw new Exception('Invalid stack pop'); + } + this.stackSlot--; + } + return item; + } + }, + + topStack: function() { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; + + if (item instanceof Literal) { + return item.value; + } else { + return item; + } + }, + + contextName: function(context) { + if (this.useDepths && context) { + return 'depths[' + context + ']'; + } else { + return 'depth' + context; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + objectLiteral: function(obj) { + var pairs = []; + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + pairs.push(this.quotedString(key) + ':' + obj[key]); + } + } + + return '{' + pairs.join(',') + '}'; + }, + + setupHelper: function(paramSize, name, blockHelper) { + var params = [], + paramsInit = this.setupParams(name, paramSize, params, blockHelper); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + paramsInit: paramsInit, + name: foundHelper, + callParams: [this.contextName(0)].concat(params).join(", ") + }; + }, + + setupOptions: function(helper, paramSize, params) { + var options = {}, contexts = [], types = [], ids = [], param, inverse, program; + + options.name = this.quotedString(helper); + options.hash = this.popStack(); + + if (this.trackIds) { + options.hashIds = this.popStack(); + } + if (this.stringParams) { + options.hashTypes = this.popStack(); + options.hashContexts = this.popStack(); + } + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + program = 'this.noop'; + } + + if (!inverse) { + inverse = 'this.noop'; + } + + options.fn = program; + options.inverse = inverse; + } + + // The parameters go on to the stack in order (making sure that they are evaluated in order) + // so we need to pop them off the stack in reverse order + var i = paramSize; + while (i--) { + param = this.popStack(); + params[i] = param; + + if (this.trackIds) { + ids[i] = this.popStack(); + } + if (this.stringParams) { + types[i] = this.popStack(); + contexts[i] = this.popStack(); + } + } + + if (this.trackIds) { + options.ids = "[" + ids.join(",") + "]"; + } + if (this.stringParams) { + options.types = "[" + types.join(",") + "]"; + options.contexts = "[" + contexts.join(",") + "]"; + } + + if (this.options.data) { + options.data = "data"; + } + + return options; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(helperName, paramSize, params, useRegister) { + var options = this.objectLiteral(this.setupOptions(helperName, paramSize, params)); + + if (useRegister) { + this.useRegister('options'); + params.push('options'); + return 'options=' + options; + } else { + params.push(options); + return ''; + } + } + }; + + var reservedWords = ( + "break else new var" + + " case finally return void" + + " catch for switch while" + + " continue function this with" + + " default if throw" + + " delete in try" + + " do instanceof typeof" + + " abstract enum int short" + + " boolean export interface static" + + " byte extends long super" + + " char final native synchronized" + + " class float package throws" + + " const goto private transient" + + " debugger implements protected volatile" + + " double import public let yield" + ).split(" "); + + var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; + + for(var i=0, l=reservedWords.length; i */ + +// +// Stub out `require` in rhino +// +function require(arg) { + var split = arg.split('/'); + var resultModule = split.length == 1 ? less.modules[split[0]] : less[split[1]]; + if (!resultModule) { + throw {message: "Cannot find module '" + arg + "'"}; + } + return resultModule; +} + + +if (typeof(window) === 'undefined') { + less = {} +} +else { + less = window.less = {} +} +tree = less.tree = {}; +less.mode = 'rhino'; + +(function () { + + console = function () { + function doLog(out) { + return function (a) { + //TODO: for some reason loging dosn't work,fix later + } + } + + return { + log: doLog(log.info), + info: doLog(log.info), + error: doLog(log.error), + warn: doLog(log.warn) + }; + }(); + + less.modules = {}; + + less.modules.path = { + join: function () { + var parts = []; + for (i in arguments) { + parts = parts.concat(arguments[i].split(/\/|\\/)); + } + var result = []; + for (i in parts) { + var part = parts[i]; + if (part === '..' && result.length > 0 && result[result.length - 1] !== '..') { + result.pop(); + } else if (part === '' && result.length > 0) { + // skip + } else if (part !== '.') { + if (part.slice(-1) === '\\' || part.slice(-1) === '/') { + part = part.slice(0, -1); + } + result.push(part); + } + } + return result.join('/'); + }, + dirname: function (p) { + var path = p.split('/'); + path.pop(); + return path.join('/'); + }, + basename: function (p, ext) { + var base = p.split('/').pop(); + if (ext) { + var index = base.lastIndexOf(ext); + if (base.length === index + ext.length) { + base = base.substr(0, index); + } + } + return base; + }, + extname: function (p) { + var index = p.lastIndexOf('.'); + return index > 0 ? p.substring(index) : ''; + } + }; + + less.modules.fs = { + readFileSync: function (name) { + // read a file into a byte array + var file = new java.io.File(name); + new Log('xxxxxxxx').info(file.getCanonicalPath()); + var stream = new java.io.FileInputStream(file); + var buffer = []; + var c; + while ((c = stream.read()) != -1) { + buffer.push(c); + } + stream.close(); + return { + length: buffer.length, + toString: function (enc) { + if (enc === 'base64') { + return encodeBase64Bytes(buffer); + } else if (enc) { + return java.lang.String["(byte[],java.lang.String)"](buffer, enc); + } else { + return java.lang.String["(byte[])"](buffer); + } + } + }; + } + }; + + less.encoder = { + encodeBase64: function (str) { + return encodeBase64String(str); + } + }; + + // --------------------------------------------------------------------------------------------- + // private helper functions + // --------------------------------------------------------------------------------------------- + + function encodeBase64Bytes(bytes) { + // requires at least a JRE Platform 6 (or JAXB 1.0 on the classpath) + return javax.xml.bind.DatatypeConverter.printBase64Binary(bytes) + } + + function encodeBase64String(str) { + return encodeBase64Bytes(new java.lang.String(str).getBytes()); + } + +})(); + +var less, tree; + +// Node.js does not have a header file added which defines less +if (less === undefined) { + less = exports; + tree = require('./tree'); + less.mode = 'node'; +} +// +// less.js - parser +// +// A relatively straight-forward predictive parser. +// There is no tokenization/lexing stage, the input is parsed +// in one sweep. +// +// To make the parser fast enough to run in the browser, several +// optimization had to be made: +// +// - Matching and slicing on a huge input is often cause of slowdowns. +// The solution is to chunkify the input into smaller strings. +// The chunks are stored in the `chunks` var, +// `j` holds the current chunk index, and `currentPos` holds +// the index of the current chunk in relation to `input`. +// This gives us an almost 4x speed-up. +// +// - In many cases, we don't need to match individual tokens; +// for example, if a value doesn't hold any variables, operations +// or dynamic references, the parser can effectively 'skip' it, +// treating it as a literal. +// An example would be '1px solid #000' - which evaluates to itself, +// we don't need to know what the individual components are. +// The drawback, of course is that you don't get the benefits of +// syntax-checking on the CSS. This gives us a 50% speed-up in the parser, +// and a smaller speed-up in the code-gen. +// +// +// Token matching is done with the `$` function, which either takes +// a terminal string or regexp, or a non-terminal function to call. +// It also takes care of moving all the indices forwards. +// +// +less.Parser = function Parser(env) { + var input, // LeSS input string + i, // current index in `input` + j, // current chunk + saveStack = [], // holds state for backtracking + furthest, // furthest index the parser has gone to + chunks, // chunkified input + current, // current chunk + currentPos, // index of current chunk, in `input` + parser, + parsers, + rootFilename = env && env.filename; + + // Top parser on an import tree must be sure there is one "env" + // which will then be passed around by reference. + if (!(env instanceof tree.parseEnv)) { + env = new tree.parseEnv(env); + } + + var imports = this.imports = { + paths: env.paths || [], // Search paths, when importing + queue: [], // Files which haven't been imported yet + files: env.files, // Holds the imported parse trees + contents: env.contents, // Holds the imported file contents + contentsIgnoredChars: env.contentsIgnoredChars, // lines inserted, not in the original less + mime: env.mime, // MIME type of .less files + error: null, // Error in parsing/evaluating an import + push: function (path, currentFileInfo, importOptions, callback) { + var parserImports = this; + this.queue.push(path); + + var fileParsedFunc = function (e, root, fullPath) { + parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue + + var importedPreviously = fullPath === rootFilename; + + parserImports.files[fullPath] = root; // Store the root + + if (e && !parserImports.error) { + parserImports.error = e; + } + + callback(e, root, importedPreviously, fullPath); + }; + + if (less.Parser.importer) { + less.Parser.importer(path, currentFileInfo, fileParsedFunc, env); + } else { + less.Parser.fileLoader(path, currentFileInfo, function (e, contents, fullPath, newFileInfo) { + if (e) { + fileParsedFunc(e); + return; + } + + var newEnv = new tree.parseEnv(env); + + newEnv.currentFileInfo = newFileInfo; + newEnv.processImports = false; + newEnv.contents[fullPath] = contents; + + if (currentFileInfo.reference || importOptions.reference) { + newFileInfo.reference = true; + } + + if (importOptions.inline) { + fileParsedFunc(null, contents, fullPath); + } else { + new (less.Parser)(newEnv).parse(contents, function (e, root) { + fileParsedFunc(e, root, fullPath); + }); + } + }, env); + } + } + }; + + function save() { + currentPos = i; + saveStack.push({current: current, i: i, j: j}); + } + + function restore() { + var state = saveStack.pop(); + current = state.current; + currentPos = i = state.i; + j = state.j; + } + + function forget() { + saveStack.pop(); + } + + function sync() { + if (i > currentPos) { + current = current.slice(i - currentPos); + currentPos = i; + } + } + + function isWhitespace(str, pos) { + var code = str.charCodeAt(pos | 0); + return (code <= 32) && (code === 32 || code === 10 || code === 9); + } + + // + // Parse from a token, regexp or string, and move forward if match + // + function $(tok) { + var tokType = typeof tok, + match, length; + + // Either match a single character in the input, + // or match a regexp in the current chunk (`current`). + // + if (tokType === "string") { + if (input.charAt(i) !== tok) { + return null; + } + skipWhitespace(1); + return tok; + } + + // regexp + sync(); + if (!(match = tok.exec(current))) { + return null; + } + + length = match[0].length; + + // The match is confirmed, add the match length to `i`, + // and consume any extra white-space characters (' ' || '\n') + // which come after that. The reason for this is that LeSS's + // grammar is mostly white-space insensitive. + // + skipWhitespace(length); + + if (typeof(match) === 'string') { + return match; + } else { + return match.length === 1 ? match[0] : match; + } + } + + // Specialization of $(tok) + function $re(tok) { + if (i > currentPos) { + current = current.slice(i - currentPos); + currentPos = i; + } + var m = tok.exec(current); + if (!m) { + return null; + } + + skipWhitespace(m[0].length); + if (typeof m === "string") { + return m; + } + + return m.length === 1 ? m[0] : m; + } + + var _$re = $re; + + // Specialization of $(tok) + function $char(tok) { + if (input.charAt(i) !== tok) { + return null; + } + skipWhitespace(1); + return tok; + } + + function skipWhitespace(length) { + var oldi = i, oldj = j, + curr = i - currentPos, + endIndex = i + current.length - curr, + mem = (i += length), + inp = input, + c; + + for (; i < endIndex; i++) { + c = inp.charCodeAt(i); + if (c > 32) { + break; + } + + if ((c !== 32) && (c !== 10) && (c !== 9) && (c !== 13)) { + break; + } + } + + current = current.slice(length + i - mem + curr); + currentPos = i; + + if (!current.length && (j < chunks.length - 1)) { + current = chunks[++j]; + skipWhitespace(0); // skip space at the beginning of a chunk + return true; // things changed + } + + return oldi !== i || oldj !== j; + } + + function expect(arg, msg, index) { + // some older browsers return typeof 'function' for RegExp + var result = (Object.prototype.toString.call(arg) === '[object Function]') ? arg.call(parsers) : $(arg); + if (result) { + return result; + } + error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'" + : "unexpected token")); + } + + // Specialization of expect() + function expectChar(arg, msg) { + if (input.charAt(i) === arg) { + skipWhitespace(1); + return arg; + } + error(msg || "expected '" + arg + "' got '" + input.charAt(i) + "'"); + } + + function error(msg, type) { + var e = new Error(msg); + e.index = i; + e.type = type || 'Syntax'; + throw e; + } + + // Same as $(), but don't change the state of the parser, + // just return the match. + function peek(tok) { + if (typeof(tok) === 'string') { + return input.charAt(i) === tok; + } else { + return tok.test(current); + } + } + + // Specialization of peek() + function peekChar(tok) { + return input.charAt(i) === tok; + } + + + function getInput(e, env) { + if (e.filename && env.currentFileInfo.filename && (e.filename !== env.currentFileInfo.filename)) { + return parser.imports.contents[e.filename]; + } else { + return input; + } + } + + function getLocation(index, inputStream) { + var n = index + 1, + line = null, + column = -1; + + while (--n >= 0 && inputStream.charAt(n) !== '\n') { + column++; + } + + if (typeof index === 'number') { + line = (inputStream.slice(0, index).match(/\n/g) || "").length; + } + + return { + line: line, + column: column + }; + } + + function getDebugInfo(index, inputStream, env) { + var filename = env.currentFileInfo.filename; + if (less.mode !== 'browser' && less.mode !== 'rhino') { + filename = require('path').resolve(filename); + } + + return { + lineNumber: getLocation(index, inputStream).line + 1, + fileName: filename + }; + } + + function LessError(e, env) { + var input = getInput(e, env), + loc = getLocation(e.index, input), + line = loc.line, + col = loc.column, + callLine = e.call && getLocation(e.call, input).line, + lines = input.split('\n'); + + this.type = e.type || 'Syntax'; + this.message = e.message; + this.filename = e.filename || env.currentFileInfo.filename; + this.index = e.index; + this.line = typeof(line) === 'number' ? line + 1 : null; + this.callLine = callLine + 1; + this.callExtract = lines[callLine]; + this.stack = e.stack; + this.column = col; + this.extract = [ + lines[line - 1], + lines[line], + lines[line + 1] + ]; + } + + LessError.prototype = new Error(); + LessError.prototype.constructor = LessError; + + this.env = env = env || {}; + + // The optimization level dictates the thoroughness of the parser, + // the lower the number, the less nodes it will create in the tree. + // This could matter for debugging, or if you want to access + // the individual nodes in the tree. + this.optimization = ('optimization' in this.env) ? this.env.optimization : 1; + + // + // The Parser + // + parser = { + + imports: imports, + // + // Parse an input string into an abstract syntax tree, + // @param str A string containing 'less' markup + // @param callback call `callback` when done. + // @param [additionalData] An optional map which can contains vars - a map (key, value) of variables to apply + // + parse: function (str, callback, additionalData) { + var root, line, lines, error = null, globalVars, modifyVars, preText = ""; + + i = j = currentPos = furthest = 0; + + globalVars = (additionalData && additionalData.globalVars) ? less.Parser.serializeVars(additionalData.globalVars) + '\n' : ''; + modifyVars = (additionalData && additionalData.modifyVars) ? '\n' + less.Parser.serializeVars(additionalData.modifyVars) : ''; + + if (globalVars || (additionalData && additionalData.banner)) { + preText = ((additionalData && additionalData.banner) ? additionalData.banner : "") + globalVars; + parser.imports.contentsIgnoredChars[env.currentFileInfo.filename] = preText.length; + } + + str = str.replace(/\r\n/g, '\n'); + // Remove potential UTF Byte Order Mark + input = str = preText + str.replace(/^\uFEFF/, '') + modifyVars; + parser.imports.contents[env.currentFileInfo.filename] = str; + + // Split the input into chunks. + chunks = (function (input) { + var len = input.length, level = 0, parenLevel = 0, + lastOpening, lastOpeningParen, lastMultiComment, lastMultiCommentEndBrace, + chunks = [], emitFrom = 0, + parserCurrentIndex, currentChunkStartIndex, cc, cc2, matched; + + function fail(msg, index) { + error = new (LessError)({ + index: index || parserCurrentIndex, + type: 'Parse', + message: msg, + filename: env.currentFileInfo.filename + }, env); + } + + function emitChunk(force) { + var len = parserCurrentIndex - emitFrom; + if (((len < 512) && !force) || !len) { + return; + } + chunks.push(input.slice(emitFrom, parserCurrentIndex + 1)); + emitFrom = parserCurrentIndex + 1; + } + + for (parserCurrentIndex = 0; parserCurrentIndex < len; parserCurrentIndex++) { + cc = input.charCodeAt(parserCurrentIndex); + if (((cc >= 97) && (cc <= 122)) || (cc < 34)) { + // a-z or whitespace + continue; + } + + switch (cc) { + case 40: // ( + parenLevel++; + lastOpeningParen = parserCurrentIndex; + continue; + case 41: // ) + if (--parenLevel < 0) { + return fail("missing opening `(`"); + } + continue; + case 59: // ; + if (!parenLevel) { + emitChunk(); + } + continue; + case 123: // { + level++; + lastOpening = parserCurrentIndex; + continue; + case 125: // } + if (--level < 0) { + return fail("missing opening `{`"); + } + if (!level && !parenLevel) { + emitChunk(); + } + continue; + case 92: // \ + if (parserCurrentIndex < len - 1) { + parserCurrentIndex++; + continue; + } + return fail("unescaped `\\`"); + case 34: + case 39: + case 96: // ", ' and ` + matched = 0; + currentChunkStartIndex = parserCurrentIndex; + for (parserCurrentIndex = parserCurrentIndex + 1; parserCurrentIndex < len; parserCurrentIndex++) { + cc2 = input.charCodeAt(parserCurrentIndex); + if (cc2 > 96) { + continue; + } + if (cc2 == cc) { + matched = 1; + break; + } + if (cc2 == 92) { // \ + if (parserCurrentIndex == len - 1) { + return fail("unescaped `\\`"); + } + parserCurrentIndex++; + } + } + if (matched) { + continue; + } + return fail("unmatched `" + String.fromCharCode(cc) + "`", currentChunkStartIndex); + case 47: // /, check for comment + if (parenLevel || (parserCurrentIndex == len - 1)) { + continue; + } + cc2 = input.charCodeAt(parserCurrentIndex + 1); + if (cc2 == 47) { + // //, find lnfeed + for (parserCurrentIndex = parserCurrentIndex + 2; parserCurrentIndex < len; parserCurrentIndex++) { + cc2 = input.charCodeAt(parserCurrentIndex); + if ((cc2 <= 13) && ((cc2 == 10) || (cc2 == 13))) { + break; + } + } + } else if (cc2 == 42) { + // /*, find */ + lastMultiComment = currentChunkStartIndex = parserCurrentIndex; + for (parserCurrentIndex = parserCurrentIndex + 2; parserCurrentIndex < len - 1; parserCurrentIndex++) { + cc2 = input.charCodeAt(parserCurrentIndex); + if (cc2 == 125) { + lastMultiCommentEndBrace = parserCurrentIndex; + } + if (cc2 != 42) { + continue; + } + if (input.charCodeAt(parserCurrentIndex + 1) == 47) { + break; + } + } + if (parserCurrentIndex == len - 1) { + return fail("missing closing `*/`", currentChunkStartIndex); + } + parserCurrentIndex++; + } + continue; + case 42: // *, check for unmatched */ + if ((parserCurrentIndex < len - 1) && (input.charCodeAt(parserCurrentIndex + 1) == 47)) { + return fail("unmatched `/*`"); + } + continue; + } + } + + if (level !== 0) { + if ((lastMultiComment > lastOpening) && (lastMultiCommentEndBrace > lastMultiComment)) { + return fail("missing closing `}` or `*/`", lastOpening); + } else { + return fail("missing closing `}`", lastOpening); + } + } else if (parenLevel !== 0) { + return fail("missing closing `)`", lastOpeningParen); + } + + emitChunk(true); + return chunks; + })(str); + + if (error) { + return callback(new (LessError)(error, env)); + } + + current = chunks[0]; + + // Start with the primary rule. + // The whole syntax tree is held under a Ruleset node, + // with the `root` property set to true, so no `{}` are + // output. The callback is called when the input is parsed. + try { + root = new (tree.Ruleset)(null, this.parsers.primary()); + root.root = true; + root.firstRoot = true; + } catch (e) { + return callback(new (LessError)(e, env)); + } + + root.toCSS = (function (evaluate) { + return function (options, variables) { + options = options || {}; + var evaldRoot, + css, + evalEnv = new tree.evalEnv(options); + + // + // Allows setting variables with a hash, so: + // + // `{ color: new(tree.Color)('#f01') }` will become: + // + // new(tree.Rule)('@color', + // new(tree.Value)([ + // new(tree.Expression)([ + // new(tree.Color)('#f01') + // ]) + // ]) + // ) + // + if (typeof(variables) === 'object' && !Array.isArray(variables)) { + variables = Object.keys(variables).map(function (k) { + var value = variables[k]; + + if (!(value instanceof tree.Value)) { + if (!(value instanceof tree.Expression)) { + value = new (tree.Expression)([value]); + } + value = new (tree.Value)([value]); + } + return new (tree.Rule)('@' + k, value, false, null, 0); + }); + evalEnv.frames = [new (tree.Ruleset)(null, variables)]; + } + + try { + var preEvalVisitors = [], + visitors = [ + new (tree.joinSelectorVisitor)(), + new (tree.processExtendsVisitor)(), + new (tree.toCSSVisitor)({compress: Boolean(options.compress)}) + ], i, root = this; + + if (options.plugins) { + for (i = 0; i < options.plugins.length; i++) { + if (options.plugins[i].isPreEvalVisitor) { + preEvalVisitors.push(options.plugins[i]); + } else { + if (options.plugins[i].isPreVisitor) { + visitors.splice(0, 0, options.plugins[i]); + } else { + visitors.push(options.plugins[i]); + } + } + } + } + + for (i = 0; i < preEvalVisitors.length; i++) { + preEvalVisitors[i].run(root); + } + + evaldRoot = evaluate.call(root, evalEnv); + + for (i = 0; i < visitors.length; i++) { + visitors[i].run(evaldRoot); + } + + if (options.sourceMap) { + evaldRoot = new tree.sourceMapOutput( + { + contentsIgnoredCharsMap: parser.imports.contentsIgnoredChars, + writeSourceMap: options.writeSourceMap, + rootNode: evaldRoot, + contentsMap: parser.imports.contents, + sourceMapFilename: options.sourceMapFilename, + sourceMapURL: options.sourceMapURL, + outputFilename: options.sourceMapOutputFilename, + sourceMapBasepath: options.sourceMapBasepath, + sourceMapRootpath: options.sourceMapRootpath, + outputSourceFiles: options.outputSourceFiles, + sourceMapGenerator: options.sourceMapGenerator + }); + } + + css = evaldRoot.toCSS({ + compress: Boolean(options.compress), + dumpLineNumbers: env.dumpLineNumbers, + strictUnits: Boolean(options.strictUnits), + numPrecision: 8 + }); + } catch (e) { + throw new (LessError)(e, env); + } + + if (options.cleancss && less.mode === 'node') { + var CleanCSS = require('clean-css'), + cleancssOptions = options.cleancssOptions || {}; + + if (cleancssOptions.keepSpecialComments === undefined) { + cleancssOptions.keepSpecialComments = "*"; + } + cleancssOptions.processImport = false; + cleancssOptions.noRebase = true; + if (cleancssOptions.noAdvanced === undefined) { + cleancssOptions.noAdvanced = true; + } + + return new CleanCSS(cleancssOptions).minify(css); + } else if (options.compress) { + return css.replace(/(^(\s)+)|((\s)+$)/g, ""); + } else { + return css; + } + }; + })(root.eval); + + // If `i` is smaller than the `input.length - 1`, + // it means the parser wasn't able to parse the whole + // string, so we've got a parsing error. + // + // We try to extract a \n delimited string, + // showing the line where the parse error occured. + // We split it up into two parts (the part which parsed, + // and the part which didn't), so we can color them differently. + if (i < input.length - 1) { + i = furthest; + var loc = getLocation(i, input); + lines = input.split('\n'); + line = loc.line + 1; + + error = { + type: "Parse", + message: "Unrecognised input", + index: i, + filename: env.currentFileInfo.filename, + line: line, + column: loc.column, + extract: [ + lines[line - 2], + lines[line - 1], + lines[line] + ] + }; + } + + var finish = function (e) { + e = error || e || parser.imports.error; + + if (e) { + if (!(e instanceof LessError)) { + e = new (LessError)(e, env); + } + + return callback(e); + } + else { + return callback(null, root); + } + }; + + if (env.processImports !== false) { + new tree.importVisitor(this.imports, finish) + .run(root); + } else { + return finish(); + } + }, + + // + // Here in, the parsing rules/functions + // + // The basic structure of the syntax tree generated is as follows: + // + // Ruleset -> Rule -> Value -> Expression -> Entity + // + // Here's some Less code: + // + // .class { + // color: #fff; + // border: 1px solid #000; + // width: @w + 4px; + // > .child {...} + // } + // + // And here's what the parse tree might look like: + // + // 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]]])) + // Ruleset (Selector [Element '>', '.child'], [...]) + // ]) + // + // In general, most rules will try to parse a token with the `$()` function, and if the return + // value is truly, will return a new node, of the relevant type. Sometimes, we need to check + // first, before parsing, that's when we use `peek()`. + // + parsers: parsers = { + // + // The `primary` rule is the *entry* and *exit* point of the parser. + // The rules here can appear at any level of the parse tree. + // + // The recursive nature of the grammar is an interplay between the `block` + // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, + // as represented by this simplified grammar: + // + // primary → (ruleset | rule)+ + // ruleset → selector+ block + // block → '{' primary '}' + // + // Only at one point is the primary rule not called from the + // block rule: at the root level. + // + primary: function () { + var mixin = this.mixin, $re = _$re, root = [], node; + + while (current) { + node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() || + mixin.call() || this.comment() || this.rulesetCall() || this.directive(); + if (node) { + root.push(node); + } else { + if (!($re(/^[\s\n]+/) || $re(/^;+/))) { + break; + } + } + if (peekChar('}')) { + break; + } + } + + return root; + }, + + // We create a Comment node for CSS comments `/* */`, + // but keep the LeSS comments `//` silent, by just skipping + // over them. + comment: function () { + var comment; + + if (input.charAt(i) !== '/') { + return; + } + + if (input.charAt(i + 1) === '/') { + return new (tree.Comment)($re(/^\/\/.*/), true, i, env.currentFileInfo); + } + comment = $re(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/); + if (comment) { + return new (tree.Comment)(comment, false, i, env.currentFileInfo); + } + }, + + comments: function () { + var comment, comments = []; + + while (true) { + comment = this.comment(); + if (!comment) { + break; + } + comments.push(comment); + } + + return comments; + }, + + // + // Entities are tokens which can be found inside an Expression + // + entities: { + // + // A string, which supports escaping " and ' + // + // "milky way" 'he\'s the one!' + // + quoted: function () { + var str, j = i, e, index = i; + + if (input.charAt(j) === '~') { + j++; + e = true; + } // Escaped strings + if (input.charAt(j) !== '"' && input.charAt(j) !== "'") { + return; + } + + if (e) { + $char('~'); + } + + str = $re(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/); + if (str) { + return new (tree.Quoted)(str[0], str[1] || str[2], e, index, env.currentFileInfo); + } + }, + + // + // A catch-all word, such as: + // + // black border-collapse + // + keyword: function () { + var k; + + k = $re(/^%|^[_A-Za-z-][_A-Za-z0-9-]*/); + if (k) { + var color = tree.Color.fromKeyword(k); + if (color) { + return color; + } + return new (tree.Keyword)(k); + } + }, + + // + // A function call + // + // rgb(255, 0, 255) + // + // We also try to catch IE's `alpha()`, but let the `alpha` parser + // deal with the details. + // + // The arguments are parsed with the `entities.arguments` parser. + // + call: function () { + var name, nameLC, args, alpha_ret, index = i; + + name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(current); + if (!name) { + return; + } + + name = name[1]; + nameLC = name.toLowerCase(); + if (nameLC === 'url') { + return null; + } + + i += name.length; + + if (nameLC === 'alpha') { + alpha_ret = parsers.alpha(); + if (typeof alpha_ret !== 'undefined') { + return alpha_ret; + } + } + + $char('('); // Parse the '(' and consume whitespace. + + args = this.arguments(); + + if (!$char(')')) { + return; + } + + if (name) { + return new (tree.Call)(name, args, index, env.currentFileInfo); + } + }, + arguments: function () { + var args = [], arg; + + while (true) { + arg = this.assignment() || parsers.expression(); + if (!arg) { + break; + } + args.push(arg); + if (!$char(',')) { + break; + } + } + return args; + }, + literal: function () { + return this.dimension() || + this.color() || + this.quoted() || + this.unicodeDescriptor(); + }, + + // Assignments are argument entities for calls. + // They are present in ie filter properties as shown below. + // + // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* ) + // + + assignment: function () { + var key, value; + key = $re(/^\w+(?=\s?=)/i); + if (!key) { + return; + } + if (!$char('=')) { + return; + } + value = parsers.entity(); + if (value) { + return new (tree.Assignment)(key, value); + } + }, + + // + // Parse url() tokens + // + // We use a specific rule for urls, because they don't really behave like + // standard function calls. The difference is that the argument doesn't have + // to be enclosed within a string, so it can't be parsed as an Expression. + // + url: function () { + var value; + + if (input.charAt(i) !== 'u' || !$re(/^url\(/)) { + return; + } + + value = this.quoted() || this.variable() || + $re(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || ""; + + expectChar(')'); + + return new (tree.URL)((value.value != null || value instanceof tree.Variable) + ? value : new (tree.Anonymous)(value), env.currentFileInfo); + }, + + // + // A Variable entity, such as `@fink`, in + // + // width: @fink + 2px + // + // We use a different parser for variable definitions, + // see `parsers.variable`. + // + variable: function () { + var name, index = i; + + if (input.charAt(i) === '@' && (name = $re(/^@@?[\w-]+/))) { + return new (tree.Variable)(name, index, env.currentFileInfo); + } + }, + + // A variable entity useing the protective {} e.g. @{var} + variableCurly: function () { + var curly, index = i; + + if (input.charAt(i) === '@' && (curly = $re(/^@\{([\w-]+)\}/))) { + return new (tree.Variable)("@" + curly[1], index, env.currentFileInfo); + } + }, + + // + // A Hexadecimal color + // + // #4F3C2F + // + // `rgb` and `hsl` colors are parsed through the `entities.call` parser. + // + color: function () { + var rgb; + + if (input.charAt(i) === '#' && (rgb = $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 + colorCandidateString = colorCandidateString[1]; + if (!colorCandidateString.match(/^[A-Fa-f0-9]+$/)) { // verify if candidate consists only of allowed HEX characters + error("Invalid HEX color code"); + } + return new (tree.Color)(rgb[1]); + } + }, + + // + // A Dimension, that is, a number and a unit + // + // 0.5em 95% + // + dimension: function () { + var value, c = input.charCodeAt(i); + //Is the first char of the dimension 0-9, '.', '+' or '-' + if ((c > 57 || c < 43) || c === 47 || c == 44) { + return; + } + + value = $re(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/); + if (value) { + return new (tree.Dimension)(value[1], value[2]); + } + }, + + // + // A unicode descriptor, as is used in unicode-range + // + // U+0?? or U+00A1-00A9 + // + unicodeDescriptor: function () { + var ud; + + ud = $re(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/); + if (ud) { + return new (tree.UnicodeDescriptor)(ud[0]); + } + }, + + // + // JavaScript code to be evaluated + // + // `window.location.href` + // + javascript: function () { + var str, j = i, e; + + if (input.charAt(j) === '~') { + j++; + e = true; + } // Escaped strings + if (input.charAt(j) !== '`') { + return; + } + if (env.javascriptEnabled !== undefined && !env.javascriptEnabled) { + error("You are using JavaScript, which has been disabled."); + } + + if (e) { + $char('~'); + } + + str = $re(/^`([^`]*)`/); + if (str) { + return new (tree.JavaScript)(str[1], i, e); + } + } + }, + + // + // The variable part of a variable definition. Used in the `rule` parser + // + // @fink: + // + variable: function () { + var name; + + if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { + return name[1]; + } + }, + + // + // The variable part of a variable definition. Used in the `rule` parser + // + // @fink(); + // + rulesetCall: function () { + var name; + + if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) { + return new tree.RulesetCall(name[1]); + } + }, + + // + // extend syntax - used to extend selectors + // + extend: function (isRule) { + var elements, e, index = i, option, extendList, extend; + + if (!(isRule ? $re(/^&:extend\(/) : $re(/^:extend\(/))) { + return; + } + + do { + option = null; + elements = null; + while (!(option = $re(/^(all)(?=\s*(\)|,))/))) { + e = this.element(); + if (!e) { + break; + } + if (elements) { + elements.push(e); + } else { + elements = [e]; + } + } + + option = option && option[1]; + 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 ($char(",")); + + expect(/^\)/); + + if (isRule) { + expect(/^;/); + } + + return extendList; + }, + + // + // extendRule - used in a rule to extend all the parent selectors + // + extendRule: function () { + return this.extend(true); + }, + + // + // Mixins + // + mixin: { + // + // A Mixin call, with an optional argument list + // + // #mixins > .square(#fff); + // .rounded(4px, black); + // .button; + // + // The `while` loop is there because mixins can be + // namespaced, but we only support the child and descendant + // selector for now. + // + call: function () { + var s = input.charAt(i), important = false, index = i, elemIndex, + elements, elem, e, c, args; + + if (s !== '.' && s !== '#') { + return; + } + + save(); // stop us absorbing part of an invalid selector + + while (true) { + elemIndex = i; + e = $re(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/); + if (!e) { + break; + } + elem = new (tree.Element)(c, e, elemIndex, env.currentFileInfo); + if (elements) { + elements.push(elem); + } else { + elements = [elem]; + } + c = $char('>'); + } + + if (elements) { + if ($char('(')) { + args = this.args(true).args; + expectChar(')'); + } + + if (parsers.important()) { + important = true; + } + + if (parsers.end()) { + forget(); + return new (tree.mixin.Call)(elements, args, index, env.currentFileInfo, important); + } + } + + restore(); + }, + args: function (isCall) { + var parsers = parser.parsers, entities = parsers.entities, + returner = {args: null, variadic: false}, + expressions = [], argsSemiColon = [], argsComma = [], + isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg; + + save(); + + while (true) { + if (isCall) { + arg = parsers.detachedRuleset() || parsers.expression(); + } else { + parsers.comments(); + if (input.charAt(i) === '.' && $re(/^\.{3}/)) { + returner.variadic = true; + if ($char(";") && !isSemiColonSeperated) { + isSemiColonSeperated = true; + } + (isSemiColonSeperated ? argsSemiColon : argsComma) + .push({variadic: true}); + break; + } + arg = entities.variable() || entities.literal() || entities.keyword(); + } + + if (!arg) { + break; + } + + nameLoop = null; + if (arg.throwAwayComments) { + arg.throwAwayComments(); + } + value = arg; + var val = null; + + if (isCall) { + // Variable + if (arg.value && arg.value.length == 1) { + val = arg.value[0]; + } + } else { + val = arg; + } + + if (val && val instanceof tree.Variable) { + if ($char(':')) { + if (expressions.length > 0) { + if (isSemiColonSeperated) { + error("Cannot mix ; and , as delimiter types"); + } + expressionContainsNamed = true; + } + + // we do not support setting a ruleset as a default variable - it doesn't make sense + // However if we do want to add it, there is nothing blocking it, just don't error + // and remove isCall dependency below + value = (isCall && parsers.detachedRuleset()) || parsers.expression(); + + if (!value) { + if (isCall) { + error("could not understand value for named argument"); + } else { + restore(); + returner.args = []; + return returner; + } + } + nameLoop = (name = val.name); + } else if (!isCall && $re(/^\.{3}/)) { + returner.variadic = true; + if ($char(";") && !isSemiColonSeperated) { + isSemiColonSeperated = true; + } + (isSemiColonSeperated ? argsSemiColon : argsComma) + .push({name: arg.name, variadic: true}); + break; + } else if (!isCall) { + name = nameLoop = val.name; + value = null; + } + } + + if (value) { + expressions.push(value); + } + + argsComma.push({name: nameLoop, value: value}); + + if ($char(',')) { + continue; + } + + if ($char(';') || isSemiColonSeperated) { + + if (expressionContainsNamed) { + error("Cannot mix ; and , as delimiter types"); + } + + isSemiColonSeperated = true; + + if (expressions.length > 1) { + value = new (tree.Value)(expressions); + } + argsSemiColon.push({name: name, value: value}); + + name = null; + expressions = []; + expressionContainsNamed = false; + } + } + + forget(); + returner.args = isSemiColonSeperated ? argsSemiColon : argsComma; + return returner; + }, + // + // A Mixin definition, with a list of parameters + // + // .rounded (@radius: 2px, @color) { + // ... + // } + // + // Until we have a finer grained state-machine, we have to + // do a look-ahead, to make sure we don't have a mixin call. + // See the `rule` function for more information. + // + // We start by matching `.rounded (`, and then proceed on to + // the argument list, which has optional default values. + // We store the parameters in `params`, with a `value` key, + // if there is a value, such as in the case of `@radius`. + // + // Once we've got our params list, and a closing `)`, we parse + // the `{...}` block. + // + definition: function () { + var name, params = [], match, ruleset, cond, variadic = false; + if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') || + peek(/^[^{]*\}/)) { + return; + } + + save(); + + match = $re(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/); + if (match) { + name = match[1]; + + var argInfo = this.args(false); + params = argInfo.args; + variadic = argInfo.variadic; + + // .mixincall("@{a}"); + // looks a bit like a mixin definition.. + // also + // .mixincall(@a: {rule: set;}); + // so we have to be nice and restore + if (!$char(')')) { + furthest = i; + restore(); + return; + } + + parsers.comments(); + + if ($re(/^when/)) { // Guard + cond = expect(parsers.conditions, 'expected condition'); + } + + ruleset = parsers.block(); + + if (ruleset) { + forget(); + return new (tree.mixin.Definition)(name, params, ruleset, cond, variadic); + } else { + restore(); + } + } else { + forget(); + } + } + }, + + // + // Entities are the smallest recognized token, + // and can be found inside a rule's value. + // + entity: function () { + var entities = this.entities; + + return entities.literal() || entities.variable() || entities.url() || + entities.call() || entities.keyword() || entities.javascript() || + this.comment(); + }, + + // + // A Rule terminator. Note that we use `peek()` to check for '}', + // because the `block` rule will be expecting it, but we still need to make sure + // it's there, if ';' was ommitted. + // + end: function () { + return $char(';') || peekChar('}'); + }, + + // + // IE's alpha function + // + // alpha(opacity=88) + // + alpha: function () { + var value; + + if (!$re(/^\(opacity=/i)) { + return; + } + value = $re(/^\d+/) || this.entities.variable(); + if (value) { + expectChar(')'); + return new (tree.Alpha)(value); + } + }, + + // + // A Selector Element + // + // div + // + h1 + // #socks + // input[type="text"] + // + // Elements are the building blocks for Selectors, + // they are made out of a `Combinator` (see combinator rule), + // and an element name, such as a tag a class, or `*`. + // + element: function () { + var e, c, v, index = i; + + c = this.combinator(); + + e = $re(/^(?:\d+\.\d+|\d+)%/) || $re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) || + $char('*') || $char('&') || this.attribute() || $re(/^\([^()@]+\)/) || $re(/^[\.#](?=@)/) || + this.entities.variableCurly(); + + if (!e) { + save(); + if ($char('(')) { + if ((v = this.selector()) && $char(')')) { + e = new (tree.Paren)(v); + forget(); + } else { + restore(); + } + } else { + forget(); + } + } + + if (e) { + return new (tree.Element)(c, e, index, env.currentFileInfo); + } + }, + + // + // Combinators combine elements together, in a Selector. + // + // Because our parser isn't white-space sensitive, special care + // has to be taken, when parsing the descendant combinator, ` `, + // as it's an empty space. We have to check the previous character + // in the input, to see if it's a ` ` character. More info on how + // we deal with this in *combinator.js*. + // + combinator: function () { + var c = input.charAt(i); + + if (c === '/') { + save(); + var slashedCombinator = $re(/^\/[a-z]+\//i); + if (slashedCombinator) { + forget(); + return new (tree.Combinator)(slashedCombinator); + } + restore(); + } + + if (c === '>' || c === '+' || c === '~' || c === '|' || c === '^') { + i++; + if (c === '^' && input.charAt(i) === '^') { + c = '^^'; + i++; + } + while (isWhitespace(input, i)) { + i++; + } + return new (tree.Combinator)(c); + } else if (isWhitespace(input, i - 1)) { + return new (tree.Combinator)(" "); + } else { + return new (tree.Combinator)(null); + } + }, + // + // A CSS selector (see selector below) + // with less extensions e.g. the ability to extend and guard + // + lessSelector: function () { + return this.selector(true); + }, + // + // A CSS Selector + // + // .class > div + h1 + // li a:hover + // + // Selectors are made out of one or more Elements, see above. + // + selector: function (isLess) { + var index = i, $re = _$re, elements, extendList, c, e, extend, when, condition; + + while ((isLess && (extend = this.extend())) || (isLess && (when = $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) { + error("Extend can only be used at the end of selector"); + } + c = input.charAt(i); + if (elements) { + elements.push(e); + } else { + elements = [e]; + } + e = null; + } + if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { + break; + } + } + + if (elements) { + return new (tree.Selector)(elements, extendList, condition, index, env.currentFileInfo); + } + if (extendList) { + error("Extend must be used to extend a selector, it cannot be used on its own"); + } + }, + attribute: function () { + if (!$char('[')) { + return; + } + + var entities = this.entities, + key, val, op; + + if (!(key = entities.variableCurly())) { + key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/); + } + + op = $re(/^[|~*$^]?=/); + if (op) { + val = entities.quoted() || $re(/^[0-9]+%/) || $re(/^[\w-]+/) || entities.variableCurly(); + } + + expectChar(']'); + + return new (tree.Attribute)(key, op, val); + }, + + // + // The `block` rule is used by `ruleset` and `mixin.definition`. + // It's a wrapper around the `primary` rule, with added `{}`. + // + block: function () { + var content; + if ($char('{') && (content = this.primary()) && $char('}')) { + return content; + } + }, + + blockRuleset: function () { + var block = this.block(); + + if (block) { + block = new tree.Ruleset(null, block); + } + return block; + }, + + detachedRuleset: function () { + var blockRuleset = this.blockRuleset(); + if (blockRuleset) { + return new tree.DetachedRuleset(blockRuleset); + } + }, + + // + // div, .class, body > p {...} + // + ruleset: function () { + var selectors, s, rules, debugInfo; + + save(); + + if (env.dumpLineNumbers) { + debugInfo = getDebugInfo(i, input, env); + } + + while (true) { + s = this.lessSelector(); + if (!s) { + break; + } + if (selectors) { + selectors.push(s); + } else { + selectors = [s]; + } + this.comments(); + if (s.condition && selectors.length > 1) { + error("Guards are only currently allowed on a single selector."); + } + if (!$char(',')) { + break; + } + if (s.condition) { + error("Guards are only currently allowed on a single selector."); + } + this.comments(); + } + + if (selectors && (rules = this.block())) { + forget(); + var ruleset = new (tree.Ruleset)(selectors, rules, env.strictImports); + if (env.dumpLineNumbers) { + ruleset.debugInfo = debugInfo; + } + return ruleset; + } else { + // Backtrack + furthest = i; + restore(); + } + }, + rule: function (tryAnonymous) { + var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable; + + if (c === '.' || c === '#' || c === '&') { + return; + } + + save(); + + name = this.variable() || this.ruleProperty(); + if (name) { + isVariable = typeof name === "string"; + + if (isVariable) { + value = this.detachedRuleset(); + } + + this.comments(); + if (!value) { + // prefer to try to parse first if its a variable or we are compressing + // but always fallback on the other one + value = !tryAnonymous && (env.compress || isVariable) ? + (this.value() || this.anonymousValue()) : + (this.anonymousValue() || this.value()); + + important = this.important(); + + // a name returned by this.ruleProperty() is always an array of the form: + // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"] + // where each item is a tree.Keyword or tree.Variable + merge = !isVariable && name.pop().value; + } + + if (value && this.end()) { + forget(); + return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo); + } else { + furthest = i; + restore(); + if (value && !tryAnonymous) { + return this.rule(true); + } + } + } else { + forget(); + } + }, + anonymousValue: function () { + var match; + match = /^([^@+\/'"*`(;{}-]*);/.exec(current); + if (match) { + i += match[0].length - 1; + return new (tree.Anonymous)(match[1]); + } + }, + + // + // An @import directive + // + // @import "lib"; + // + // Depending on our environment, importing is done differently: + // In the browser, it's an XHR request, in Node, it would be a + // file-system operation. The function used for importing is + // stored in `import`, which we pass to the Import constructor. + // + "import": function () { + var path, features, index = i; + + var dir = $re(/^@import?\s+/); + + if (dir) { + var options = (dir ? this.importOptions() : null) || {}; + + if ((path = this.entities.quoted() || this.entities.url())) { + features = this.mediaFeatures(); + + if (!$(';')) { + i = index; + error("missing semi-colon or unrecognised media features on import"); + } + features = features && new (tree.Value)(features); + return new (tree.Import)(path, features, options, index, env.currentFileInfo); + } + else { + i = index; + error("malformed import statement"); + } + } + }, + + importOptions: function () { + var o, options = {}, optionName, value; + + // list of options, surrounded by parens + if (!$char('(')) { + return null; + } + do { + o = this.importOption(); + if (o) { + optionName = o; + value = true; + switch (optionName) { + case "css": + optionName = "less"; + value = false; + break; + case "once": + optionName = "multiple"; + value = false; + break; + } + options[optionName] = value; + if (!$char(',')) { + break; + } + } + } while (o); + expectChar(')'); + return options; + }, + + importOption: function () { + var opt = $re(/^(less|css|multiple|once|inline|reference)/); + if (opt) { + return opt[1]; + } + }, + + mediaFeature: function () { + var entities = this.entities, nodes = [], e, p; + do { + e = entities.keyword() || entities.variable(); + if (e) { + nodes.push(e); + } else if ($char('(')) { + p = this.property(); + e = this.value(); + if ($char(')')) { + if (p && e) { + nodes.push(new (tree.Paren)(new (tree.Rule)(p, e, null, null, i, env.currentFileInfo, true))); + } else if (e) { + nodes.push(new (tree.Paren)(e)); + } else { + return null; + } + } else { + return null; + } + } + } while (e); + + if (nodes.length > 0) { + return new (tree.Expression)(nodes); + } + }, + + mediaFeatures: function () { + var entities = this.entities, features = [], e; + do { + e = this.mediaFeature(); + if (e) { + features.push(e); + if (!$char(',')) { + break; + } + } else { + e = entities.variable(); + if (e) { + features.push(e); + if (!$char(',')) { + break; + } + } + } + } while (e); + + return features.length > 0 ? features : null; + }, + + media: function () { + var features, rules, media, debugInfo; + + if (env.dumpLineNumbers) { + debugInfo = getDebugInfo(i, input, env); + } + + if ($re(/^@media/)) { + features = this.mediaFeatures(); + + rules = this.block(); + if (rules) { + media = new (tree.Media)(rules, features, i, env.currentFileInfo); + if (env.dumpLineNumbers) { + media.debugInfo = debugInfo; + } + return media; + } + } + }, + + // + // A CSS Directive + // + // @charset "utf-8"; + // + directive: function () { + var index = i, name, value, rules, nonVendorSpecificName, + hasIdentifier, hasExpression, hasUnknown, hasBlock = true; + + if (input.charAt(i) !== '@') { + return; + } + + value = this['import']() || this.media(); + if (value) { + return value; + } + + save(); + + name = $re(/^@[a-z-]+/); + + if (!name) { + return; + } + + nonVendorSpecificName = name; + if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) { + nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1); + } + + switch (nonVendorSpecificName) { + /* + case "@font-face": + case "@viewport": + case "@top-left": + case "@top-left-corner": + case "@top-center": + case "@top-right": + case "@top-right-corner": + case "@bottom-left": + case "@bottom-left-corner": + case "@bottom-center": + case "@bottom-right": + case "@bottom-right-corner": + case "@left-top": + case "@left-middle": + case "@left-bottom": + case "@right-top": + case "@right-middle": + case "@right-bottom": + hasBlock = true; + break; + */ + case "@charset": + hasIdentifier = true; + hasBlock = false; + break; + case "@namespace": + hasExpression = true; + hasBlock = false; + break; + case "@keyframes": + hasIdentifier = true; + break; + case "@host": + case "@page": + case "@document": + case "@supports": + hasUnknown = true; + break; + } + + this.comments(); + + if (hasIdentifier) { + value = this.entity(); + if (!value) { + error("expected " + name + " identifier"); + } + } else if (hasExpression) { + value = this.expression(); + if (!value) { + error("expected " + name + " expression"); + } + } else if (hasUnknown) { + value = ($re(/^[^{;]+/) || '').trim(); + if (value) { + value = new (tree.Anonymous)(value); + } + } + + this.comments(); + + if (hasBlock) { + rules = this.blockRuleset(); + } + + if (rules || (!hasBlock && value && $char(';'))) { + forget(); + return new (tree.Directive)(name, value, rules, index, env.currentFileInfo, + env.dumpLineNumbers ? getDebugInfo(index, input, env) : null); + } + + restore(); + }, + + // + // A Value is a comma-delimited list of Expressions + // + // font-family: Baskerville, Georgia, serif; + // + // In a Rule, a Value represents everything after the `:`, + // and before the `;`. + // + value: function () { + var e, expressions = []; + + do { + e = this.expression(); + if (e) { + expressions.push(e); + if (!$char(',')) { + break; + } + } + } while (e); + + if (expressions.length > 0) { + return new (tree.Value)(expressions); + } + }, + important: function () { + if (input.charAt(i) === '!') { + return $re(/^! *important/); + } + }, + sub: function () { + var a, e; + + if ($char('(')) { + a = this.addition(); + if (a) { + e = new (tree.Expression)([a]); + expectChar(')'); + e.parens = true; + return e; + } + } + }, + multiplication: function () { + var m, a, op, operation, isSpaced; + m = this.operand(); + if (m) { + isSpaced = isWhitespace(input, i - 1); + while (true) { + if (peek(/^\/[*\/]/)) { + break; + } + + save(); + + op = $char('/') || $char('*'); + + if (!op) { + forget(); + break; + } + + a = this.operand(); + + if (!a) { + restore(); + break; + } + forget(); + + m.parensInOp = true; + a.parensInOp = true; + operation = new (tree.Operation)(op, [operation || m, a], isSpaced); + isSpaced = isWhitespace(input, i - 1); + } + return operation || m; + } + }, + addition: function () { + var m, a, op, operation, isSpaced; + m = this.multiplication(); + if (m) { + isSpaced = isWhitespace(input, i - 1); + while (true) { + op = $re(/^[-+]\s+/) || (!isSpaced && ($char('+') || $char('-'))); + if (!op) { + break; + } + a = this.multiplication(); + if (!a) { + break; + } + + m.parensInOp = true; + a.parensInOp = true; + operation = new (tree.Operation)(op, [operation || m, a], isSpaced); + isSpaced = isWhitespace(input, i - 1); + } + return operation || m; + } + }, + conditions: function () { + var a, b, index = i, condition; + + a = this.condition(); + if (a) { + while (true) { + if (!peek(/^,\s*(not\s*)?\(/) || !$char(',')) { + break; + } + b = this.condition(); + if (!b) { + break; + } + condition = new (tree.Condition)('or', condition || a, b, index); + } + return condition || a; + } + }, + condition: function () { + var entities = this.entities, index = i, negate = false, + a, b, c, op; + + if ($re(/^not/)) { + negate = true; + } + expectChar('('); + a = this.addition() || entities.keyword() || entities.quoted(); + if (a) { + op = $re(/^(?:>=|<=|=<|[<=>])/); + if (op) { + b = this.addition() || entities.keyword() || entities.quoted(); + if (b) { + c = new (tree.Condition)(op, a, b, index, negate); + } else { + error('expected expression'); + } + } else { + c = new (tree.Condition)('=', a, new (tree.Keyword)('true'), index, negate); + } + expectChar(')'); + return $re(/^and/) ? new (tree.Condition)('and', c, this.condition()) : c; + } + }, + + // + // An operand is anything that can be part of an operation, + // such as a Color, or a Variable + // + operand: function () { + var entities = this.entities, + p = input.charAt(i + 1), negate; + + if (input.charAt(i) === '-' && (p === '@' || p === '(')) { + negate = $char('-'); + } + var o = this.sub() || entities.dimension() || + entities.color() || entities.variable() || + entities.call(); + + if (negate) { + o.parensInOp = true; + o = new (tree.Negative)(o); + } + + return o; + }, + + // + // Expressions either represent mathematical operations, + // or white-space delimited Entities. + // + // 1px solid black + // @var * 2 + // + expression: function () { + var entities = [], e, delim; + + do { + e = this.addition() || this.entity(); + if (e) { + entities.push(e); + // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here + if (!peek(/^\/[\/*]/)) { + delim = $char('/'); + if (delim) { + entities.push(new (tree.Anonymous)(delim)); + } + } + } + } while (e); + if (entities.length > 0) { + return new (tree.Expression)(entities); + } + }, + property: function () { + var name = $re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/); + if (name) { + return name[1]; + } + }, + ruleProperty: function () { + var c = current, name = [], index = [], length = 0, s, k; + + function match(re) { + var a = re.exec(c); + if (a) { + index.push(i + length); + length += a[0].length; + c = c.slice(a[1].length); + return name.push(a[1]); + } + } + + function cutOutBlockComments() { + //match block comments + var a = /^\s*\/\*(?:[^*]|\*+[^\/*])*\*+\//.exec(c); + if (a) { + length += a[0].length; + c = c.slice(a[0].length); + return true; + } + return false; + } + + match(/^(\*?)/); + while (match(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/)); // ! + while (cutOutBlockComments()); + if ((name.length > 1) && match(/^\s*((?:\+_|\+)?)\s*:/)) { + // at last, we have the complete match now. move forward, + // convert name particles to tree objects and return: + skipWhitespace(length); + if (name[0] === '') { + name.shift(); + index.shift(); + } + for (k = 0; k < name.length; k++) { + s = name[k]; + name[k] = (s.charAt(0) !== '@') + ? new (tree.Keyword)(s) + : new (tree.Variable)('@' + s.slice(2, -1), + index[k], env.currentFileInfo); + } + return name; + } + } + } + }; + return parser; +}; +less.Parser.serializeVars = function (vars) { + var s = ''; + + for (var name in vars) { + if (Object.hasOwnProperty.call(vars, name)) { + var value = vars[name]; + s += ((name[0] === '@') ? '' : '@') + name + ': ' + value + + ((('' + value).slice(-1) === ';') ? '' : ';'); + } + } + + return s; +}; + +(function (tree) { + + tree.functions = { + rgb: function (r, g, b) { + return this.rgba(r, g, b, 1.0); + }, + rgba: function (r, g, b, a) { + var rgb = [r, g, b].map(function (c) { + return scaled(c, 255); + }); + a = number(a); + return new (tree.Color)(rgb, a); + }, + hsl: function (h, s, l) { + return this.hsla(h, s, l, 1.0); + }, + 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; + } + } + + h = (number(h) % 360) / 360; + s = clamp(number(s)); + l = clamp(number(l)); + a = clamp(number(a)); + + var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + var m1 = l * 2 - m2; + + return this.rgba(hue(h + 1 / 3) * 255, + hue(h) * 255, + hue(h - 1 / 3) * 255, + a); + }, + + hsv: function (h, s, v) { + return this.hsva(h, s, v, 1.0); + }, + + hsva: function (h, s, v, a) { + h = ((number(h) % 360) / 360) * 360; + s = number(s); + v = number(v); + a = number(a); + + var i, f; + i = Math.floor((h / 60) % 6); + f = (h / 60) - i; + + var vs = [v, + v * (1 - s), + v * (1 - f * s), + v * (1 - (1 - f) * s)]; + var perm = [[0, 3, 1], + [2, 0, 1], + [1, 0, 3], + [1, 2, 0], + [3, 1, 0], + [0, 1, 2]]; + + return this.rgba(vs[perm[i][0]] * 255, + vs[perm[i][1]] * 255, + vs[perm[i][2]] * 255, + a); + }, + + hue: function (color) { + return new (tree.Dimension)(color.toHSL().h); + }, + saturation: function (color) { + return new (tree.Dimension)(color.toHSL().s * 100, '%'); + }, + lightness: function (color) { + return new (tree.Dimension)(color.toHSL().l * 100, '%'); + }, + hsvhue: function (color) { + return new (tree.Dimension)(color.toHSV().h); + }, + hsvsaturation: function (color) { + return new (tree.Dimension)(color.toHSV().s * 100, '%'); + }, + hsvvalue: function (color) { + return new (tree.Dimension)(color.toHSV().v * 100, '%'); + }, + red: function (color) { + return new (tree.Dimension)(color.rgb[0]); + }, + green: function (color) { + return new (tree.Dimension)(color.rgb[1]); + }, + blue: function (color) { + return new (tree.Dimension)(color.rgb[2]); + }, + alpha: function (color) { + return new (tree.Dimension)(color.toHSL().a); + }, + luma: function (color) { + return new (tree.Dimension)(color.luma() * color.alpha * 100, '%'); + }, + luminance: function (color) { + var luminance = + (0.2126 * color.rgb[0] / 255) + + (0.7152 * color.rgb[1] / 255) + + (0.0722 * color.rgb[2] / 255); + + return new (tree.Dimension)(luminance * color.alpha * 100, '%'); + }, + saturate: function (color, amount) { + // filter: saturate(3.2); + // should be kept as is, so check for color + if (!color.rgb) { + return null; + } + var hsl = color.toHSL(); + + hsl.s += amount.value / 100; + hsl.s = clamp(hsl.s); + return hsla(hsl); + }, + desaturate: function (color, amount) { + var hsl = color.toHSL(); + + hsl.s -= amount.value / 100; + hsl.s = clamp(hsl.s); + return hsla(hsl); + }, + lighten: function (color, amount) { + var hsl = color.toHSL(); + + hsl.l += amount.value / 100; + hsl.l = clamp(hsl.l); + return hsla(hsl); + }, + darken: function (color, amount) { + var hsl = color.toHSL(); + + hsl.l -= amount.value / 100; + hsl.l = clamp(hsl.l); + return hsla(hsl); + }, + fadein: function (color, amount) { + var hsl = color.toHSL(); + + hsl.a += amount.value / 100; + hsl.a = clamp(hsl.a); + return hsla(hsl); + }, + fadeout: function (color, amount) { + var hsl = color.toHSL(); + + hsl.a -= amount.value / 100; + hsl.a = clamp(hsl.a); + return hsla(hsl); + }, + fade: function (color, amount) { + var hsl = color.toHSL(); + + hsl.a = amount.value / 100; + hsl.a = clamp(hsl.a); + return hsla(hsl); + }, + spin: function (color, amount) { + var hsl = color.toHSL(); + var hue = (hsl.h + amount.value) % 360; + + hsl.h = hue < 0 ? 360 + hue : hue; + + return hsla(hsl); + }, + // + // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein + // http://sass-lang.com + // + mix: function (color1, color2, weight) { + if (!weight) { + weight = new (tree.Dimension)(50); + } + var p = weight.value / 100.0; + var w = p * 2 - 1; + var a = color1.toHSL().a - color2.toHSL().a; + + var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2, + color1.rgb[1] * w1 + color2.rgb[1] * w2, + color1.rgb[2] * w1 + color2.rgb[2] * w2]; + + var alpha = color1.alpha * p + color2.alpha * (1 - p); + + return new (tree.Color)(rgb, alpha); + }, + greyscale: function (color) { + return this.desaturate(color, new (tree.Dimension)(100)); + }, + contrast: function (color, dark, light, threshold) { + // filter: contrast(3.2); + // should be kept as is, so check for color + if (!color.rgb) { + return null; + } + if (typeof light === 'undefined') { + light = this.rgba(255, 255, 255, 1.0); + } + if (typeof dark === 'undefined') { + dark = this.rgba(0, 0, 0, 1.0); + } + //Figure out which is actually light and dark! + if (dark.luma() > light.luma()) { + var t = light; + light = dark; + dark = t; + } + if (typeof threshold === 'undefined') { + threshold = 0.43; + } else { + threshold = number(threshold); + } + if (color.luma() < threshold) { + return light; + } else { + return dark; + } + }, + e: function (str) { + return new (tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str.value); + }, + escape: function (str) { + return new (tree.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; + + result = result.replace(new RegExp(pattern.value, flags ? flags.value : ''), replacement.value); + return new (tree.Quoted)(string.quote || '', result, string.escaped); + }, + '%': function (string /* arg, arg, ...*/) { + var args = Array.prototype.slice.call(arguments, 1), + result = string.value; + + for (var i = 0; i < args.length; i++) { + /*jshint loopfunc:true */ + result = result.replace(/%[sda]/i, function (token) { + var value = token.match(/s/i) ? args[i].value : args[i].toCSS(); + return token.match(/[A-Z]$/) ? encodeURIComponent(value) : value; + }); + } + result = result.replace(/%%/g, '%'); + return new (tree.Quoted)(string.quote || '', result, string.escaped); + }, + unit: function (val, unit) { + if (!(val instanceof tree.Dimension)) { + throw { + type: "Argument", + message: "the first argument to unit must be a number" + (val instanceof tree.Operation ? ". Have you forgotten parenthesis?" : "") + }; + } + if (unit) { + if (unit instanceof tree.Keyword) { + unit = unit.value; + } else { + unit = unit.toCSS(); + } + } else { + unit = ""; + } + return new (tree.Dimension)(val.value, unit); + }, + convert: function (val, unit) { + return val.convertTo(unit.value); + }, + round: function (n, f) { + var fraction = typeof(f) === "undefined" ? 0 : f.value; + return _math(function (num) { + return num.toFixed(fraction); + }, null, n); + }, + pi: function () { + return new (tree.Dimension)(Math.PI); + }, + mod: function (a, b) { + return new (tree.Dimension)(a.value % b.value, a.unit); + }, + pow: function (x, y) { + if (typeof x === "number" && typeof y === "number") { + x = new (tree.Dimension)(x); + y = new (tree.Dimension)(y); + } else if (!(x instanceof tree.Dimension) || !(y instanceof tree.Dimension)) { + throw {type: "Argument", message: "arguments must be numbers"}; + } + + return new (tree.Dimension)(Math.pow(x.value, y.value), x.unit); + }, + _minmax: function (isMin, args) { + args = Array.prototype.slice.call(args); + switch (args.length) { + case 0: + throw {type: "Argument", message: "one or more arguments required"}; + } + var i, j, current, currentUnified, referenceUnified, unit, unitStatic, unitClone, + order = [], // elems only contains original argument values. + values = {}; // key is the unit.toString() for unified tree.Dimension values, + // value is the index into the order array. + for (i = 0; i < args.length; i++) { + current = args[i]; + if (!(current instanceof tree.Dimension)) { + if (Array.isArray(args[i].value)) { + Array.prototype.push.apply(args, Array.prototype.slice.call(args[i].value)); + } + continue; + } + currentUnified = current.unit.toString() === "" && unitClone !== undefined ? new (tree.Dimension)(current.value, unitClone).unify() : current.unify(); + unit = currentUnified.unit.toString() === "" && unitStatic !== undefined ? unitStatic : currentUnified.unit.toString(); + unitStatic = unit !== "" && unitStatic === undefined || unit !== "" && order[0].unify().unit.toString() === "" ? unit : unitStatic; + 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) { + throw{type: "Argument", message: "incompatible types"}; + } + values[unit] = order.length; + order.push(current); + continue; + } + referenceUnified = order[j].unit.toString() === "" && unitClone !== undefined ? new (tree.Dimension)(order[j].value, unitClone).unify() : order[j].unify(); + if (isMin && currentUnified.value < referenceUnified.value || + !isMin && currentUnified.value > referenceUnified.value) { + order[j] = current; + } + } + if (order.length == 1) { + return order[0]; + } + args = order.map(function (a) { + return a.toCSS(this.env); + }).join(this.env.compress ? "," : ", "); + return new (tree.Anonymous)((isMin ? "min" : "max") + "(" + args + ")"); + }, + min: function () { + return this._minmax(true, arguments); + }, + max: function () { + return this._minmax(false, arguments); + }, + "get-unit": function (n) { + return new (tree.Anonymous)(n.unit); + }, + argb: function (color) { + return new (tree.Anonymous)(color.toARGB()); + }, + percentage: function (n) { + return new (tree.Dimension)(n.value * 100, '%'); + }, + color: function (n) { + if (n instanceof tree.Quoted) { + var colorCandidate = n.value, + returnColor; + returnColor = tree.Color.fromKeyword(colorCandidate); + if (returnColor) { + return returnColor; + } + if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/.test(colorCandidate)) { + return new (tree.Color)(colorCandidate.slice(1)); + } + throw {type: "Argument", message: "argument must be a color keyword or 3/6 digit hex e.g. #FFF"}; + } else { + throw {type: "Argument", message: "argument must be a string"}; + } + }, + iscolor: function (n) { + return this._isa(n, tree.Color); + }, + isnumber: function (n) { + return this._isa(n, tree.Dimension); + }, + isstring: function (n) { + return this._isa(n, tree.Quoted); + }, + iskeyword: function (n) { + return this._isa(n, tree.Keyword); + }, + isurl: function (n) { + return this._isa(n, tree.URL); + }, + ispixel: function (n) { + return this.isunit(n, 'px'); + }, + ispercentage: function (n) { + return this.isunit(n, '%'); + }, + isem: function (n) { + return this.isunit(n, 'em'); + }, + isunit: function (n, unit) { + return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False; + }, + _isa: function (n, Type) { + return (n instanceof Type) ? tree.True : tree.False; + }, + tint: function (color, amount) { + return this.mix(this.rgb(255, 255, 255), color, amount); + }, + shade: function (color, amount) { + return this.mix(this.rgb(0, 0, 0), color, amount); + }, + extract: function (values, index) { + index = index.value - 1; // (1-based index) + // handle non-array values as an array of length 1 + // return 'undefined' if index is invalid + return Array.isArray(values.value) + ? values.value[index] : Array(values)[index]; + }, + length: function (values) { + var n = Array.isArray(values.value) ? values.value.length : 1; + return new tree.Dimension(n); + }, + + "data-uri": function (mimetypeNode, filePathNode) { + + if (typeof window !== 'undefined') { + return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env); + } + + var mimetype = mimetypeNode.value; + var filePath = (filePathNode && filePathNode.value); + + var fs = require('./fs'), + path = require('path'), + useBase64 = false; + + if (arguments.length < 2) { + filePath = mimetype; + } + + var fragmentStart = filePath.indexOf('#'); + var fragment = ''; + if (fragmentStart !== -1) { + fragment = filePath.slice(fragmentStart); + filePath = filePath.slice(0, fragmentStart); + } + + if (this.env.isPathRelative(filePath)) { + if (this.currentFileInfo.relativeUrls) { + filePath = path.join(this.currentFileInfo.currentDirectory, filePath); + } else { + filePath = path.join(this.currentFileInfo.entryPath, filePath); + } + } + + // detect the mimetype if not given + if (arguments.length < 2) { + var mime; + try { + mime = require('mime'); + } catch (ex) { + mime = tree._mime; + } + + mimetype = mime.lookup(filePath); + + // use base 64 unless it's an ASCII or UTF-8 format + var charset = mime.charsets.lookup(mimetype); + useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0; + if (useBase64) { + mimetype += ';base64'; + } + } + else { + useBase64 = /;base64$/.test(mimetype); + } + + var buf = fs.readFileSync(filePath); + + // IE8 cannot handle a data-uri larger than 32KB. 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) { + + if (this.env.ieCompat !== false) { + if (!this.env.silent) { + console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!", filePath, fileSizeInKB, DATA_URI_MAX_KB); + } + + return new tree.URL(filePathNode || mimetypeNode, this.currentFileInfo).eval(this.env); + } + } + + buf = useBase64 ? buf.toString('base64') + : encodeURIComponent(buf); + + var uri = "\"data:" + mimetype + ',' + buf + fragment + "\""; + return new (tree.URL)(new (tree.Anonymous)(uri)); + }, + + "svg-gradient": function (direction) { + + function throwArgumentDescriptor() { + throw { + type: "Argument", + message: "svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]" + }; + } + + if (arguments.length < 3) { + throwArgumentDescriptor(); + } + var stops = Array.prototype.slice.call(arguments, 1), + gradientDirectionSvg, + gradientType = "linear", + rectangleDimension = 'x="0" y="0" width="1" height="1"', + useBase64 = true, + renderEnv = {compress: false}, + returner, + directionValue = direction.toCSS(renderEnv), + i, color, position, positionValue, alpha; + + switch (directionValue) { + case "to bottom": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"'; + break; + case "to right": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"'; + break; + case "to bottom right": + gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"'; + break; + case "to top right": + gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"'; + break; + case "ellipse": + case "ellipse at center": + gradientType = "radial"; + gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"'; + 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'" + }; + } + returner = '' + + '' + + '<' + gradientType + 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' + gradientDirectionSvg + '>'; + + for (i = 0; i < stops.length; i += 1) { + if (stops[i].value) { + color = stops[i].value[0]; + position = stops[i].value[1]; + } else { + color = stops[i]; + position = undefined; + } + + if (!(color instanceof tree.Color) || (!((i === 0 || i + 1 === stops.length) && position === undefined) && !(position instanceof tree.Dimension))) { + throwArgumentDescriptor(); + } + positionValue = position ? position.toCSS(renderEnv) : i === 0 ? "0%" : "100%"; + alpha = color.alpha; + returner += ''; + } + returner += '' + + ''; + + if (useBase64) { + try { + returner = require('./encoder').encodeBase64(returner); // TODO browser implementation + } catch (e) { + useBase64 = false; + } + } + + returner = "'data:image/svg+xml" + (useBase64 ? ";base64" : "") + "," + returner + "'"; + return new (tree.URL)(new (tree.Anonymous)(returner)); + } + }; + +// these static methods are used as a fallback when the optional 'mime' dependency is missing + tree._mime = { + // this map is intentionally incomplete + // if you want more, install 'mime' dep + _types: { + '.htm': 'text/html', + '.html': 'text/html', + '.gif': 'image/gif', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png' + }, + lookup: function (filepath) { + var ext = require('path').extname(filepath), + type = tree._mime._types[ext]; + if (type === undefined) { + throw new Error('Optional dependency "mime" is required for ' + ext); + } + return type; + }, + charsets: { + lookup: function (type) { + // assumes all text types are UTF-8 + return type && (/^text\//).test(type) ? 'UTF-8' : ''; + } + } + }; + +// Math + + var mathFunctions = { + // name, unit + ceil: null, + floor: null, + sqrt: null, + abs: null, + tan: "", + sin: "", + cos: "", + atan: "rad", + asin: "rad", + acos: "rad" + }; + + function _math(fn, unit, n) { + if (!(n instanceof tree.Dimension)) { + throw {type: "Argument", message: "argument must be a number"}; + } + if (unit == null) { + unit = n.unit; + } else { + n = n.unify(); + } + return new (tree.Dimension)(fn(parseFloat(n.value)), unit); + } + +// ~ End of Math + +// Color Blending +// ref: http://www.w3.org/TR/compositing-1 + + function colorBlend(mode, color1, color2) { + var ab = color1.alpha, cb, // backdrop + as = color2.alpha, cs, // source + ar, cr, r = []; // result + + ar = as + ab * (1 - as); + for (var i = 0; i < 3; i++) { + cb = color1.rgb[i] / 255; + cs = color2.rgb[i] / 255; + cr = mode(cb, cs); + if (ar) { + cr = (as * cs + ab * (cb + - as * (cb + cs - cr))) / ar; + } + r[i] = cr * 255; + } + + return new (tree.Color)(r, ar); + } + + var colorBlendMode = { + multiply: function (cb, cs) { + return cb * cs; + }, + screen: function (cb, cs) { + return cb + cs - cb * cs; + }, + overlay: function (cb, cs) { + cb *= 2; + return (cb <= 1) + ? colorBlendMode.multiply(cb, cs) + : colorBlendMode.screen(cb - 1, cs); + }, + softlight: function (cb, cs) { + var d = 1, e = cb; + if (cs > 0.5) { + e = 1; + d = (cb > 0.25) ? Math.sqrt(cb) + : ((16 * cb - 12) * cb + 4) * cb; + } + return cb - (1 - 2 * cs) * e * (d - cb); + }, + hardlight: function (cb, cs) { + return colorBlendMode.overlay(cs, cb); + }, + difference: function (cb, cs) { + return Math.abs(cb - cs); + }, + exclusion: function (cb, cs) { + return cb + cs - 2 * cb * cs; + }, + + // non-w3c functions: + average: function (cb, cs) { + return (cb + cs) / 2; + }, + negation: function (cb, cs) { + return 1 - Math.abs(cb + cs - 1); + } + }; + +// ~ End of Color Blending + + tree.defaultFunc = { + eval: function () { + var v = this.value_, e = this.error_; + if (e) { + throw e; + } + if (v != null) { + return v ? tree.True : tree.False; + } + }, + value: function (v) { + this.value_ = v; + }, + error: function (e) { + this.error_ = e; + }, + reset: function () { + this.value_ = this.error_ = null; + } + }; + + function initFunctions() { + var f, tf = tree.functions; + + // math + for (f in mathFunctions) { + if (mathFunctions.hasOwnProperty(f)) { + tf[f] = _math.bind(null, Math[f], mathFunctions[f]); + } + } + + // color blending + for (f in colorBlendMode) { + if (colorBlendMode.hasOwnProperty(f)) { + tf[f] = colorBlend.bind(null, colorBlendMode[f]); + } + } + + // default + f = tree.defaultFunc; + tf["default"] = f.eval.bind(f); + + } + + initFunctions(); + + function hsla(color) { + return tree.functions.hsla(color.h, color.s, color.l, color.a); + } + + function scaled(n, size) { + if (n instanceof tree.Dimension && n.unit.is('%')) { + return parseFloat(n.value * size / 100); + } else { + return number(n); + } + } + + function number(n) { + if (n instanceof tree.Dimension) { + return parseFloat(n.unit.is('%') ? n.value / 100 : n.value); + } else if (typeof(n) === 'number') { + return n; + } else { + throw { + error: "RuntimeError", + message: "color functions take numbers as parameters" + }; + } + } + + function clamp(val) { + return Math.min(1, Math.max(0, val)); + } + + tree.fround = function (env, value) { + var p = env && env.numPrecision; + //add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded... + return (p == null) ? value : Number((value + 2e-16).toFixed(p)); + }; + + tree.functionCall = function (env, currentFileInfo) { + this.env = env; + this.currentFileInfo = currentFileInfo; + }; + + tree.functionCall.prototype = tree.functions; + +})(require('./tree')); + +(function (tree) { + tree.colors = { + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgrey': '#a9a9a9', + 'darkgreen': '#006400', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'grey': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgrey': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370d8', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#d87093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32' + }; +})(require('./tree')); + +(function (tree) { + + tree.debugInfo = function (env, ctx, lineSeperator) { + var result = ""; + if (env.dumpLineNumbers && !env.compress) { + switch (env.dumpLineNumbers) { + case 'comments': + result = tree.debugInfo.asComment(ctx); + break; + case 'mediaquery': + result = tree.debugInfo.asMediaQuery(ctx); + break; + case 'all': + result = tree.debugInfo.asComment(ctx) + (lineSeperator || "") + tree.debugInfo.asMediaQuery(ctx); + break; + } + } + return result; + }; + + tree.debugInfo.asComment = function (ctx) { + return '/* line ' + ctx.debugInfo.lineNumber + ', ' + ctx.debugInfo.fileName + ' */\n'; + }; + + tree.debugInfo.asMediaQuery = function (ctx) { + return '@media -sass-debug-info{filename{font-family:' + + ('file://' + ctx.debugInfo.fileName).replace(/([.:\/\\])/g, function (a) { + if (a == '\\') { + a = '\/'; + } + return '\\' + a; + }) + + '}line{font-family:\\00003' + ctx.debugInfo.lineNumber + '}}\n'; + }; + + tree.find = function (obj, fun) { + for (var i = 0, r; i < obj.length; i++) { + r = fun.call(obj, obj[i]); + if (r) { + return r; + } + } + return null; + }; + + tree.jsify = function (obj) { + if (Array.isArray(obj.value) && (obj.value.length > 1)) { + return '[' + obj.value.map(function (v) { + return v.toCSS(); + }).join(', ') + ']'; + } else { + return obj.toCSS(); + } + }; + + tree.toCSS = function (env) { + var strs = []; + this.genCSS(env, { + add: function (chunk, fileInfo, index) { + strs.push(chunk); + }, + isEmpty: function () { + return strs.length === 0; + } + }); + return strs.join(''); + }; + + tree.outputRuleset = function (env, output, rules) { + var ruleCnt = rules.length, i; + env.tabLevel = (env.tabLevel | 0) + 1; + + // Compressed + if (env.compress) { + output.add('{'); + for (i = 0; i < ruleCnt; i++) { + rules[i].genCSS(env, output); + } + output.add('}'); + env.tabLevel--; + return; + } + + // Non-compressed + var tabSetStr = '\n' + Array(env.tabLevel).join(" "), tabRuleStr = tabSetStr + " "; + if (!ruleCnt) { + output.add(" {" + tabSetStr + '}'); + } else { + output.add(" {" + tabRuleStr); + rules[0].genCSS(env, output); + for (i = 1; i < ruleCnt; i++) { + output.add(tabRuleStr); + rules[i].genCSS(env, output); + } + output.add(tabSetStr + '}'); + } + + env.tabLevel--; + }; + +})(require('./tree')); + +(function (tree) { + + tree.Alpha = function (val) { + this.value = val; + }; + tree.Alpha.prototype = { + type: "Alpha", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + eval: function (env) { + if (this.value.eval) { + return new tree.Alpha(this.value.eval(env)); + } + return this; + }, + genCSS: function (env, output) { + output.add("alpha(opacity="); + + if (this.value.genCSS) { + this.value.genCSS(env, output); + } else { + output.add(this.value); + } + + output.add(")"); + }, + toCSS: tree.toCSS + }; + +})(require('../tree')); + +(function (tree) { + + tree.Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike) { + this.value = value; + this.index = index; + this.mapLines = mapLines; + this.currentFileInfo = currentFileInfo; + this.rulesetLike = (typeof rulesetLike === 'undefined') ? false : rulesetLike; + }; + tree.Anonymous.prototype = { + type: "Anonymous", + eval: function () { + return new tree.Anonymous(this.value, this.index, this.currentFileInfo, this.mapLines, this.rulesetLike); + }, + compare: function (x) { + if (!x.toCSS) { + return -1; + } + + var left = this.toCSS(), + right = x.toCSS(); + + if (left === right) { + return 0; + } + + return left < right ? -1 : 1; + }, + isRulesetLike: function () { + return this.rulesetLike; + }, + genCSS: function (env, output) { + output.add(this.value, this.currentFileInfo, this.index, this.mapLines); + }, + toCSS: tree.toCSS + }; + +})(require('../tree')); + +(function (tree) { + + tree.Assignment = function (key, val) { + this.key = key; + this.value = val; + }; + tree.Assignment.prototype = { + type: "Assignment", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + eval: function (env) { + if (this.value.eval) { + return new (tree.Assignment)(this.key, this.value.eval(env)); + } + return this; + }, + genCSS: function (env, output) { + output.add(this.key + '='); + if (this.value.genCSS) { + this.value.genCSS(env, output); + } else { + output.add(this.value); + } + }, + toCSS: tree.toCSS + }; + +})(require('../tree')); + +(function (tree) { + +// +// A function call node. +// + tree.Call = function (name, args, index, currentFileInfo) { + this.name = name; + this.args = args; + this.index = index; + this.currentFileInfo = currentFileInfo; + }; + tree.Call.prototype = { + type: "Call", + accept: function (visitor) { + if (this.args) { + this.args = visitor.visitArray(this.args); + } + }, + // + // When evaluating a function call, + // we either find the function in `tree.functions` [1], + // 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. + // + eval: function (env) { + var args = this.args.map(function (a) { + return a.eval(env); + }), + nameLC = this.name.toLowerCase(), + result, func; + + if (nameLC in tree.functions) { // 1. + try { + func = new tree.functionCall(env, this.currentFileInfo); + result = func[nameLC].apply(func, args); + if (result != null) { + return result; + } + } catch (e) { + throw { + type: e.type || "Runtime", + message: "error evaluating function `" + this.name + "`" + + (e.message ? ': ' + e.message : ''), + index: this.index, filename: this.currentFileInfo.filename + }; + } + } + + return new tree.Call(this.name, args, this.index, this.currentFileInfo); + }, + + genCSS: function (env, output) { + output.add(this.name + "(", this.currentFileInfo, this.index); + + for (var i = 0; i < this.args.length; i++) { + this.args[i].genCSS(env, output); + if (i + 1 < this.args.length) { + output.add(", "); + } + } + + output.add(")"); + }, + + toCSS: tree.toCSS + }; + +})(require('../tree')); + +(function (tree) { +// +// RGB Colors - #ff0014, #eee +// + tree.Color = function (rgb, a) { + // + // The end goal here, is to parse the arguments + // into an integer triplet, such as `128, 255, 0` + // + // This facilitates operations and conversions. + // + if (Array.isArray(rgb)) { + this.rgb = rgb; + } else if (rgb.length == 6) { + this.rgb = rgb.match(/.{2}/g).map(function (c) { + return parseInt(c, 16); + }); + } else { + this.rgb = rgb.split('').map(function (c) { + return parseInt(c + c, 16); + }); + } + this.alpha = typeof(a) === 'number' ? a : 1; + }; + + var transparentKeyword = "transparent"; + + tree.Color.prototype = { + type: "Color", + eval: function () { + return this; + }, + luma: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255; + + r = (r <= 0.03928) ? r / 12.92 : Math.pow(((r + 0.055) / 1.055), 2.4); + g = (g <= 0.03928) ? g / 12.92 : Math.pow(((g + 0.055) / 1.055), 2.4); + b = (b <= 0.03928) ? b / 12.92 : Math.pow(((b + 0.055) / 1.055), 2.4); + + return 0.2126 * r + 0.7152 * g + 0.0722 * b; + }, + + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, + toCSS: function (env, doNotCompress) { + var compress = env && env.compress && !doNotCompress, + alpha = tree.fround(env, this.alpha); + + // If we have some transparency, the only way to represent it + // is via `rgba`. Otherwise, we use the hex representation, + // which has better compatibility with older browsers. + // Values are capped between `0` and `255`, rounded and zero-padded. + if (alpha < 1) { + if (alpha === 0 && this.isTransparentKeyword) { + return transparentKeyword; + } + return "rgba(" + this.rgb.map(function (c) { + return clamp(Math.round(c), 255); + }).concat(clamp(alpha, 1)) + .join(',' + (compress ? '' : ' ')) + ")"; + } else { + var color = this.toRGB(); + + if (compress) { + var splitcolor = color.split(''); + + // Convert color to short format + if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) { + color = '#' + splitcolor[1] + splitcolor[3] + splitcolor[5]; + } + } + + return color; + } + }, + + // + // Operations have to be done per-channel, if not, + // channels will spill onto each other. Once we have + // our result, in the form of an integer triplet, + // we create a new Color node to hold the result. + // + operate: function (env, op, other) { + var rgb = []; + var alpha = this.alpha * (1 - other.alpha) + other.alpha; + for (var c = 0; c < 3; c++) { + rgb[c] = tree.operate(env, op, this.rgb[c], other.rgb[c]); + } + return new (tree.Color)(rgb, alpha); + }, + + toRGB: function () { + return toHex(this.rgb); + }, + + toHSL: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255, + a = this.alpha; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2, d = max - min; + + if (max === min) { + h = s = 0; + } else { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + 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; + } + h /= 6; + } + return {h: h * 360, s: s, l: l, a: a}; + }, + //Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript + toHSV: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255, + a = this.alpha; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, v = max; + + var d = max - min; + if (max === 0) { + s = 0; + } else { + s = d / max; + } + + if (max === min) { + h = 0; + } else { + 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; + } + h /= 6; + } + return {h: h * 360, s: s, v: v, a: a}; + }, + toARGB: function () { + return toHex([this.alpha * 255].concat(this.rgb)); + }, + compare: function (x) { + if (!x.rgb) { + return -1; + } + + return (x.rgb[0] === this.rgb[0] && + x.rgb[1] === this.rgb[1] && + x.rgb[2] === this.rgb[2] && + x.alpha === this.alpha) ? 0 : -1; + } + }; + + tree.Color.fromKeyword = function (keyword) { + keyword = keyword.toLowerCase(); + + if (tree.colors.hasOwnProperty(keyword)) { + // detect named color + return new (tree.Color)(tree.colors[keyword].slice(1)); + } + if (keyword === transparentKeyword) { + var transparent = new (tree.Color)([0, 0, 0], 0); + transparent.isTransparentKeyword = true; + return transparent; + } + }; + + function toHex(v) { + return '#' + v.map(function (c) { + c = clamp(Math.round(c), 255); + return (c < 16 ? '0' : '') + c.toString(16); + }).join(''); + } + + function clamp(v, max) { + return Math.min(Math.max(v, 0), max); + } + +})(require('../tree')); + +(function (tree) { + + tree.Comment = function (value, silent, index, currentFileInfo) { + this.value = value; + this.silent = !!silent; + this.currentFileInfo = currentFileInfo; + }; + tree.Comment.prototype = { + type: "Comment", + genCSS: function (env, output) { + if (this.debugInfo) { + output.add(tree.debugInfo(env, this), this.currentFileInfo, this.index); + } + output.add(this.value.trim()); //TODO shouldn't need to trim, we shouldn't grab the \n + }, + toCSS: tree.toCSS, + isSilent: function (env) { + var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced), + isCompressed = env.compress && !this.value.match(/^\/\*!/); + return this.silent || isReference || isCompressed; + }, + eval: function () { + return this; + }, + markReferenced: function () { + this.isReferenced = true; + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Condition = function (op, l, r, i, negate) { + this.op = op.trim(); + this.lvalue = l; + this.rvalue = r; + this.index = i; + this.negate = negate; + }; + tree.Condition.prototype = { + type: "Condition", + accept: function (visitor) { + this.lvalue = visitor.visit(this.lvalue); + this.rvalue = visitor.visit(this.rvalue); + }, + eval: function (env) { + var a = this.lvalue.eval(env), + b = this.rvalue.eval(env); + + var i = this.index, result; + + result = (function (op) { + switch (op) { + case 'and': + return a && b; + case 'or': + return a || b; + default: + if (a.compare) { + result = a.compare(b); + } else if (b.compare) { + result = b.compare(a); + } else { + throw { + type: "Type", + message: "Unable to perform comparison", + index: i + }; + } + switch (result) { + case -1: + return op === '<' || op === '=<' || op === '<='; + case 0: + return op === '=' || op === '>=' || op === '=<' || op === '<='; + case 1: + return op === '>' || op === '>='; + } + } + })(this.op); + return this.negate ? !result : result; + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.DetachedRuleset = function (ruleset, frames) { + this.ruleset = ruleset; + this.frames = frames; + }; + tree.DetachedRuleset.prototype = { + type: "DetachedRuleset", + accept: function (visitor) { + this.ruleset = visitor.visit(this.ruleset); + }, + eval: function (env) { + var frames = this.frames || env.frames.slice(0); + return new tree.DetachedRuleset(this.ruleset, frames); + }, + callEval: function (env) { + return this.ruleset.eval(this.frames ? new (tree.evalEnv)(env, this.frames.concat(env.frames)) : env); + } + }; +})(require('../tree')); + +(function (tree) { + +// +// A number with a unit +// + tree.Dimension = function (value, unit) { + this.value = parseFloat(value); + this.unit = (unit && unit instanceof tree.Unit) ? unit : + new (tree.Unit)(unit ? [unit] : undefined); + }; + + tree.Dimension.prototype = { + type: "Dimension", + accept: function (visitor) { + this.unit = visitor.visit(this.unit); + }, + eval: function (env) { + return this; + }, + toColor: function () { + return new (tree.Color)([this.value, this.value, this.value]); + }, + genCSS: function (env, output) { + if ((env && env.strictUnits) && !this.unit.isSingular()) { + throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: " + this.unit.toString()); + } + + var value = tree.fround(env, this.value), + strValue = String(value); + + if (value !== 0 && value < 0.000001 && value > -0.000001) { + // would be output 1e-6 etc. + strValue = value.toFixed(20).replace(/0+$/, ""); + } + + if (env && env.compress) { + // Zero values doesn't need a unit + if (value === 0 && this.unit.isLength()) { + output.add(strValue); + return; + } + + // Float values doesn't need a leading zero + if (value > 0 && value < 1) { + strValue = (strValue).substr(1); + } + } + + output.add(strValue); + this.unit.genCSS(env, output); + }, + toCSS: tree.toCSS, + + // In an operation between two Dimensions, + // we default to the first Dimension's unit, + // so `1px + 2` will yield `3px`. + operate: function (env, op, other) { + /*jshint noempty:false */ + var value = tree.operate(env, op, this.value, other.value), + unit = this.unit.clone(); + + if (op === '+' || op === '-') { + if (unit.numerator.length === 0 && unit.denominator.length === 0) { + unit.numerator = other.unit.numerator.slice(0); + unit.denominator = other.unit.denominator.slice(0); + } else if (other.unit.numerator.length === 0 && unit.denominator.length === 0) { + // do nothing + } else { + other = other.convertTo(this.unit.usedUnits()); + + if (env.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 = tree.operate(env, op, this.value, other.value); + } + } else if (op === '*') { + unit.numerator = unit.numerator.concat(other.unit.numerator).sort(); + unit.denominator = unit.denominator.concat(other.unit.denominator).sort(); + unit.cancel(); + } else if (op === '/') { + unit.numerator = unit.numerator.concat(other.unit.denominator).sort(); + unit.denominator = unit.denominator.concat(other.unit.numerator).sort(); + unit.cancel(); + } + return new (tree.Dimension)(value, unit); + }, + + compare: function (other) { + if (other instanceof tree.Dimension) { + var a, b, + aValue, bValue; + + if (this.unit.isEmpty() || other.unit.isEmpty()) { + a = this; + b = other; + } else { + a = this.unify(); + b = other.unify(); + if (a.unit.compare(b.unit) !== 0) { + return -1; + } + } + aValue = a.value; + bValue = b.value; + + if (bValue > aValue) { + return -1; + } else if (bValue < aValue) { + return 1; + } else { + return 0; + } + } else { + return -1; + } + }, + + unify: function () { + return this.convertTo({length: 'px', duration: 's', angle: 'rad'}); + }, + + convertTo: function (conversions) { + var value = this.value, unit = this.unit.clone(), + i, groupName, group, targetUnit, derivedConversions = {}, applyUnit; + + if (typeof conversions === 'string') { + for (i in tree.UnitConversions) { + if (tree.UnitConversions[i].hasOwnProperty(conversions)) { + derivedConversions = {}; + derivedConversions[i] = conversions; + } + } + conversions = derivedConversions; + } + applyUnit = function (atomicUnit, denominator) { + /*jshint loopfunc:true */ + if (group.hasOwnProperty(atomicUnit)) { + if (denominator) { + value = value / (group[atomicUnit] / group[targetUnit]); + } else { + value = value * (group[atomicUnit] / group[targetUnit]); + } + + return targetUnit; + } + + return atomicUnit; + }; + + for (groupName in conversions) { + if (conversions.hasOwnProperty(groupName)) { + targetUnit = conversions[groupName]; + group = tree.UnitConversions[groupName]; + + unit.map(applyUnit); + } + } + + unit.cancel(); + + return new (tree.Dimension)(value, unit); + } + }; + +// http://www.w3.org/TR/css3-values/#absolute-lengths + tree.UnitConversions = { + length: { + 'm': 1, + 'cm': 0.01, + 'mm': 0.001, + 'in': 0.0254, + 'px': 0.0254 / 96, + 'pt': 0.0254 / 72, + 'pc': 0.0254 / 72 * 12 + }, + duration: { + 's': 1, + 'ms': 0.001 + }, + angle: { + 'rad': 1 / (2 * Math.PI), + 'deg': 1 / 360, + 'grad': 1 / 400, + 'turn': 1 + } + }; + + tree.Unit = function (numerator, denominator, backupUnit) { + this.numerator = numerator ? numerator.slice(0).sort() : []; + this.denominator = denominator ? denominator.slice(0).sort() : []; + this.backupUnit = backupUnit; + }; + + tree.Unit.prototype = { + type: "Unit", + clone: function () { + return new tree.Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit); + }, + genCSS: function (env, output) { + if (this.numerator.length >= 1) { + output.add(this.numerator[0]); + } else if (this.denominator.length >= 1) { + output.add(this.denominator[0]); + } else if ((!env || !env.strictUnits) && this.backupUnit) { + output.add(this.backupUnit); + } + }, + toCSS: tree.toCSS, + + toString: function () { + var i, returnStr = this.numerator.join("*"); + for (i = 0; i < this.denominator.length; i++) { + returnStr += "/" + this.denominator[i]; + } + return returnStr; + }, + + compare: function (other) { + return this.is(other.toString()) ? 0 : -1; + }, + + is: function (unitString) { + return this.toString() === unitString; + }, + + isLength: function () { + return Boolean(this.toCSS().match(/px|em|%|in|cm|mm|pc|pt|ex/)); + }, + + isEmpty: function () { + return this.numerator.length === 0 && this.denominator.length === 0; + }, + + isSingular: function () { + return this.numerator.length <= 1 && this.denominator.length === 0; + }, + + map: function (callback) { + var i; + + for (i = 0; i < this.numerator.length; i++) { + this.numerator[i] = callback(this.numerator[i], false); + } + + for (i = 0; i < this.denominator.length; i++) { + this.denominator[i] = callback(this.denominator[i], true); + } + }, + + usedUnits: function () { + var group, result = {}, mapUnit; + + mapUnit = function (atomicUnit) { + /*jshint loopfunc:true */ + if (group.hasOwnProperty(atomicUnit) && !result[groupName]) { + result[groupName] = atomicUnit; + } + + return atomicUnit; + }; + + for (var groupName in tree.UnitConversions) { + if (tree.UnitConversions.hasOwnProperty(groupName)) { + group = tree.UnitConversions[groupName]; + + this.map(mapUnit); + } + } + + return result; + }, + + cancel: function () { + var counter = {}, atomicUnit, i, backup; + + 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; + } + + this.numerator = []; + this.denominator = []; + + for (atomicUnit in counter) { + if (counter.hasOwnProperty(atomicUnit)) { + var count = counter[atomicUnit]; + + if (count > 0) { + for (i = 0; i < count; i++) { + this.numerator.push(atomicUnit); + } + } else if (count < 0) { + for (i = 0; i < -count; i++) { + this.denominator.push(atomicUnit); + } + } + } + } + + if (this.numerator.length === 0 && this.denominator.length === 0 && backup) { + this.backupUnit = backup; + } + + this.numerator.sort(); + this.denominator.sort(); + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Directive = function (name, value, rules, index, currentFileInfo, debugInfo) { + this.name = name; + this.value = value; + if (rules) { + this.rules = rules; + this.rules.allowImports = true; + } + this.index = index; + this.currentFileInfo = currentFileInfo; + this.debugInfo = debugInfo; + }; + + tree.Directive.prototype = { + type: "Directive", + accept: function (visitor) { + var value = this.value, rules = this.rules; + if (rules) { + rules = visitor.visit(rules); + } + if (value) { + value = visitor.visit(value); + } + }, + isRulesetLike: function () { + return !this.isCharset(); + }, + isCharset: function () { + return "@charset" === this.name; + }, + genCSS: function (env, output) { + var value = this.value, rules = this.rules; + output.add(this.name, this.currentFileInfo, this.index); + if (value) { + output.add(' '); + value.genCSS(env, output); + } + if (rules) { + tree.outputRuleset(env, output, [rules]); + } else { + output.add(';'); + } + }, + toCSS: tree.toCSS, + eval: function (env) { + var value = this.value, rules = this.rules; + if (value) { + value = value.eval(env); + } + if (rules) { + rules = rules.eval(env); + rules.root = true; + } + return new (tree.Directive)(this.name, value, rules, + this.index, this.currentFileInfo, this.debugInfo); + }, + variable: function (name) { + if (this.rules) return tree.Ruleset.prototype.variable.call(this.rules, name); + }, + find: function () { + if (this.rules) return tree.Ruleset.prototype.find.apply(this.rules, arguments); + }, + rulesets: function () { + if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); + }, + markReferenced: function () { + var i, rules; + this.isReferenced = true; + if (this.rules) { + rules = this.rules.rules; + for (i = 0; i < rules.length; i++) { + if (rules[i].markReferenced) { + rules[i].markReferenced(); + } + } + } + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Element = function (combinator, value, index, currentFileInfo) { + this.combinator = combinator instanceof tree.Combinator ? + combinator : new (tree.Combinator)(combinator); + + if (typeof(value) === 'string') { + this.value = value.trim(); + } else if (value) { + this.value = value; + } else { + this.value = ""; + } + this.index = index; + this.currentFileInfo = currentFileInfo; + }; + tree.Element.prototype = { + type: "Element", + accept: function (visitor) { + var value = this.value; + this.combinator = visitor.visit(this.combinator); + if (typeof value === "object") { + this.value = visitor.visit(value); + } + }, + eval: function (env) { + return new (tree.Element)(this.combinator, + this.value.eval ? this.value.eval(env) : this.value, + this.index, + this.currentFileInfo); + }, + genCSS: function (env, output) { + output.add(this.toCSS(env), this.currentFileInfo, this.index); + }, + toCSS: function (env) { + var value = (this.value.toCSS ? this.value.toCSS(env) : this.value); + if (value === '' && this.combinator.value.charAt(0) === '&') { + return ''; + } else { + return this.combinator.toCSS(env || {}) + value; + } + } + }; + + tree.Attribute = function (key, op, value) { + this.key = key; + this.op = op; + this.value = value; + }; + tree.Attribute.prototype = { + type: "Attribute", + eval: function (env) { + return new (tree.Attribute)(this.key.eval ? this.key.eval(env) : this.key, + this.op, (this.value && this.value.eval) ? this.value.eval(env) : this.value); + }, + genCSS: function (env, output) { + output.add(this.toCSS(env)); + }, + toCSS: function (env) { + var value = this.key.toCSS ? this.key.toCSS(env) : this.key; + + if (this.op) { + value += this.op; + value += (this.value.toCSS ? this.value.toCSS(env) : this.value); + } + + return '[' + value + ']'; + } + }; + + tree.Combinator = function (value) { + if (value === ' ') { + this.value = ' '; + } else { + this.value = value ? value.trim() : ""; + } + }; + tree.Combinator.prototype = { + type: "Combinator", + _noSpaceCombinators: { + '': true, + ' ': true, + '|': true + }, + genCSS: function (env, output) { + var spaceOrEmpty = (env.compress || this._noSpaceCombinators[this.value]) ? '' : ' '; + output.add(spaceOrEmpty + this.value + spaceOrEmpty); + }, + toCSS: tree.toCSS + }; + +})(require('../tree')); + +(function (tree) { + + tree.Expression = function (value) { + this.value = value; + }; + tree.Expression.prototype = { + type: "Expression", + accept: function (visitor) { + if (this.value) { + this.value = visitor.visitArray(this.value); + } + }, + eval: function (env) { + var returnValue, + inParenthesis = this.parens && !this.parensInOp, + doubleParen = false; + if (inParenthesis) { + env.inParenthesis(); + } + if (this.value.length > 1) { + returnValue = new (tree.Expression)(this.value.map(function (e) { + return e.eval(env); + })); + } else if (this.value.length === 1) { + if (this.value[0].parens && !this.value[0].parensInOp) { + doubleParen = true; + } + returnValue = this.value[0].eval(env); + } else { + returnValue = this; + } + if (inParenthesis) { + env.outOfParenthesis(); + } + if (this.parens && this.parensInOp && !(env.isMathOn()) && !doubleParen) { + returnValue = new (tree.Paren)(returnValue); + } + return returnValue; + }, + genCSS: function (env, output) { + for (var i = 0; i < this.value.length; i++) { + this.value[i].genCSS(env, output); + if (i + 1 < this.value.length) { + output.add(" "); + } + } + }, + toCSS: tree.toCSS, + throwAwayComments: function () { + this.value = this.value.filter(function (v) { + return !(v instanceof tree.Comment); + }); + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Extend = function Extend(selector, option, index) { + this.selector = selector; + this.option = option; + this.index = index; + this.object_id = tree.Extend.next_id++; + this.parent_ids = [this.object_id]; + + switch (option) { + case "all": + this.allowBefore = true; + this.allowAfter = true; + break; + default: + this.allowBefore = false; + this.allowAfter = false; + break; + } + }; + tree.Extend.next_id = 0; + + tree.Extend.prototype = { + type: "Extend", + accept: function (visitor) { + this.selector = visitor.visit(this.selector); + }, + eval: function (env) { + return new (tree.Extend)(this.selector.eval(env), this.option, this.index); + }, + clone: function (env) { + return new (tree.Extend)(this.selector, this.option, this.index); + }, + findSelfSelectors: function (selectors) { + var selfElements = [], + i, + selectorElements; + + 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 + if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === "") { + selectorElements[0].combinator.value = ' '; + } + selfElements = selfElements.concat(selectors[i].elements); + } + + this.selfSelectors = [{elements: selfElements}]; + } + }; + +})(require('../tree')); + +(function (tree) { +// +// CSS @import node +// +// The general strategy here is that we don't want to wait +// for the parsing to be completed, before we start importing +// the file. That's because in the context of a browser, +// most of the time will be spent waiting for the server to respond. +// +// On creation, we push the import path to our import queue, though +// `import,push`, we also pass it a callback, which it'll call once +// the file has been fetched, and parsed. +// + tree.Import = function (path, features, options, index, currentFileInfo) { + this.options = options; + this.index = index; + this.path = path; + this.features = features; + this.currentFileInfo = currentFileInfo; + + if (this.options.less !== undefined || this.options.inline) { + this.css = !this.options.less || this.options.inline; + } else { + var pathValue = this.getPath(); + if (pathValue && /css([\?;].*)?$/.test(pathValue)) { + this.css = true; + } + } + }; + +// +// The actual import node doesn't return anything, when converted to CSS. +// The reason is that it's used at the evaluation stage, so that the rules +// it imports can be treated like any other rules. +// +// In `eval`, we make sure all Import nodes get evaluated, recursively, so +// we end up with a flat structure, which can easily be imported in the parent +// ruleset. +// + tree.Import.prototype = { + type: "Import", + accept: function (visitor) { + if (this.features) { + this.features = visitor.visit(this.features); + } + this.path = visitor.visit(this.path); + if (!this.options.inline && this.root) { + this.root = visitor.visit(this.root); + } + }, + genCSS: function (env, output) { + if (this.css) { + output.add("@import ", this.currentFileInfo, this.index); + this.path.genCSS(env, output); + if (this.features) { + output.add(" "); + this.features.genCSS(env, output); + } + output.add(';'); + } + }, + toCSS: tree.toCSS, + getPath: function () { + if (this.path instanceof tree.Quoted) { + var path = this.path.value; + return (this.css !== undefined || /(\.[a-z]*$)|([\?;].*)$/.test(path)) ? path : path + '.less'; + } else if (this.path instanceof tree.URL) { + return this.path.value.value; + } + return null; + }, + evalForImport: function (env) { + return new (tree.Import)(this.path.eval(env), this.features, this.options, this.index, this.currentFileInfo); + }, + evalPath: function (env) { + var path = this.path.eval(env); + var rootpath = this.currentFileInfo && this.currentFileInfo.rootpath; + + if (!(path instanceof tree.URL)) { + if (rootpath) { + var pathValue = path.value; + // Add the base path if the import is relative + if (pathValue && env.isPathRelative(pathValue)) { + path.value = rootpath + pathValue; + } + } + path.value = env.normalizePath(path.value); + } + + return path; + }, + eval: function (env) { + var ruleset, features = this.features && this.features.eval(env); + + if (this.skip) { + if (typeof this.skip === "function") { + this.skip = this.skip(); + } + if (this.skip) { + return []; + } + } + + if (this.options.inline) { + //todo needs to reference css file not import + var contents = new (tree.Anonymous)(this.root, 0, {filename: this.importedFilename}, true, true); + return this.features ? new (tree.Media)([contents], this.features.value) : [contents]; + } else if (this.css) { + var newImport = new (tree.Import)(this.evalPath(env), features, this.options, this.index); + if (!newImport.css && this.error) { + throw this.error; + } + return newImport; + } else { + ruleset = new (tree.Ruleset)(null, this.root.rules.slice(0)); + + ruleset.evalImports(env); + + return this.features ? new (tree.Media)(ruleset.rules, this.features.value) : ruleset.rules; + } + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.JavaScript = function (string, index, escaped) { + this.escaped = escaped; + this.expression = string; + this.index = index; + }; + tree.JavaScript.prototype = { + type: "JavaScript", + eval: function (env) { + var result, + that = this, + context = {}; + + var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) { + return tree.jsify(new (tree.Variable)('@' + name, that.index).eval(env)); + }); + + try { + expression = new (Function)('return (' + expression + ')'); + } catch (e) { + throw { + message: "JavaScript evaluation error: " + e.message + " from `" + expression + "`", + index: this.index + }; + } + + var variables = env.frames[0].variables(); + for (var k in variables) { + if (variables.hasOwnProperty(k)) { + /*jshint loopfunc:true */ + context[k.slice(1)] = { + value: variables[k].value, + toJS: function () { + return this.value.eval(env).toCSS(); + } + }; + } + } + + try { + result = expression.call(context); + } catch (e) { + throw { + message: "JavaScript evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'", + index: this.index + }; + } + if (typeof(result) === 'number') { + return new (tree.Dimension)(result); + } else if (typeof(result) === 'string') { + return new (tree.Quoted)('"' + result + '"', result, this.escaped, this.index); + } else if (Array.isArray(result)) { + return new (tree.Anonymous)(result.join(', ')); + } else { + return new (tree.Anonymous)(result); + } + } + }; + +})(require('../tree')); + + +(function (tree) { + + tree.Keyword = function (value) { + this.value = value; + }; + tree.Keyword.prototype = { + type: "Keyword", + eval: function () { + return this; + }, + genCSS: function (env, output) { + if (this.value === '%') { + throw {type: "Syntax", message: "Invalid % without number"}; + } + output.add(this.value); + }, + toCSS: tree.toCSS, + compare: function (other) { + if (other instanceof tree.Keyword) { + return other.value === this.value ? 0 : 1; + } else { + return -1; + } + } + }; + + tree.True = new (tree.Keyword)('true'); + tree.False = new (tree.Keyword)('false'); + +})(require('../tree')); + +(function (tree) { + + tree.Media = function (value, features, index, currentFileInfo) { + this.index = index; + this.currentFileInfo = currentFileInfo; + + var selectors = this.emptySelectors(); + + this.features = new (tree.Value)(features); + this.rules = [new (tree.Ruleset)(selectors, value)]; + this.rules[0].allowImports = true; + }; + tree.Media.prototype = { + type: "Media", + accept: function (visitor) { + if (this.features) { + this.features = visitor.visit(this.features); + } + if (this.rules) { + this.rules = visitor.visitArray(this.rules); + } + }, + genCSS: function (env, output) { + output.add('@media ', this.currentFileInfo, this.index); + this.features.genCSS(env, output); + tree.outputRuleset(env, output, this.rules); + }, + toCSS: tree.toCSS, + eval: function (env) { + if (!env.mediaBlocks) { + env.mediaBlocks = []; + env.mediaPath = []; + } + + var media = new (tree.Media)(null, [], this.index, this.currentFileInfo); + if (this.debugInfo) { + this.rules[0].debugInfo = this.debugInfo; + media.debugInfo = this.debugInfo; + } + var strictMathBypass = false; + if (!env.strictMath) { + strictMathBypass = true; + env.strictMath = true; + } + try { + media.features = this.features.eval(env); + } + finally { + if (strictMathBypass) { + env.strictMath = false; + } + } + + env.mediaPath.push(media); + env.mediaBlocks.push(media); + + env.frames.unshift(this.rules[0]); + media.rules = [this.rules[0].eval(env)]; + env.frames.shift(); + + env.mediaPath.pop(); + + return env.mediaPath.length === 0 ? media.evalTop(env) : + media.evalNested(env); + }, + variable: function (name) { + return tree.Ruleset.prototype.variable.call(this.rules[0], name); + }, + find: function () { + return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); + }, + rulesets: function () { + return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); + }, + emptySelectors: function () { + var el = new (tree.Element)('', '&', this.index, this.currentFileInfo), + sels = [new (tree.Selector)([el], null, null, this.index, this.currentFileInfo)]; + sels[0].mediaEmpty = true; + return sels; + }, + markReferenced: function () { + var i, rules = this.rules[0].rules; + this.rules[0].markReferenced(); + this.isReferenced = true; + for (i = 0; i < rules.length; i++) { + if (rules[i].markReferenced) { + rules[i].markReferenced(); + } + } + }, + + evalTop: function (env) { + var result = this; + + // Render all dependent Media blocks. + if (env.mediaBlocks.length > 1) { + var selectors = this.emptySelectors(); + result = new (tree.Ruleset)(selectors, env.mediaBlocks); + result.multiMedia = true; + } + + delete env.mediaBlocks; + delete env.mediaPath; + + return result; + }, + evalNested: function (env) { + var i, value, + path = env.mediaPath.concat([this]); + + // Extract the media-query conditions separated with `,` (OR). + for (i = 0; i < path.length; i++) { + value = path[i].features instanceof tree.Value ? + path[i].features.value : path[i].features; + path[i] = Array.isArray(value) ? value : [value]; + } + + // Trace all permutations to generate the resulting media-query. + // + // (a, b and c) with nested (d, e) -> + // a and d + // a and e + // b and c and d + // b and c and e + this.features = new (tree.Value)(this.permute(path).map(function (path) { + path = path.map(function (fragment) { + return fragment.toCSS ? fragment : new (tree.Anonymous)(fragment); + }); + + for (i = path.length - 1; i > 0; i--) { + path.splice(i, 0, new (tree.Anonymous)("and")); + } + + return new (tree.Expression)(path); + })); + + // Fake a tree-node that doesn't output anything. + return new (tree.Ruleset)([], []); + }, + 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; + } + }, + bubbleSelectors: function (selectors) { + if (!selectors) + return; + this.rules = [new (tree.Ruleset)(selectors.slice(0), [this.rules[0]])]; + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.mixin = {}; + tree.mixin.Call = function (elements, args, index, currentFileInfo, important) { + this.selector = new (tree.Selector)(elements); + this.arguments = (args && args.length) ? args : null; + this.index = index; + this.currentFileInfo = currentFileInfo; + this.important = important; + }; + tree.mixin.Call.prototype = { + type: "MixinCall", + accept: function (visitor) { + if (this.selector) { + this.selector = visitor.visit(this.selector); + } + if (this.arguments) { + this.arguments = visitor.visitArray(this.arguments); + } + }, + eval: function (env) { + var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule, + candidates = [], candidate, conditionResult = [], defaultFunc = tree.defaultFunc, + defaultResult, defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset; + + args = this.arguments && this.arguments.map(function (a) { + return {name: a.name, value: a.value.eval(env)}; + }); + + for (i = 0; i < env.frames.length; i++) { + if ((mixins = env.frames[i].find(this.selector)).length > 0) { + isOneFound = true; + + // To make `default()` function independent of definition order we have two "subpasses" here. + // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`), + // and build candidate list with corresponding flags. Then, when we know all possible matches, + // we make a final decision. + + for (m = 0; m < mixins.length; m++) { + mixin = mixins[m]; + isRecursive = false; + for (f = 0; f < env.frames.length; f++) { + if ((!(mixin instanceof tree.mixin.Definition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) { + isRecursive = true; + break; + } + } + if (isRecursive) { + continue; + } + + if (mixin.matchArgs(args, env)) { + candidate = {mixin: mixin, group: defNone}; + + if (mixin.matchCondition) { + for (f = 0; f < 2; f++) { + defaultFunc.value(f); + conditionResult[f] = mixin.matchCondition(args, env); + } + if (conditionResult[0] || conditionResult[1]) { + if (conditionResult[0] != conditionResult[1]) { + candidate.group = conditionResult[1] ? + defTrue : defFalse; + } + + candidates.push(candidate); + } + } + else { + candidates.push(candidate); + } + + match = true; + } + } + + defaultFunc.reset(); + + count = [0, 0, 0]; + for (m = 0; m < candidates.length; m++) { + count[candidates[m].group]++; + } + + if (count[defNone] > 0) { + defaultResult = defFalse; + } else { + defaultResult = defTrue; + if ((count[defTrue] + count[defFalse]) > 1) { + throw { + type: 'Runtime', + message: 'Ambiguous use of `default()` found when matching for `' + + this.format(args) + '`', + index: this.index, filename: this.currentFileInfo.filename + }; + } + } + + for (m = 0; m < candidates.length; m++) { + candidate = candidates[m].group; + if ((candidate === defNone) || (candidate === defaultResult)) { + try { + mixin = candidates[m].mixin; + if (!(mixin instanceof tree.mixin.Definition)) { + originalRuleset = mixin.originalRuleset || mixin; + mixin = new tree.mixin.Definition("", [], mixin.rules, null, false); + mixin.originalRuleset = originalRuleset; + } + Array.prototype.push.apply( + rules, mixin.evalCall(env, args, this.important).rules); + } catch (e) { + throw { + message: e.message, + index: this.index, + filename: this.currentFileInfo.filename, + stack: e.stack + }; + } + } + } + + if (match) { + if (!this.currentFileInfo || !this.currentFileInfo.reference) { + for (i = 0; i < rules.length; i++) { + rule = rules[i]; + if (rule.markReferenced) { + rule.markReferenced(); + } + } + } + return rules; + } + } + } + if (isOneFound) { + throw { + type: 'Runtime', + message: 'No matching definition was found for `' + this.format(args) + '`', + index: this.index, filename: this.currentFileInfo.filename + }; + } else { + throw { + type: 'Name', + message: this.selector.toCSS().trim() + " is undefined", + index: this.index, filename: this.currentFileInfo.filename + }; + } + }, + format: function (args) { + return this.selector.toCSS().trim() + '(' + + (args ? args.map(function (a) { + var argValue = ""; + if (a.name) { + argValue += a.name + ":"; + } + if (a.value.toCSS) { + argValue += a.value.toCSS(); + } else { + argValue += "???"; + } + return argValue; + }).join(', ') : "") + ")"; + } + }; + + tree.mixin.Definition = function (name, params, rules, condition, variadic, frames) { + this.name = name; + this.selectors = [new (tree.Selector)([new (tree.Element)(null, name, this.index, this.currentFileInfo)])]; + this.params = params; + this.condition = condition; + this.variadic = variadic; + this.arity = params.length; + 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; + } + }, 0); + this.parent = tree.Ruleset.prototype; + this.frames = frames; + }; + tree.mixin.Definition.prototype = { + type: "MixinDefinition", + accept: function (visitor) { + if (this.params && this.params.length) { + this.params = visitor.visitArray(this.params); + } + this.rules = visitor.visitArray(this.rules); + if (this.condition) { + this.condition = visitor.visit(this.condition); + } + }, + variable: function (name) { + return this.parent.variable.call(this, name); + }, + variables: function () { + return this.parent.variables.call(this); + }, + find: function () { + return this.parent.find.apply(this, arguments); + }, + rulesets: function () { + return this.parent.rulesets.apply(this); + }, + + evalParams: function (env, mixinEnv, args, evaldArguments) { + /*jshint boss:true */ + var frame = new (tree.Ruleset)(null, null), + varargs, arg, + params = this.params.slice(0), + i, j, val, name, isNamedFound, argIndex, argsLength = 0; + + mixinEnv = new tree.evalEnv(mixinEnv, [frame].concat(mixinEnv.frames)); + + if (args) { + args = args.slice(0); + argsLength = args.length; + + for (i = 0; i < argsLength; i++) { + arg = args[i]; + if (name = (arg && arg.name)) { + isNamedFound = false; + for (j = 0; j < params.length; j++) { + if (!evaldArguments[j] && name === params[j].name) { + evaldArguments[j] = arg.value.eval(env); + frame.prependRule(new (tree.Rule)(name, arg.value.eval(env))); + isNamedFound = true; + break; + } + } + if (isNamedFound) { + args.splice(i, 1); + i--; + continue; + } else { + throw { + type: 'Runtime', message: "Named argument for " + this.name + + ' ' + args[i].name + ' not found' + }; + } + } + } + } + argIndex = 0; + for (i = 0; i < params.length; i++) { + if (evaldArguments[i]) { + continue; + } + + arg = args && args[argIndex]; + + if (name = params[i].name) { + if (params[i].variadic) { + varargs = []; + for (j = argIndex; j < argsLength; j++) { + varargs.push(args[j].value.eval(env)); + } + frame.prependRule(new (tree.Rule)(name, new (tree.Expression)(varargs).eval(env))); + } else { + val = arg && arg.value; + if (val) { + val = val.eval(env); + } else if (params[i].value) { + val = params[i].value.eval(mixinEnv); + frame.resetCache(); + } else { + throw { + type: 'Runtime', message: "wrong number of arguments for " + this.name + + ' (' + argsLength + ' for ' + this.arity + ')' + }; + } + + frame.prependRule(new (tree.Rule)(name, val)); + evaldArguments[i] = val; + } + } + + if (params[i].variadic && args) { + for (j = argIndex; j < argsLength; j++) { + evaldArguments[j] = args[j].value.eval(env); + } + } + argIndex++; + } + + return frame; + }, + eval: function (env) { + return new tree.mixin.Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || env.frames.slice(0)); + }, + evalCall: function (env, args, important) { + var _arguments = [], + mixinFrames = this.frames ? this.frames.concat(env.frames) : env.frames, + frame = this.evalParams(env, new (tree.evalEnv)(env, mixinFrames), args, _arguments), + rules, ruleset; + + frame.prependRule(new (tree.Rule)('@arguments', new (tree.Expression)(_arguments).eval(env))); + + rules = this.rules.slice(0); + + ruleset = new (tree.Ruleset)(null, rules); + ruleset.originalRuleset = this; + ruleset = ruleset.eval(new (tree.evalEnv)(env, [this, frame].concat(mixinFrames))); + if (important) { + ruleset = this.parent.makeImportant.apply(ruleset); + } + return ruleset; + }, + matchCondition: function (args, env) { + if (this.condition && !this.condition.eval( + new (tree.evalEnv)(env, + [this.evalParams(env, new (tree.evalEnv)(env, this.frames ? this.frames.concat(env.frames) : env.frames), args, [])] // the parameter variables + .concat(this.frames) // the parent namespace/mixin frames + .concat(env.frames)))) { // the current environment frames + return false; + } + return true; + }, + matchArgs: function (args, env) { + var argsLength = (args && args.length) || 0, len; + + if (!this.variadic) { + if (argsLength < this.required) { + return false; + } + if (argsLength > this.params.length) { + return false; + } + } else { + if (argsLength < (this.required - 1)) { + return false; + } + } + + len = Math.min(argsLength, this.arity); + + for (var i = 0; i < len; i++) { + if (!this.params[i].name && !this.params[i].variadic) { + if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) { + return false; + } + } + } + return true; + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Negative = function (node) { + this.value = node; + }; + tree.Negative.prototype = { + type: "Negative", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + output.add('-'); + this.value.genCSS(env, output); + }, + toCSS: tree.toCSS, + eval: function (env) { + if (env.isMathOn()) { + return (new (tree.Operation)('*', [new (tree.Dimension)(-1), this.value])).eval(env); + } + return new (tree.Negative)(this.value.eval(env)); + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Operation = function (op, operands, isSpaced) { + this.op = op.trim(); + this.operands = operands; + this.isSpaced = isSpaced; + }; + tree.Operation.prototype = { + type: "Operation", + accept: function (visitor) { + this.operands = visitor.visit(this.operands); + }, + eval: function (env) { + var a = this.operands[0].eval(env), + b = this.operands[1].eval(env); + + if (env.isMathOn()) { + if (a instanceof tree.Dimension && b instanceof tree.Color) { + a = a.toColor(); + } + if (b instanceof tree.Dimension && a instanceof tree.Color) { + b = b.toColor(); + } + if (!a.operate) { + throw { + type: "Operation", + message: "Operation on an invalid type" + }; + } + + return a.operate(env, this.op, b); + } else { + return new (tree.Operation)(this.op, [a, b], this.isSpaced); + } + }, + genCSS: function (env, output) { + this.operands[0].genCSS(env, output); + if (this.isSpaced) { + output.add(" "); + } + output.add(this.op); + if (this.isSpaced) { + output.add(" "); + } + this.operands[1].genCSS(env, output); + }, + toCSS: tree.toCSS + }; + + tree.operate = function (env, op, a, b) { + switch (op) { + case '+': + return a + b; + case '-': + return a - b; + case '*': + return a * b; + case '/': + return a / b; + } + }; + +})(require('../tree')); + + +(function (tree) { + + tree.Paren = function (node) { + this.value = node; + }; + tree.Paren.prototype = { + type: "Paren", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + output.add('('); + this.value.genCSS(env, output); + output.add(')'); + }, + toCSS: tree.toCSS, + eval: function (env) { + return new (tree.Paren)(this.value.eval(env)); + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Quoted = function (str, content, escaped, index, currentFileInfo) { + this.escaped = escaped; + this.value = content || ''; + this.quote = str.charAt(0); + this.index = index; + this.currentFileInfo = currentFileInfo; + }; + tree.Quoted.prototype = { + type: "Quoted", + genCSS: function (env, output) { + if (!this.escaped) { + output.add(this.quote, this.currentFileInfo, this.index); + } + output.add(this.value); + if (!this.escaped) { + output.add(this.quote); + } + }, + toCSS: tree.toCSS, + eval: function (env) { + var that = this; + var value = this.value.replace(/`([^`]+)`/g, function (_, exp) { + return new (tree.JavaScript)(exp, that.index, true).eval(env).value; + }).replace(/@\{([\w-]+)\}/g, function (_, name) { + var v = new (tree.Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true); + return (v instanceof tree.Quoted) ? v.value : v.toCSS(); + }); + return new (tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index, this.currentFileInfo); + }, + compare: function (x) { + if (!x.toCSS) { + return -1; + } + + var left, right; + + // when comparing quoted strings allow the quote to differ + if (x.type === "Quoted" && !this.escaped && !x.escaped) { + left = x.value; + right = this.value; + } else { + left = this.toCSS(); + right = x.toCSS(); + } + + if (left === right) { + return 0; + } + + return left < right ? -1 : 1; + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Rule = function (name, value, important, merge, index, currentFileInfo, inline, variable) { + this.name = name; + this.value = (value instanceof tree.Value || value instanceof tree.Ruleset) ? value : new (tree.Value)([value]); + this.important = important ? ' ' + important.trim() : ''; + this.merge = merge; + this.index = index; + this.currentFileInfo = currentFileInfo; + this.inline = inline || false; + this.variable = (variable !== undefined) ? variable + : (name.charAt && (name.charAt(0) === '@')); + }; + + tree.Rule.prototype = { + type: "Rule", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + output.add(this.name + (env.compress ? ':' : ': '), this.currentFileInfo, this.index); + try { + this.value.genCSS(env, output); + } + catch (e) { + e.index = this.index; + e.filename = this.currentFileInfo.filename; + throw e; + } + output.add(this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";"), this.currentFileInfo, this.index); + }, + toCSS: tree.toCSS, + eval: function (env) { + var strictMathBypass = false, name = this.name, variable = this.variable, evaldValue; + if (typeof name !== "string") { + // expand 'primitive' name directly to get + // things faster (~10% for benchmark.less): + name = (name.length === 1) + && (name[0] instanceof tree.Keyword) + ? name[0].value : evalName(env, name); + variable = false; // never treat expanded interpolation as new variable name + } + if (name === "font" && !env.strictMath) { + strictMathBypass = true; + env.strictMath = true; + } + try { + evaldValue = this.value.eval(env); + + if (!this.variable && evaldValue.type === "DetachedRuleset") { + throw { + message: "Rulesets cannot be evaluated on a property.", + index: this.index, filename: this.currentFileInfo.filename + }; + } + + return new (tree.Rule)(name, + evaldValue, + this.important, + this.merge, + this.index, this.currentFileInfo, this.inline, + variable); + } + catch (e) { + if (typeof e.index !== 'number') { + e.index = this.index; + e.filename = this.currentFileInfo.filename; + } + throw e; + } + finally { + if (strictMathBypass) { + env.strictMath = false; + } + } + }, + makeImportant: function () { + return new (tree.Rule)(this.name, + this.value, + "!important", + this.merge, + this.index, this.currentFileInfo, this.inline); + } + }; + + function evalName(env, name) { + var value = "", i, n = name.length, + output = { + add: function (s) { + value += s; + } + }; + for (i = 0; i < n; i++) { + name[i].eval(env).genCSS(env, output); + } + return value; + } + +})(require('../tree')); + +(function (tree) { + + tree.RulesetCall = function (variable) { + this.variable = variable; + }; + tree.RulesetCall.prototype = { + type: "RulesetCall", + accept: function (visitor) { + }, + eval: function (env) { + var detachedRuleset = new (tree.Variable)(this.variable).eval(env); + return detachedRuleset.callEval(env); + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Ruleset = function (selectors, rules, strictImports) { + this.selectors = selectors; + this.rules = rules; + this._lookups = {}; + this.strictImports = strictImports; + }; + tree.Ruleset.prototype = { + type: "Ruleset", + accept: function (visitor) { + if (this.paths) { + visitor.visitArray(this.paths, true); + } else if (this.selectors) { + this.selectors = visitor.visitArray(this.selectors); + } + if (this.rules && this.rules.length) { + this.rules = visitor.visitArray(this.rules); + } + }, + eval: function (env) { + var thisSelectors = this.selectors, selectors, + selCnt, selector, i, defaultFunc = tree.defaultFunc, hasOnePassingSelector = false; + + if (thisSelectors && (selCnt = thisSelectors.length)) { + selectors = []; + defaultFunc.error({ + type: "Syntax", + message: "it is currently only allowed in parametric mixin guards," + }); + for (i = 0; i < selCnt; i++) { + selector = thisSelectors[i].eval(env); + selectors.push(selector); + if (selector.evaldCondition) { + hasOnePassingSelector = true; + } + } + defaultFunc.reset(); + } else { + hasOnePassingSelector = true; + } + + var rules = this.rules ? this.rules.slice(0) : null, + ruleset = new (tree.Ruleset)(selectors, rules, this.strictImports), + rule, subRule; + + ruleset.originalRuleset = this; + ruleset.root = this.root; + ruleset.firstRoot = this.firstRoot; + ruleset.allowImports = this.allowImports; + + if (this.debugInfo) { + ruleset.debugInfo = this.debugInfo; + } + + if (!hasOnePassingSelector) { + rules.length = 0; + } + + // push the current ruleset to the frames stack + var envFrames = env.frames; + envFrames.unshift(ruleset); + + // currrent selectors + var envSelectors = env.selectors; + if (!envSelectors) { + env.selectors = envSelectors = []; + } + envSelectors.unshift(this.selectors); + + // Evaluate imports + if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) { + ruleset.evalImports(env); + } + + // Store the frames around mixin definitions, + // so they can be evaluated like closures when the time comes. + var rsRules = ruleset.rules, rsRuleCnt = rsRules ? rsRules.length : 0; + for (i = 0; i < rsRuleCnt; i++) { + if (rsRules[i] instanceof tree.mixin.Definition || rsRules[i] instanceof tree.DetachedRuleset) { + rsRules[i] = rsRules[i].eval(env); + } + } + + var mediaBlockCount = (env.mediaBlocks && env.mediaBlocks.length) || 0; + + // Evaluate mixin calls. + for (i = 0; i < rsRuleCnt; i++) { + if (rsRules[i] instanceof tree.mixin.Call) { + /*jshint loopfunc:true */ + rules = rsRules[i].eval(env).filter(function (r) { + if ((r instanceof tree.Rule) && r.variable) { + // do not pollute the scope if the variable is + // already there. consider returning false here + // but we need a way to "return" variable from mixins + return !(ruleset.variable(r.name)); + } + return true; + }); + rsRules.splice.apply(rsRules, [i, 1].concat(rules)); + rsRuleCnt += rules.length - 1; + i += rules.length - 1; + ruleset.resetCache(); + } else if (rsRules[i] instanceof tree.RulesetCall) { + /*jshint loopfunc:true */ + rules = rsRules[i].eval(env).rules.filter(function (r) { + if ((r instanceof tree.Rule) && r.variable) { + // do not pollute the scope at all + return false; + } + return true; + }); + rsRules.splice.apply(rsRules, [i, 1].concat(rules)); + rsRuleCnt += rules.length - 1; + i += rules.length - 1; + ruleset.resetCache(); + } + } + + // Evaluate everything else + for (i = 0; i < rsRules.length; i++) { + rule = rsRules[i]; + if (!(rule instanceof tree.mixin.Definition || rule instanceof tree.DetachedRuleset)) { + rsRules[i] = rule = rule.eval ? rule.eval(env) : rule; + } + } + + // Evaluate everything else + for (i = 0; i < rsRules.length; i++) { + rule = rsRules[i]; + // for rulesets, check if it is a css guard and can be removed + if (rule instanceof tree.Ruleset && rule.selectors && rule.selectors.length === 1) { + // check if it can be folded in (e.g. & where) + if (rule.selectors[0].isJustParentSelector()) { + rsRules.splice(i--, 1); + + for (var j = 0; j < rule.rules.length; j++) { + subRule = rule.rules[j]; + if (!(subRule instanceof tree.Rule) || !subRule.variable) { + rsRules.splice(++i, 0, subRule); + } + } + } + } + } + + // Pop the stack + envFrames.shift(); + envSelectors.shift(); + + if (env.mediaBlocks) { + for (i = mediaBlockCount; i < env.mediaBlocks.length; i++) { + env.mediaBlocks[i].bubbleSelectors(selectors); + } + } + + return ruleset; + }, + evalImports: function (env) { + var rules = this.rules, i, importRules; + if (!rules) { + return; + } + + for (i = 0; i < rules.length; i++) { + if (rules[i] instanceof tree.Import) { + importRules = rules[i].eval(env); + if (importRules && importRules.length) { + rules.splice.apply(rules, [i, 1].concat(importRules)); + i += importRules.length - 1; + } else { + rules.splice(i, 1, importRules); + } + this.resetCache(); + } + } + }, + makeImportant: function () { + return new tree.Ruleset(this.selectors, this.rules.map(function (r) { + if (r.makeImportant) { + return r.makeImportant(); + } else { + return r; + } + }), this.strictImports); + }, + matchArgs: function (args) { + return !args || args.length === 0; + }, + // lets you call a css selector with a guard + matchCondition: function (args, env) { + var lastSelector = this.selectors[this.selectors.length - 1]; + if (!lastSelector.evaldCondition) { + return false; + } + if (lastSelector.condition && !lastSelector.condition.eval( + new (tree.evalEnv)(env, + env.frames))) { + return false; + } + return true; + }, + resetCache: function () { + this._rulesets = null; + this._variables = null; + this._lookups = {}; + }, + variables: function () { + if (!this._variables) { + this._variables = !this.rules ? {} : this.rules.reduce(function (hash, r) { + if (r instanceof tree.Rule && r.variable === true) { + hash[r.name] = r; + } + return hash; + }, {}); + } + return this._variables; + }, + variable: function (name) { + return this.variables()[name]; + }, + rulesets: function () { + if (!this.rules) { + return null; + } + + var _Ruleset = tree.Ruleset, _MixinDefinition = tree.mixin.Definition, + filtRules = [], rules = this.rules, cnt = rules.length, + i, rule; + + for (i = 0; i < cnt; i++) { + rule = rules[i]; + if ((rule instanceof _Ruleset) || (rule instanceof _MixinDefinition)) { + filtRules.push(rule); + } + } + + return filtRules; + }, + prependRule: function (rule) { + var rules = this.rules; + if (rules) { + rules.unshift(rule); + } else { + this.rules = [rule]; + } + }, + find: function (selector, self) { + self = self || this; + var rules = [], match, + key = selector.toCSS(); + + if (key in this._lookups) { + return this._lookups[key]; + } + + this.rulesets().forEach(function (rule) { + if (rule !== self) { + for (var j = 0; j < rule.selectors.length; j++) { + match = selector.match(rule.selectors[j]); + if (match) { + if (selector.elements.length > match) { + Array.prototype.push.apply(rules, rule.find( + new (tree.Selector)(selector.elements.slice(match)), self)); + } else { + rules.push(rule); + } + break; + } + } + } + }); + this._lookups[key] = rules; + return rules; + }, + genCSS: function (env, output) { + var i, j, + charsetRuleNodes = [], + ruleNodes = [], + rulesetNodes = [], + rulesetNodeCnt, + debugInfo, // Line number debugging + rule, + path; + + env.tabLevel = (env.tabLevel || 0); + + if (!this.root) { + env.tabLevel++; + } + + var tabRuleStr = env.compress ? '' : Array(env.tabLevel + 1).join(" "), + tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "), + sep; + + function isRulesetLikeNode(rule, root) { + // if it has nested rules, then it should be treated like a ruleset + if (rule.rules) + return true; + + // medias and comments do not have nested rules, but should be treated like rulesets anyway + if ((rule instanceof tree.Media) || (root && rule instanceof tree.Comment)) + return true; + + // some directives and anonumoust nodes are ruleset like, others are not + if ((rule instanceof tree.Directive) || (rule instanceof tree.Anonymous)) { + return rule.isRulesetLike(); + } + + //anything else is assumed to be a rule + return false; + } + + for (i = 0; i < this.rules.length; i++) { + rule = this.rules[i]; + if (isRulesetLikeNode(rule, this.root)) { + rulesetNodes.push(rule); + } else { + //charsets should float on top of everything + if (rule.isCharset && rule.isCharset()) { + charsetRuleNodes.push(rule); + } else { + ruleNodes.push(rule); + } + } + } + ruleNodes = charsetRuleNodes.concat(ruleNodes); + + // If this is the root node, we don't render + // a selector, or {}. + if (!this.root) { + debugInfo = tree.debugInfo(env, this, tabSetStr); + + if (debugInfo) { + output.add(debugInfo); + output.add(tabSetStr); + } + + var paths = this.paths, pathCnt = paths.length, + pathSubCnt; + + sep = env.compress ? ',' : (',\n' + tabSetStr); + + for (i = 0; i < pathCnt; i++) { + path = paths[i]; + if (!(pathSubCnt = path.length)) { + continue; + } + if (i > 0) { + output.add(sep); + } + + env.firstSelector = true; + path[0].genCSS(env, output); + + env.firstSelector = false; + for (j = 1; j < pathSubCnt; j++) { + path[j].genCSS(env, output); + } + } + + output.add((env.compress ? '{' : ' {\n') + tabRuleStr); + } + + // Compile rules and rulesets + for (i = 0; i < ruleNodes.length; i++) { + rule = ruleNodes[i]; + + // @page{ directive ends up with root elements inside it, a mix of rules and rulesets + // In this instance we do not know whether it is the last property + if (i + 1 === ruleNodes.length && (!this.root || rulesetNodes.length === 0 || this.firstRoot)) { + env.lastRule = true; + } + + if (rule.genCSS) { + rule.genCSS(env, output); + } else if (rule.value) { + output.add(rule.value.toString()); + } + + if (!env.lastRule) { + output.add(env.compress ? '' : ('\n' + tabRuleStr)); + } else { + env.lastRule = false; + } + } + + if (!this.root) { + output.add((env.compress ? '}' : '\n' + tabSetStr + '}')); + env.tabLevel--; + } + + sep = (env.compress ? "" : "\n") + (this.root ? tabRuleStr : tabSetStr); + rulesetNodeCnt = rulesetNodes.length; + if (rulesetNodeCnt) { + if (ruleNodes.length && sep) { + output.add(sep); + } + rulesetNodes[0].genCSS(env, output); + for (i = 1; i < rulesetNodeCnt; i++) { + if (sep) { + output.add(sep); + } + rulesetNodes[i].genCSS(env, output); + } + } + + if (!output.isEmpty() && !env.compress && this.firstRoot) { + output.add('\n'); + } + }, + + toCSS: tree.toCSS, + + markReferenced: function () { + if (!this.selectors) { + return; + } + for (var s = 0; s < this.selectors.length; s++) { + this.selectors[s].markReferenced(); + } + }, + + joinSelectors: function (paths, context, selectors) { + for (var s = 0; s < selectors.length; s++) { + this.joinSelector(paths, context, selectors[s]); + } + }, + + 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); + } else { + // the new list of selectors to add + selectorsMultiplied = []; + + // 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); + } + + // 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 (tree.Element)(el.combinator, '', el.index, el.currentFileInfo)); + } + selectorsMultiplied.push(sel); + } + 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; + + //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 (sel.length > 0) { + newSelectorPath = sel.slice(0); + lastSelector = newSelectorPath.pop(); + newJoinedSelector = selector.createDerived(lastSelector.elements.slice(0)); + newJoinedSelectorEmpty = false; + } + else { + newJoinedSelector = selector.createDerived([]); + } + + //put together the parent selectors after the join + if (parentSel.length > 1) { + afterParentJoin = afterParentJoin.concat(parentSel.slice(1)); + } + + if (parentSel.length > 0) { + newJoinedSelectorEmpty = false; + + // join the elements so far with the first part of the parent + newJoinedSelector.elements.push(new (tree.Element)(el.combinator, parentSel[0].elements[0].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 = []; + } + } + + // 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); + } + + for (i = 0; i < newSelectors.length; i++) { + if (newSelectors[i].length > 0) { + paths.push(newSelectors[i]); + } + } + }, + + mergeElementsOnToSelectors: function (elements, selectors) { + var i, sel; + + if (selectors.length === 0) { + selectors.push([new (tree.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 (tree.Selector)(elements)); + } + } + } + }; +})(require('../tree')); + +(function (tree) { + + tree.Selector = function (elements, extendList, condition, index, currentFileInfo, isReferenced) { + this.elements = elements; + this.extendList = extendList; + this.condition = condition; + this.currentFileInfo = currentFileInfo || {}; + this.isReferenced = isReferenced; + if (!condition) { + this.evaldCondition = true; + } + }; + tree.Selector.prototype = { + type: "Selector", + accept: function (visitor) { + if (this.elements) { + this.elements = visitor.visitArray(this.elements); + } + if (this.extendList) { + this.extendList = visitor.visitArray(this.extendList); + } + if (this.condition) { + this.condition = visitor.visit(this.condition); + } + }, + createDerived: function (elements, extendList, evaldCondition) { + evaldCondition = (evaldCondition != null) ? evaldCondition : this.evaldCondition; + var newSelector = new (tree.Selector)(elements, extendList || this.extendList, null, this.index, this.currentFileInfo, this.isReferenced); + newSelector.evaldCondition = evaldCondition; + newSelector.mediaEmpty = this.mediaEmpty; + return newSelector; + }, + match: function (other) { + var elements = this.elements, + len = elements.length, + olen, i; + + other.CacheElements(); + + olen = other._elements.length; + if (olen === 0 || len < olen) { + return 0; + } else { + for (i = 0; i < olen; i++) { + if (elements[i].value !== other._elements[i]) { + return 0; + } + } + } + + return olen; // return number of matched elements + }, + CacheElements: function () { + var css = '', len, v, i; + + if (!this._elements) { + + len = this.elements.length; + for (i = 0; i < len; i++) { + + v = this.elements[i]; + css += v.combinator.value; + + if (!v.value.value) { + css += v.value; + continue; + } + + if (typeof v.value.value !== "string") { + css = ''; + break; + } + css += v.value.value; + } + + this._elements = css.match(/[,&#\*\.\w-]([\w-]|(\\.))*/g); + + if (this._elements) { + if (this._elements[0] === "&") { + this._elements.shift(); + } + + } else { + this._elements = []; + } + + } + }, + isJustParentSelector: function () { + return !this.mediaEmpty && + this.elements.length === 1 && + this.elements[0].value === '&' && + (this.elements[0].combinator.value === ' ' || this.elements[0].combinator.value === ''); + }, + eval: function (env) { + var evaldCondition = this.condition && this.condition.eval(env), + elements = this.elements, extendList = this.extendList; + + elements = elements && elements.map(function (e) { + return e.eval(env); + }); + extendList = extendList && extendList.map(function (extend) { + return extend.eval(env); + }); + + return this.createDerived(elements, extendList, evaldCondition); + }, + genCSS: function (env, output) { + var i, element; + if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") { + output.add(' ', this.currentFileInfo, this.index); + } + if (!this._css) { + //TODO caching? speed comparison? + for (i = 0; i < this.elements.length; i++) { + element = this.elements[i]; + element.genCSS(env, output); + } + } + }, + toCSS: tree.toCSS, + markReferenced: function () { + this.isReferenced = true; + }, + getIsReferenced: function () { + return !this.currentFileInfo.reference || this.isReferenced; + }, + getIsOutput: function () { + return this.evaldCondition; + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.UnicodeDescriptor = function (value) { + this.value = value; + }; + tree.UnicodeDescriptor.prototype = { + type: "UnicodeDescriptor", + genCSS: function (env, output) { + output.add(this.value); + }, + toCSS: tree.toCSS, + eval: function () { + return this; + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.URL = function (val, currentFileInfo, isEvald) { + this.value = val; + this.currentFileInfo = currentFileInfo; + this.isEvald = isEvald; + }; + tree.URL.prototype = { + type: "Url", + accept: function (visitor) { + this.value = visitor.visit(this.value); + }, + genCSS: function (env, output) { + output.add("url("); + this.value.genCSS(env, output); + output.add(")"); + }, + toCSS: tree.toCSS, + eval: function (ctx) { + var val = this.value.eval(ctx), + rootpath; + + if (!this.isEvald) { + // Add the base path if the URL is relative + rootpath = this.currentFileInfo && this.currentFileInfo.rootpath; + if (rootpath && typeof val.value === "string" && ctx.isPathRelative(val.value)) { + if (!val.quote) { + rootpath = rootpath.replace(/[\(\)'"\s]/g, function (match) { + return "\\" + match; + }); + } + val.value = rootpath + val.value; + } + + val.value = ctx.normalizePath(val.value); + + // Add url args if enabled + if (ctx.urlArgs) { + if (!val.value.match(/^\s*data:/)) { + var delimiter = val.value.indexOf('?') === -1 ? '?' : '&'; + var urlArgs = delimiter + ctx.urlArgs; + if (val.value.indexOf('#') !== -1) { + val.value = val.value.replace('#', urlArgs + '#'); + } else { + val.value += urlArgs; + } + } + } + } + + return new (tree.URL)(val, this.currentFileInfo, true); + } + }; + +})(require('../tree')); + +(function (tree) { + + tree.Value = function (value) { + this.value = value; + }; + tree.Value.prototype = { + type: "Value", + accept: function (visitor) { + if (this.value) { + this.value = visitor.visitArray(this.value); + } + }, + eval: function (env) { + if (this.value.length === 1) { + return this.value[0].eval(env); + } else { + return new (tree.Value)(this.value.map(function (v) { + return v.eval(env); + })); + } + }, + genCSS: function (env, output) { + var i; + for (i = 0; i < this.value.length; i++) { + this.value[i].genCSS(env, output); + if (i + 1 < this.value.length) { + output.add((env && env.compress) ? ',' : ', '); + } + } + }, + toCSS: tree.toCSS + }; + +})(require('../tree')); + +(function (tree) { + + tree.Variable = function (name, index, currentFileInfo) { + this.name = name; + this.index = index; + this.currentFileInfo = currentFileInfo || {}; + }; + tree.Variable.prototype = { + type: "Variable", + eval: function (env) { + var variable, name = this.name; + + if (name.indexOf('@@') === 0) { + name = '@' + new (tree.Variable)(name.slice(1)).eval(env).value; + } + + if (this.evaluating) { + throw { + type: 'Name', + message: "Recursive variable definition for " + name, + filename: this.currentFileInfo.file, + index: this.index + }; + } + + this.evaluating = true; + + variable = tree.find(env.frames, function (frame) { + var v = frame.variable(name); + if (v) { + return v.value.eval(env); + } + }); + if (variable) { + this.evaluating = false; + return variable; + } else { + throw { + type: 'Name', + message: "variable " + name + " is undefined", + filename: this.currentFileInfo.filename, + index: this.index + }; + } + } + }; + +})(require('../tree')); + +(function (tree) { + + var parseCopyProperties = [ + 'paths', // option - unmodified - paths to search for imports on + 'optimization', // option - optimization level (for the chunker) + 'files', // list of files that have been imported, used for import-once + 'contents', // map - filename to contents of all the files + 'contentsIgnoredChars', // map - filename to lines at the begining of each file to ignore + 'relativeUrls', // option - whether to adjust URL's to be relative + 'rootpath', // option - rootpath to append to URL's + 'strictImports', // option - + 'insecure', // option - whether to allow imports from insecure ssl hosts + 'dumpLineNumbers', // option - whether to dump line numbers + 'compress', // option - whether to compress + 'processImports', // option - whether to process imports. if false then imports will not be imported + 'syncImport', // option - whether to import synchronously + 'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true + 'mime', // browser only - mime type for sheet import + 'useFileCache', // browser only - whether to use the per file session cache + 'currentFileInfo' // information about the current file - for error reporting and importing and making urls relative etc. + ]; + + //currentFileInfo = { + // 'relativeUrls' - option - whether to adjust URL's to be relative + // 'filename' - full resolved filename of current file + // 'rootpath' - path to append to normal URLs for this node + // 'currentDirectory' - path to the current file, absolute + // 'rootFilename' - filename of the base file + // 'entryPath' - absolute path to the entry file + // 'reference' - whether the file should not be output and only output parts that are referenced + + tree.parseEnv = function (options) { + copyFromOriginal(options, this, parseCopyProperties); + + if (!this.contents) { + this.contents = {}; + } + if (!this.contentsIgnoredChars) { + this.contentsIgnoredChars = {}; + } + if (!this.files) { + this.files = {}; + } + + if (typeof this.paths === "string") { + this.paths = [this.paths]; + } + + if (!this.currentFileInfo) { + var filename = (options && options.filename) || "input"; + var entryPath = filename.replace(/[^\/\\]*$/, ""); + if (options) { + options.filename = null; + } + this.currentFileInfo = { + filename: filename, + relativeUrls: this.relativeUrls, + rootpath: (options && options.rootpath) || "", + currentDirectory: entryPath, + entryPath: entryPath, + rootFilename: filename + }; + } + }; + + var evalCopyProperties = [ + 'silent', // whether to swallow errors and warnings + 'verbose', // whether to log more activity + 'compress', // whether to compress + 'yuicompress', // whether to compress with the outside tool yui compressor + 'ieCompat', // whether to enforce IE compatibility (IE8 data-uri) + 'strictMath', // whether math has to be within parenthesis + 'strictUnits', // whether units need to evaluate correctly + 'cleancss', // whether to compress with clean-css + 'sourceMap', // whether to output a source map + 'importMultiple', // whether we are currently importing multiple copies + 'urlArgs' // whether to add args into url tokens + ]; + + tree.evalEnv = function (options, frames) { + copyFromOriginal(options, this, evalCopyProperties); + + this.frames = frames || []; + }; + + tree.evalEnv.prototype.inParenthesis = function () { + if (!this.parensStack) { + this.parensStack = []; + } + this.parensStack.push(true); + }; + + tree.evalEnv.prototype.outOfParenthesis = function () { + this.parensStack.pop(); + }; + + tree.evalEnv.prototype.isMathOn = function () { + return this.strictMath ? (this.parensStack && this.parensStack.length) : true; + }; + + tree.evalEnv.prototype.isPathRelative = function (path) { + return !/^(?:[a-z-]+:|\/)/.test(path); + }; + + tree.evalEnv.prototype.normalizePath = function (path) { + var + segments = path.split("/").reverse(), + segment; + + path = []; + while (segments.length !== 0) { + segment = segments.pop(); + switch (segment) { + case ".": + break; + case "..": + if ((path.length === 0) || (path[path.length - 1] === "..")) { + path.push(segment); + } else { + path.pop(); + } + break; + default: + path.push(segment); + break; + } + } + + return path.join("/"); + }; + + //todo - do the same for the toCSS env + //tree.toCSSEnv = function (options) { + //}; + + var copyFromOriginal = function (original, destination, propertiesToCopy) { + if (!original) { + return; + } + + for (var i = 0; i < propertiesToCopy.length; i++) { + if (original.hasOwnProperty(propertiesToCopy[i])) { + destination[propertiesToCopy[i]] = original[propertiesToCopy[i]]; + } + } + }; + +})(require('./tree')); + +(function (tree) { + + var _visitArgs = {visitDeeper: true}, + _hasIndexed = false; + + function _noop(node) { + return node; + } + + function indexNodeTypes(parent, ticker) { + // add .typeIndex to tree node types for lookup table + var key, child; + for (key in parent) { + if (parent.hasOwnProperty(key)) { + child = parent[key]; + switch (typeof child) { + case "function": + // ignore bound functions directly on tree which do not have a prototype + // or aren't nodes + if (child.prototype && child.prototype.type) { + child.prototype.typeIndex = ticker++; + } + break; + case "object": + ticker = indexNodeTypes(child, ticker); + break; + } + } + } + return ticker; + } + + tree.visitor = function (implementation) { + this._implementation = implementation; + this._visitFnCache = []; + + if (!_hasIndexed) { + indexNodeTypes(tree, 1); + _hasIndexed = true; + } + }; + + tree.visitor.prototype = { + visit: function (node) { + if (!node) { + return node; + } + + var nodeTypeIndex = node.typeIndex; + if (!nodeTypeIndex) { + return node; + } + + var visitFnCache = this._visitFnCache, + impl = this._implementation, + aryIndx = nodeTypeIndex << 1, + outAryIndex = aryIndx | 1, + func = visitFnCache[aryIndx], + funcOut = visitFnCache[outAryIndex], + visitArgs = _visitArgs, + fnName; + + visitArgs.visitDeeper = true; + + if (!func) { + fnName = "visit" + node.type; + func = impl[fnName] || _noop; + funcOut = impl[fnName + "Out"] || _noop; + visitFnCache[aryIndx] = func; + visitFnCache[outAryIndex] = funcOut; + } + + if (func !== _noop) { + var newNode = func.call(impl, node, visitArgs); + if (impl.isReplacing) { + node = newNode; + } + } + + if (visitArgs.visitDeeper && node && node.accept) { + node.accept(this); + } + + if (funcOut != _noop) { + funcOut.call(impl, node); + } + + return node; + }, + visitArray: function (nodes, nonReplacing) { + if (!nodes) { + return nodes; + } + + var cnt = nodes.length, i; + + // Non-replacing + if (nonReplacing || !this._implementation.isReplacing) { + for (i = 0; i < cnt; i++) { + this.visit(nodes[i]); + } + return nodes; + } + + // Replacing + var out = []; + for (i = 0; i < cnt; i++) { + var evald = this.visit(nodes[i]); + if (!evald.splice) { + out.push(evald); + } else if (evald.length) { + this.flatten(evald, out); + } + } + return out; + }, + flatten: function (arr, out) { + if (!out) { + out = []; + } + + var cnt, i, item, + nestedCnt, j, nestedItem; + + for (i = 0, cnt = arr.length; i < cnt; i++) { + item = arr[i]; + if (!item.splice) { + out.push(item); + continue; + } + + for (j = 0, nestedCnt = item.length; j < nestedCnt; j++) { + nestedItem = item[j]; + if (!nestedItem.splice) { + out.push(nestedItem); + } else if (nestedItem.length) { + this.flatten(nestedItem, out); + } + } + } + + return out; + } + }; + +})(require('./tree')); +(function (tree) { + tree.importVisitor = function (importer, finish, evalEnv, onceFileDetectionMap, recursionDetector) { + this._visitor = new tree.visitor(this); + this._importer = importer; + this._finish = finish; + this.env = evalEnv || new tree.evalEnv(); + this.importCount = 0; + this.onceFileDetectionMap = onceFileDetectionMap || {}; + this.recursionDetector = {}; + if (recursionDetector) { + for (var fullFilename in recursionDetector) { + if (recursionDetector.hasOwnProperty(fullFilename)) { + this.recursionDetector[fullFilename] = true; + } + } + } + }; + + tree.importVisitor.prototype = { + isReplacing: true, + run: function (root) { + var error; + try { + // process the contents + this._visitor.visit(root); + } + catch (e) { + error = e; + } + + this.isFinished = true; + + if (this.importCount === 0) { + this._finish(error); + } + }, + visitImport: function (importNode, visitArgs) { + var importVisitor = this, + evaldImportNode, + inlineCSS = importNode.options.inline; + + if (!importNode.css || inlineCSS) { + + try { + evaldImportNode = importNode.evalForImport(this.env); + } 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)) { + importNode = evaldImportNode; + this.importCount++; + var env = new tree.evalEnv(this.env, this.env.frames.slice(0)); + + if (importNode.options.multiple) { + env.importMultiple = true; + } + + this._importer.push(importNode.getPath(), 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 (!env.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 && (env.importMultiple || !duplicateImport)) { + importVisitor.recursionDetector[fullPath] = true; + new (tree.importVisitor)(importVisitor._importer, subFinish, env, importVisitor.onceFileDetectionMap, importVisitor.recursionDetector) + .run(root); + return; + } + } + + subFinish(); + }); + } + } + visitArgs.visitDeeper = false; + return importNode; + }, + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + return ruleNode; + }, + visitDirective: function (directiveNode, visitArgs) { + this.env.frames.unshift(directiveNode); + return directiveNode; + }, + visitDirectiveOut: function (directiveNode) { + this.env.frames.shift(); + }, + visitMixinDefinition: function (mixinDefinitionNode, visitArgs) { + this.env.frames.unshift(mixinDefinitionNode); + return mixinDefinitionNode; + }, + visitMixinDefinitionOut: function (mixinDefinitionNode) { + this.env.frames.shift(); + }, + visitRuleset: function (rulesetNode, visitArgs) { + this.env.frames.unshift(rulesetNode); + return rulesetNode; + }, + visitRulesetOut: function (rulesetNode) { + this.env.frames.shift(); + }, + visitMedia: function (mediaNode, visitArgs) { + this.env.frames.unshift(mediaNode.rules[0]); + return mediaNode; + }, + visitMediaOut: function (mediaNode) { + this.env.frames.shift(); + } + }; + +})(require('./tree')); +(function (tree) { + tree.joinSelectorVisitor = function () { + this.contexts = [[]]; + this._visitor = new tree.visitor(this); + }; + + tree.joinSelectorVisitor.prototype = { + run: function (root) { + return this._visitor.visit(root); + }, + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitMixinDefinition: function (mixinDefinitionNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + + visitRuleset: function (rulesetNode, visitArgs) { + var context = this.contexts[this.contexts.length - 1], + paths = [], selectors; + + this.contexts.push(paths); + + if (!rulesetNode.root) { + selectors = rulesetNode.selectors; + if (selectors) { + selectors = selectors.filter(function (selector) { + return selector.getIsOutput(); + }); + rulesetNode.selectors = selectors.length ? selectors : (selectors = null); + if (selectors) { + rulesetNode.joinSelectors(paths, context, selectors); + } + } + if (!selectors) { + rulesetNode.rules = null; + } + rulesetNode.paths = paths; + } + }, + visitRulesetOut: function (rulesetNode) { + this.contexts.length = this.contexts.length - 1; + }, + visitMedia: function (mediaNode, visitArgs) { + var context = this.contexts[this.contexts.length - 1]; + mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia); + } + }; + +})(require('./tree')); +(function (tree) { + tree.toCSSVisitor = function (env) { + this._visitor = new tree.visitor(this); + this._env = env; + }; + + tree.toCSSVisitor.prototype = { + isReplacing: true, + run: function (root) { + return this._visitor.visit(root); + }, + + visitRule: function (ruleNode, visitArgs) { + if (ruleNode.variable) { + return []; + } + return ruleNode; + }, + + visitMixinDefinition: function (mixinNode, visitArgs) { + // 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._env)) { + return []; + } + return commentNode; + }, + + visitMedia: function (mediaNode, visitArgs) { + mediaNode.accept(this._visitor); + visitArgs.visitDeeper = false; + + if (!mediaNode.rules.length) { + return []; + } + return mediaNode; + }, + + visitDirective: function (directiveNode, visitArgs) { + if (directiveNode.currentFileInfo.reference && !directiveNode.isReferenced) { + return []; + } + if (directiveNode.name === "@charset") { + // 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._env).replace(/\n/g, "") + " */\n"); + comment.debugInfo = directiveNode.debugInfo; + return this._visitor.visit(comment); + } + return []; + } + this.charset = true; + } + if (directiveNode.rules && directiveNode.rules.rules) { + this._mergeRules(directiveNode.rules.rules); + } + return directiveNode; + }, + + checkPropertiesInRoot: function (rules) { + var ruleNode; + 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.", + index: ruleNode.index, + filename: ruleNode.currentFileInfo ? ruleNode.currentFileInfo.filename : null + }; + } + } + }, + + visitRuleset: function (rulesetNode, visitArgs) { + var rule, rulesets = []; + if (rulesetNode.firstRoot) { + this.checkPropertiesInRoot(rulesetNode.rules); + } + if (!rulesetNode.root) { + if (rulesetNode.paths) { + rulesetNode.paths = rulesetNode.paths + .filter(function (p) { + var i; + if (p[0].elements[0].combinator.value === ' ') { + p[0].elements[0].combinator = new (tree.Combinator)(''); + } + for (i = 0; i < p.length; i++) { + if (p[i].getIsReferenced() && p[i].getIsOutput()) { + return true; + } + } + return false; + }); + } + + // Compile rules and rulesets + var nodeRules = rulesetNode.rules, nodeRuleCnt = nodeRules ? nodeRules.length : 0; + for (var i = 0; i < nodeRuleCnt;) { + rule = nodeRules[i]; + if (rule && rule.rules) { + // visit because we are moving them out from being a child + rulesets.push(this._visitor.visit(rule)); + nodeRules.splice(i, 1); + nodeRuleCnt--; + continue; + } + i++; + } + // accept the visitor to remove rules and refactor itself + // then we can decide now whether we want it or not + if (nodeRuleCnt > 0) { + rulesetNode.accept(this._visitor); + } else { + rulesetNode.rules = null; + } + visitArgs.visitDeeper = false; + + nodeRules = rulesetNode.rules; + if (nodeRules) { + this._mergeRules(nodeRules); + nodeRules = rulesetNode.rules; + } + if (nodeRules) { + this._removeDuplicateRules(nodeRules); + nodeRules = rulesetNode.rules; + } + + // now decide whether we keep the ruleset + if (nodeRules && nodeRules.length > 0 && rulesetNode.paths.length > 0) { + rulesets.splice(0, 0, rulesetNode); + } + } else { + rulesetNode.accept(this._visitor); + visitArgs.visitDeeper = false; + if (rulesetNode.firstRoot || (rulesetNode.rules && rulesetNode.rules.length > 0)) { + rulesets.splice(0, 0, rulesetNode); + } + } + if (rulesets.length === 1) { + return rulesets[0]; + } + return rulesets; + }, + + _removeDuplicateRules: function (rules) { + if (!rules) { + return; + } + + // remove duplicates + var ruleCache = {}, + ruleList, rule, i; + + for (i = rules.length - 1; i >= 0; i--) { + rule = rules[i]; + if (rule instanceof tree.Rule) { + if (!ruleCache[rule.name]) { + ruleCache[rule.name] = rule; + } else { + ruleList = ruleCache[rule.name]; + if (ruleList instanceof tree.Rule) { + ruleList = ruleCache[rule.name] = [ruleCache[rule.name].toCSS(this._env)]; + } + var ruleCSS = rule.toCSS(this._env); + if (ruleList.indexOf(ruleCSS) !== -1) { + rules.splice(i, 1); + } else { + ruleList.push(ruleCSS); + } + } + } + } + }, + + _mergeRules: function (rules) { + if (!rules) { + return; + } + + var groups = {}, + parts, + rule, + key; + + for (var i = 0; i < rules.length; i++) { + rule = rules[i]; + + if ((rule instanceof tree.Rule) && rule.merge) { + key = [rule.name, + rule.important ? "!" : ""].join(","); + + if (!groups[key]) { + groups[key] = []; + } else { + rules.splice(i--, 1); + } + + groups[key].push(rule); + } + } + + Object.keys(groups).map(function (k) { + + function toExpression(values) { + return new (tree.Expression)(values.map(function (p) { + return p.value; + })); + } + + function toValue(values) { + return new (tree.Value)(values.map(function (p) { + return p; + })); + } + + parts = groups[k]; + + if (parts.length > 1) { + rule = parts[0]; + var spacedGroups = []; + var lastSpacedGroup = []; + parts.map(function (p) { + if (p.merge === "+") { + if (lastSpacedGroup.length > 0) { + spacedGroups.push(toExpression(lastSpacedGroup)); + } + lastSpacedGroup = []; + } + lastSpacedGroup.push(p); + }); + spacedGroups.push(toExpression(lastSpacedGroup)); + rule.value = toValue(spacedGroups); + } + }); + } + }; + +})(require('./tree')); +(function (tree) { + /*jshint loopfunc:true */ + + tree.extendFinderVisitor = function () { + this._visitor = new tree.visitor(this); + this.contexts = []; + this.allExtendsStack = [[]]; + }; + + tree.extendFinderVisitor.prototype = { + run: function (root) { + root = this._visitor.visit(root); + root.allExtends = this.allExtendsStack[0]; + return root; + }, + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitMixinDefinition: function (mixinDefinitionNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitRuleset: function (rulesetNode, visitArgs) { + if (rulesetNode.root) { + return; + } + + var i, j, extend, allSelectorsExtendList = [], extendList; + + // 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++) { + if (rulesetNode.rules[i] instanceof tree.Extend) { + allSelectorsExtendList.push(rules[i]); + rulesetNode.extendOnEveryPath = true; + } + } + + // 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++) { + var selectorPath = paths[i], + selector = selectorPath[selectorPath.length - 1], + selExtendList = selector.extendList; + + extendList = selExtendList ? selExtendList.slice(0).concat(allSelectorsExtendList) + : allSelectorsExtendList; + + if (extendList) { + extendList = extendList.map(function (allSelectorsExtend) { + return allSelectorsExtend.clone(); + }); + } + + 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.contexts.push(rulesetNode.selectors); + }, + visitRulesetOut: function (rulesetNode) { + if (!rulesetNode.root) { + this.contexts.length = this.contexts.length - 1; + } + }, + visitMedia: function (mediaNode, visitArgs) { + mediaNode.allExtends = []; + this.allExtendsStack.push(mediaNode.allExtends); + }, + visitMediaOut: function (mediaNode) { + this.allExtendsStack.length = this.allExtendsStack.length - 1; + }, + visitDirective: function (directiveNode, visitArgs) { + directiveNode.allExtends = []; + this.allExtendsStack.push(directiveNode.allExtends); + }, + visitDirectiveOut: function (directiveNode) { + this.allExtendsStack.length = this.allExtendsStack.length - 1; + } + }; + + tree.processExtendsVisitor = function () { + this._visitor = new tree.visitor(this); + }; + + tree.processExtendsVisitor.prototype = { + run: function (root) { + var extendFinder = new tree.extendFinderVisitor(); + 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); + }, + 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 + // 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 + + var extendIndex, targetExtendIndex, matches, extendsToAdd = [], newSelector, extendVisitor = this, selectorPath, extend, targetExtend, newExtend; + + iterationCount = iterationCount || 0; + + //loop through comparing every extend with every target extend. + // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place + // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one + // 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++) { + + extend = extendsList[extendIndex]; + targetExtend = extendsListTarget[targetExtendIndex]; + + // look for circular references + 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]]; + matches = extendVisitor.findMatch(extend, selectorPath); + + if (matches.length) { + + // we found a match, so for each self selector.. + extend.selfSelectors.forEach(function (selfSelector) { + + // process the extend as usual + newSelector = extendVisitor.extendSelector(matches, selectorPath, selfSelector); + + // but now we create a new extend from it + newExtend = new (tree.Extend)(targetExtend.selector, targetExtend.option, 0); + newExtend.selfSelectors = newSelector; + + // add the extend onto the list of extends for that selector + newSelector[newSelector.length - 1].extendList = [newExtend]; + + // record that we need to add it. + extendsToAdd.push(newExtend); + newExtend.ruleset = targetExtend.ruleset; + + //remember its parents for circular references + newExtend.parent_ids = newExtend.parent_ids.concat(targetExtend.parent_ids, extend.parent_ids); + + // only process the selector once.. if we have :extend(.a,.b) then multiple + // extends will look at the same selector path, so when extending + // we know that any others will be duplicates in terms of what is added to the css + if (targetExtend.firstExtendOnThisSelectorPath) { + newExtend.firstExtendOnThisSelectorPath = true; + targetExtend.ruleset.paths.push(newSelector); + } + }); + } + } + } + + if (extendsToAdd.length) { + // try to detect circular references to stop a stack overflow. + // may no longer be needed. + this.extendChainCount++; + if (iterationCount > 100) { + var selectorOne = "{unable to calculate}"; + var selectorTwo = "{unable to calculate}"; + try { + selectorOne = extendsToAdd[0].selfSelectors[0].toCSS(); + selectorTwo = extendsToAdd[0].selector.toCSS(); + } + catch (e) { + } + 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)); + } else { + return extendsToAdd; + } + }, + visitRule: function (ruleNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitMixinDefinition: function (mixinDefinitionNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitSelector: function (selectorNode, visitArgs) { + visitArgs.visitDeeper = false; + }, + visitRuleset: function (rulesetNode, visitArgs) { + if (rulesetNode.root) { + return; + } + 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++) { + selectorPath = rulesetNode.paths[pathIndex]; + + // extending extends happens initially, before the main pass + if (rulesetNode.extendOnEveryPath) { + continue; + } + var extendList = selectorPath[selectorPath.length - 1].extendList; + if (extendList && extendList.length) { + continue; + } + + matches = this.findMatch(allExtends[extendIndex], selectorPath); + + if (matches.length) { + + allExtends[extendIndex].selfSelectors.forEach(function (selfSelector) { + selectorsToAdd.push(extendVisitor.extendSelector(matches, selectorPath, selfSelector)); + }); + } + } + } + rulesetNode.paths = rulesetNode.paths.concat(selectorsToAdd); + }, + findMatch: function (extend, haystackSelectorPath) { + // + // look through the haystack selector path to try and find the needle - extend.selector + // returns an array of selector matches that can then be replaced + // + var haystackSelectorIndex, hackstackSelector, hackstackElementIndex, haystackElement, + targetCombinator, i, + extendVisitor = this, + needleElements = extend.selector.elements, + potentialMatches = [], potentialMatch, matches = []; + + // loop through the haystack elements + for (haystackSelectorIndex = 0; haystackSelectorIndex < haystackSelectorPath.length; haystackSelectorIndex++) { + hackstackSelector = haystackSelectorPath[haystackSelectorIndex]; + + 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 + }); + } + + 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 + targetCombinator = haystackElement.combinator.value; + if (targetCombinator === '' && hackstackElementIndex === 0) { + targetCombinator = ' '; + } + + // if we don't match, null our match to indicate failure + if (!extendVisitor.isElementValuesEqual(needleElements[potentialMatch.matched].value, haystackElement.value) || + (potentialMatch.matched > 0 && needleElements[potentialMatch.matched].combinator.value !== targetCombinator)) { + potentialMatch = null; + } else { + potentialMatch.matched++; + } + + // if we are still valid and have finished, test whether we have elements after and whether these are allowed + if (potentialMatch) { + potentialMatch.finished = potentialMatch.matched === needleElements.length; + if (potentialMatch.finished && + (!extend.allowAfter && (hackstackElementIndex + 1 < hackstackSelector.elements.length || haystackSelectorIndex + 1 < haystackSelectorPath.length))) { + potentialMatch = null; + } + } + // if null we remove, if not, we are still valid, so either push as a valid match or continue + if (potentialMatch) { + if (potentialMatch.finished) { + potentialMatch.length = needleElements.length; + potentialMatch.endPathIndex = haystackSelectorIndex; + potentialMatch.endPathElementIndex = hackstackElementIndex + 1; // index after end of match + potentialMatches.length = 0; // we don't allow matches to overlap, so start matching again + matches.push(potentialMatch); + } + } else { + potentialMatches.splice(i, 1); + i--; + } + } + } + } + return matches; + }, + isElementValuesEqual: function (elementValue1, elementValue2) { + if (typeof elementValue1 === "string" || typeof elementValue2 === "string") { + return elementValue1 === elementValue2; + } + if (elementValue1 instanceof tree.Attribute) { + if (elementValue1.op !== elementValue2.op || elementValue1.key !== elementValue2.key) { + return false; + } + if (!elementValue1.value || !elementValue2.value) { + if (elementValue1.value || elementValue2.value) { + return false; + } + return true; + } + elementValue1 = elementValue1.value.value || elementValue1.value; + elementValue2 = elementValue2.value.value || elementValue2.value; + return elementValue1 === elementValue2; + } + elementValue1 = elementValue1.value; + elementValue2 = elementValue2.value; + if (elementValue1 instanceof tree.Selector) { + if (!(elementValue2 instanceof tree.Selector) || elementValue1.elements.length !== elementValue2.elements.length) { + return false; + } + 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; + } + } + if (!this.isElementValuesEqual(elementValue1.elements[i].value, elementValue2.elements[i].value)) { + return false; + } + } + return true; + } + return false; + }, + extendSelector: function (matches, selectorPath, replacementSelector) { + + //for a set of matches, replace each match with the replacement selector + + var currentSelectorPathIndex = 0, + currentSelectorPathElementIndex = 0, + path = [], + matchIndex, + selector, + firstElement, + match, + newElements; + + for (matchIndex = 0; matchIndex < matches.length; matchIndex++) { + match = matches[matchIndex]; + selector = selectorPath[match.pathIndex]; + firstElement = new tree.Element( + match.initialCombinator, + replacementSelector.elements[0].value, + replacementSelector.elements[0].index, + replacementSelector.elements[0].currentFileInfo + ); + + if (match.pathIndex > currentSelectorPathIndex && currentSelectorPathElementIndex > 0) { + path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex)); + currentSelectorPathElementIndex = 0; + currentSelectorPathIndex++; + } + + newElements = selector.elements + .slice(currentSelectorPathElementIndex, match.index) + .concat([firstElement]) + .concat(replacementSelector.elements.slice(1)); + + if (currentSelectorPathIndex === match.pathIndex && matchIndex > 0) { + path[path.length - 1].elements = + path[path.length - 1].elements.concat(newElements); + } else { + path = path.concat(selectorPath.slice(currentSelectorPathIndex, match.pathIndex)); + + path.push(new tree.Selector( + newElements + )); + } + currentSelectorPathIndex = match.endPathIndex; + currentSelectorPathElementIndex = match.endPathElementIndex; + if (currentSelectorPathElementIndex >= selectorPath[currentSelectorPathIndex].elements.length) { + currentSelectorPathElementIndex = 0; + currentSelectorPathIndex++; + } + } + + if (currentSelectorPathIndex < selectorPath.length && currentSelectorPathElementIndex > 0) { + path[path.length - 1].elements = path[path.length - 1].elements.concat(selectorPath[currentSelectorPathIndex].elements.slice(currentSelectorPathElementIndex)); + currentSelectorPathIndex++; + } + + path = path.concat(selectorPath.slice(currentSelectorPathIndex, selectorPath.length)); + + return path; + }, + visitRulesetOut: function (rulesetNode) { + }, + visitMedia: function (mediaNode, visitArgs) { + 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; + }, + visitDirective: function (directiveNode, visitArgs) { + 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; + } + }; + +})(require('./tree')); + +(function (tree) { + + tree.sourceMapOutput = function (options) { + this._css = []; + this._rootNode = options.rootNode; + this._writeSourceMap = options.writeSourceMap; + this._contentsMap = options.contentsMap; + this._contentsIgnoredCharsMap = options.contentsIgnoredCharsMap; + this._sourceMapFilename = options.sourceMapFilename; + this._outputFilename = options.outputFilename; + this._sourceMapURL = options.sourceMapURL; + if (options.sourceMapBasepath) { + this._sourceMapBasepath = options.sourceMapBasepath.replace(/\\/g, '/'); + } + this._sourceMapRootpath = options.sourceMapRootpath; + this._outputSourceFiles = options.outputSourceFiles; + this._sourceMapGeneratorConstructor = options.sourceMapGenerator || require("source-map").SourceMapGenerator; + + if (this._sourceMapRootpath && this._sourceMapRootpath.charAt(this._sourceMapRootpath.length - 1) !== '/') { + this._sourceMapRootpath += '/'; + } + + this._lineNumber = 0; + this._column = 0; + }; + + tree.sourceMapOutput.prototype.normalizeFilename = function (filename) { + filename = filename.replace(/\\/g, '/'); + + 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); + } + } + return (this._sourceMapRootpath || "") + filename; + }; + + tree.sourceMapOutput.prototype.add = function (chunk, fileInfo, index, mapLines) { + + //ignore adding empty strings + if (!chunk) { + return; + } + + var lines, + sourceLines, + columns, + sourceColumns, + i; + + if (fileInfo) { + var inputSource = this._contentsMap[fileInfo.filename]; + + // remove vars/banner added to the top of the file + if (this._contentsIgnoredCharsMap[fileInfo.filename]) { + // adjust the index + index -= this._contentsIgnoredCharsMap[fileInfo.filename]; + if (index < 0) { + index = 0; + } + // adjust the source + inputSource = inputSource.slice(this._contentsIgnoredCharsMap[fileInfo.filename]); + } + inputSource = inputSource.substring(0, index); + sourceLines = inputSource.split("\n"); + sourceColumns = sourceLines[sourceLines.length - 1]; + } + + lines = chunk.split("\n"); + columns = lines[lines.length - 1]; + + if (fileInfo) { + if (!mapLines) { + this._sourceMapGenerator.addMapping({ + generated: {line: this._lineNumber + 1, column: this._column}, + original: {line: sourceLines.length, column: sourceColumns.length}, + source: this.normalizeFilename(fileInfo.filename) + }); + } else { + 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) + }); + } + } + } + + if (lines.length === 1) { + this._column += columns.length; + } else { + this._lineNumber += lines.length - 1; + this._column = columns.length; + } + + this._css.push(chunk); + }; + + tree.sourceMapOutput.prototype.isEmpty = function () { + return this._css.length === 0; + }; + + tree.sourceMapOutput.prototype.toCSS = function (env) { + this._sourceMapGenerator = new this._sourceMapGeneratorConstructor({ + file: this._outputFilename, + sourceRoot: null + }); + + if (this._outputSourceFiles) { + for (var filename in this._contentsMap) { + if (this._contentsMap.hasOwnProperty(filename)) { + var source = this._contentsMap[filename]; + if (this._contentsIgnoredCharsMap[filename]) { + source = source.slice(this._contentsIgnoredCharsMap[filename]); + } + this._sourceMapGenerator.setSourceContent(this.normalizeFilename(filename), source); + } + } + } + + this._rootNode.genCSS(env, this); + + if (this._css.length > 0) { + var sourceMapURL, + sourceMapContent = JSON.stringify(this._sourceMapGenerator.toJSON()); + + if (this._sourceMapURL) { + sourceMapURL = this._sourceMapURL; + } else if (this._sourceMapFilename) { + sourceMapURL = this.normalizeFilename(this._sourceMapFilename); + } + + if (this._writeSourceMap) { + this._writeSourceMap(sourceMapContent); + } else { + sourceMapURL = "data:application/json;base64," + require('./encoder.js').encodeBase64(sourceMapContent); + } + + if (sourceMapURL) { + this._css.push("/*# sourceMappingURL=" + sourceMapURL + " */"); + } + } + + return this._css.join(''); + }; + +})(require('./tree')); + +// wraps the source-map code in a less module +(function () { + less.modules["source-map"] = function () { + + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + + /** + * Define a module along with a payload. + * @param {string} moduleName Name for the payload + * @param {ignored} deps Ignored. For compatibility with CommonJS AMD Spec + * @param {function} payload Function with (require, exports, module) params + */ + function define(moduleName, deps, payload) { + if (typeof moduleName != "string") { + throw new TypeError('Expected string, got: ' + moduleName); + } + + if (arguments.length == 2) { + payload = deps; + } + + if (moduleName in define.modules) { + throw new Error("Module already defined: " + moduleName); + } + define.modules[moduleName] = payload; + }; + + /** + * The global store of un-instantiated modules + */ + define.modules = {}; + + + /** + * We invoke require() in the context of a Domain so we can have multiple + * sets of modules running separate from each other. + * This contrasts with JSMs which are singletons, Domains allows us to + * optionally load a CommonJS module twice with separate data each time. + * Perhaps you want 2 command lines with a different set of commands in each, + * for example. + */ + function Domain() { + this.modules = {}; + this._currentModule = null; + } + + (function () { + + /** + * Lookup module names and resolve them by calling the definition function if + * needed. + * There are 2 ways to call this, either with an array of dependencies and a + * callback to call when the dependencies are found (which can happen + * asynchronously in an in-page context) or with a single string an no callback + * where the dependency is resolved synchronously and returned. + * The API is designed to be compatible with the CommonJS AMD spec and + * RequireJS. + * @param {string[]|string} deps A name, or names for the payload + * @param {function|undefined} callback Function to call when the dependencies + * are resolved + * @return {undefined|object} The module required or undefined for + * array/callback method + */ + Domain.prototype.require = function (deps, callback) { + if (Array.isArray(deps)) { + var params = deps.map(function (dep) { + return this.lookup(dep); + }, this); + if (callback) { + callback.apply(null, params); + } + return undefined; + } + else { + return this.lookup(deps); + } + }; + + function normalize(path) { + var bits = path.split('/'); + var i = 1; + while (i < bits.length) { + if (bits[i] === '..') { + bits.splice(i - 1, 1); + } else if (bits[i] === '.') { + bits.splice(i, 1); + } else { + i++; + } + } + return bits.join('/'); + } + + function join(a, b) { + a = a.trim(); + b = b.trim(); + if (/^\//.test(b)) { + return b; + } else { + return a.replace(/\/*$/, '/') + b; + } + } + + function dirname(path) { + var bits = path.split('/'); + bits.pop(); + return bits.join('/'); + } + + /** + * Lookup module names and resolve them by calling the definition function if + * needed. + * @param {string} moduleName A name for the payload to lookup + * @return {object} The module specified by aModuleName or null if not found. + */ + Domain.prototype.lookup = function (moduleName) { + if (/^\./.test(moduleName)) { + moduleName = normalize(join(dirname(this._currentModule), moduleName)); + } + + if (moduleName in this.modules) { + var module = this.modules[moduleName]; + return module; + } + + if (!(moduleName in define.modules)) { + throw new Error("Module not defined: " + moduleName); + } + + var module = define.modules[moduleName]; + + if (typeof module == "function") { + var exports = {}; + var previousModule = this._currentModule; + this._currentModule = moduleName; + module(this.require.bind(this), exports, {id: moduleName, uri: ""}); + this._currentModule = previousModule; + module = exports; + } + + // cache the resulting module object for next time + this.modules[moduleName] = module; + + return module; + }; + + }()); + + define.Domain = Domain; + define.globalDomain = new Domain(); + var require = define.globalDomain.require.bind(define.globalDomain); + /* -*- Mode: js; js-indent-level: 2; -*- */ + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + define('source-map/source-map-generator', ['require', 'exports', 'module', 'source-map/base64-vlq', 'source-map/util', 'source-map/array-set'], function (require, exports, module) { + + var base64VLQ = require('./base64-vlq'); + var util = require('./util'); + var ArraySet = require('./array-set').ArraySet; + + /** + * An instance of the SourceMapGenerator represents a source map which is + * being built incrementally. To create a new one, you must pass an object + * with the following properties: + * + * - file: The filename of the generated source. + * - sourceRoot: An optional root for all URLs in this source map. + */ + function SourceMapGenerator(aArgs) { + this._file = util.getArg(aArgs, 'file'); + this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); + this._sources = new ArraySet(); + this._names = new ArraySet(); + this._mappings = []; + this._sourcesContents = null; + } + + SourceMapGenerator.prototype._version = 3; + + /** + * Creates a new SourceMapGenerator based on a SourceMapConsumer + * + * @param aSourceMapConsumer The SourceMap. + */ + SourceMapGenerator.fromSourceMap = + function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) { + var sourceRoot = aSourceMapConsumer.sourceRoot; + var generator = new SourceMapGenerator({ + file: aSourceMapConsumer.file, + sourceRoot: sourceRoot + }); + aSourceMapConsumer.eachMapping(function (mapping) { + var newMapping = { + generated: { + line: mapping.generatedLine, + column: mapping.generatedColumn + } + }; + + if (mapping.source) { + newMapping.source = mapping.source; + if (sourceRoot) { + newMapping.source = util.relative(sourceRoot, newMapping.source); + } + + newMapping.original = { + line: mapping.originalLine, + column: mapping.originalColumn + }; + + if (mapping.name) { + newMapping.name = mapping.name; + } + } + + generator.addMapping(newMapping); + }); + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content) { + generator.setSourceContent(sourceFile, content); + } + }); + return generator; + }; + + /** + * Add a single mapping from original source line and column to the generated + * source's line and column for this source map being created. The mapping + * object should have the following properties: + * + * - generated: An object with the generated line and column positions. + * - original: An object with the original line and column positions. + * - source: The original source file (relative to the sourceRoot). + * - name: An optional original token name for this mapping. + */ + SourceMapGenerator.prototype.addMapping = + function SourceMapGenerator_addMapping(aArgs) { + var generated = util.getArg(aArgs, 'generated'); + var original = util.getArg(aArgs, 'original', null); + var source = util.getArg(aArgs, 'source', null); + var name = util.getArg(aArgs, 'name', null); + + this._validateMapping(generated, original, source, name); + + if (source && !this._sources.has(source)) { + this._sources.add(source); + } + + if (name && !this._names.has(name)) { + this._names.add(name); + } + + this._mappings.push({ + generatedLine: generated.line, + generatedColumn: generated.column, + originalLine: original != null && original.line, + originalColumn: original != null && original.column, + source: source, + name: name + }); + }; + + /** + * Set the source content for a source file. + */ + SourceMapGenerator.prototype.setSourceContent = + function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) { + var source = aSourceFile; + if (this._sourceRoot) { + source = util.relative(this._sourceRoot, source); + } + + if (aSourceContent !== null) { + // Add the source content to the _sourcesContents map. + // Create a new _sourcesContents map if the property is null. + if (!this._sourcesContents) { + this._sourcesContents = {}; + } + this._sourcesContents[util.toSetString(source)] = aSourceContent; + } else { + // Remove the source file from the _sourcesContents map. + // If the _sourcesContents map is empty, set the property to null. + delete this._sourcesContents[util.toSetString(source)]; + if (Object.keys(this._sourcesContents).length === 0) { + this._sourcesContents = null; + } + } + }; + + /** + * Applies the mappings of a sub-source-map for a specific source file to the + * source map being generated. Each mapping to the supplied source file is + * rewritten using the supplied source map. Note: The resolution for the + * resulting mappings is the minimium of this map and the supplied map. + * + * @param aSourceMapConsumer The source map to be applied. + * @param aSourceFile Optional. The filename of the source file. + * If omitted, SourceMapConsumer's file property will be used. + */ + SourceMapGenerator.prototype.applySourceMap = + function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile) { + // If aSourceFile is omitted, we will use the file property of the SourceMap + if (!aSourceFile) { + aSourceFile = aSourceMapConsumer.file; + } + var sourceRoot = this._sourceRoot; + // Make "aSourceFile" relative if an absolute Url is passed. + if (sourceRoot) { + aSourceFile = util.relative(sourceRoot, aSourceFile); + } + // Applying the SourceMap can add and remove items from the sources and + // the names array. + var newSources = new ArraySet(); + var newNames = new ArraySet(); + + // Find mappings for the "aSourceFile" + this._mappings.forEach(function (mapping) { + if (mapping.source === aSourceFile && mapping.originalLine) { + // Check if it can be mapped by the source map, then update the mapping. + var original = aSourceMapConsumer.originalPositionFor({ + line: mapping.originalLine, + column: mapping.originalColumn + }); + if (original.source !== null) { + // Copy mapping + if (sourceRoot) { + mapping.source = util.relative(sourceRoot, original.source); + } else { + mapping.source = original.source; + } + mapping.originalLine = original.line; + mapping.originalColumn = original.column; + if (original.name !== null && mapping.name !== null) { + // Only use the identifier name if it's an identifier + // in both SourceMaps + mapping.name = original.name; + } + } + } + + var source = mapping.source; + if (source && !newSources.has(source)) { + newSources.add(source); + } + + var name = mapping.name; + if (name && !newNames.has(name)) { + newNames.add(name); + } + + }, this); + this._sources = newSources; + this._names = newNames; + + // Copy sourcesContents of applied map. + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content) { + if (sourceRoot) { + sourceFile = util.relative(sourceRoot, sourceFile); + } + this.setSourceContent(sourceFile, content); + } + }, this); + }; + + /** + * A mapping can have one of the three levels of data: + * + * 1. Just the generated position. + * 2. The Generated position, original position, and original source. + * 3. Generated and original position, original source, as well as a name + * token. + * + * To maintain consistency, we validate that any new mapping being added falls + * in to one of these categories. + */ + SourceMapGenerator.prototype._validateMapping = + function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource, + aName) { + if (aGenerated && 'line' in aGenerated && 'column' in aGenerated + && aGenerated.line > 0 && aGenerated.column >= 0 + && !aOriginal && !aSource && !aName) { + // Case 1. + return; + } + else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated + && aOriginal && 'line' in aOriginal && 'column' in aOriginal + && aGenerated.line > 0 && aGenerated.column >= 0 + && aOriginal.line > 0 && aOriginal.column >= 0 + && aSource) { + // Cases 2 and 3. + return; + } + else { + throw new Error('Invalid mapping: ' + JSON.stringify({ + generated: aGenerated, + source: aSource, + original: aOriginal, + name: aName + })); + } + }; + + /** + * Serialize the accumulated mappings in to the stream of base 64 VLQs + * specified by the source map format. + */ + SourceMapGenerator.prototype._serializeMappings = + function SourceMapGenerator_serializeMappings() { + var previousGeneratedColumn = 0; + var previousGeneratedLine = 1; + var previousOriginalColumn = 0; + var previousOriginalLine = 0; + var previousName = 0; + var previousSource = 0; + var result = ''; + var mapping; + + // The mappings must be guaranteed to be in sorted order before we start + // serializing them or else the generated line numbers (which are defined + // via the ';' separators) will be all messed up. Note: it might be more + // performant to maintain the sorting as we insert them, rather than as we + // serialize them, but the big O is the same either way. + this._mappings.sort(util.compareByGeneratedPositions); + + for (var i = 0, len = this._mappings.length; i < len; i++) { + mapping = this._mappings[i]; + + if (mapping.generatedLine !== previousGeneratedLine) { + previousGeneratedColumn = 0; + while (mapping.generatedLine !== previousGeneratedLine) { + result += ';'; + previousGeneratedLine++; + } + } + else { + if (i > 0) { + if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) { + continue; + } + result += ','; + } + } + + result += base64VLQ.encode(mapping.generatedColumn + - previousGeneratedColumn); + previousGeneratedColumn = mapping.generatedColumn; + + if (mapping.source) { + result += base64VLQ.encode(this._sources.indexOf(mapping.source) + - previousSource); + previousSource = this._sources.indexOf(mapping.source); + + // lines are stored 0-based in SourceMap spec version 3 + result += base64VLQ.encode(mapping.originalLine - 1 + - previousOriginalLine); + previousOriginalLine = mapping.originalLine - 1; + + result += base64VLQ.encode(mapping.originalColumn + - previousOriginalColumn); + previousOriginalColumn = mapping.originalColumn; + + if (mapping.name) { + result += base64VLQ.encode(this._names.indexOf(mapping.name) + - previousName); + previousName = this._names.indexOf(mapping.name); + } + } + } + + return result; + }; + + SourceMapGenerator.prototype._generateSourcesContent = + function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) { + return aSources.map(function (source) { + if (!this._sourcesContents) { + return null; + } + if (aSourceRoot) { + source = util.relative(aSourceRoot, source); + } + var key = util.toSetString(source); + return Object.prototype.hasOwnProperty.call(this._sourcesContents, + key) + ? this._sourcesContents[key] + : null; + }, this); + }; + + /** + * Externalize the source map. + */ + SourceMapGenerator.prototype.toJSON = + function SourceMapGenerator_toJSON() { + var map = { + version: this._version, + file: this._file, + sources: this._sources.toArray(), + names: this._names.toArray(), + mappings: this._serializeMappings() + }; + if (this._sourceRoot) { + map.sourceRoot = this._sourceRoot; + } + if (this._sourcesContents) { + map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot); + } + + return map; + }; + + /** + * Render the source map being generated to a string. + */ + SourceMapGenerator.prototype.toString = + function SourceMapGenerator_toString() { + return JSON.stringify(this); + }; + + exports.SourceMapGenerator = SourceMapGenerator; + + }); + /* -*- Mode: js; js-indent-level: 2; -*- */ + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + * + * Based on the Base 64 VLQ implementation in Closure Compiler: + * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java + * + * Copyright 2011 The Closure Compiler Authors. All rights reserved. + * 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 Google Inc. 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS 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. + */ + define('source-map/base64-vlq', ['require', 'exports', 'module', 'source-map/base64'], function (require, exports, module) { + + var base64 = require('./base64'); + + // A single base 64 digit can contain 6 bits of data. For the base 64 variable + // length quantities we use in the source map spec, the first bit is the sign, + // the next four bits are the actual value, and the 6th bit is the + // continuation bit. The continuation bit tells us whether there are more + // digits in this value following this digit. + // + // Continuation + // | Sign + // | | + // V V + // 101011 + + var VLQ_BASE_SHIFT = 5; + + // binary: 100000 + var VLQ_BASE = 1 << VLQ_BASE_SHIFT; + + // binary: 011111 + var VLQ_BASE_MASK = VLQ_BASE - 1; + + // binary: 100000 + var VLQ_CONTINUATION_BIT = VLQ_BASE; + + /** + * Converts from a two-complement value to a value where the sign bit is + * is placed in the least significant bit. For example, as decimals: + * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) + * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) + */ + function toVLQSigned(aValue) { + return aValue < 0 + ? ((-aValue) << 1) + 1 + : (aValue << 1) + 0; + } + + /** + * Converts to a two-complement value from a value where the sign bit is + * is placed in the least significant bit. For example, as decimals: + * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 + * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 + */ + function fromVLQSigned(aValue) { + var isNegative = (aValue & 1) === 1; + var shifted = aValue >> 1; + return isNegative + ? -shifted + : shifted; + } + + /** + * Returns the base 64 VLQ encoded value. + */ + exports.encode = function base64VLQ_encode(aValue) { + var encoded = ""; + var digit; + + var vlq = toVLQSigned(aValue); + + do { + digit = vlq & VLQ_BASE_MASK; + vlq >>>= VLQ_BASE_SHIFT; + if (vlq > 0) { + // There are still more digits in this value, so we must make sure the + // continuation bit is marked. + digit |= VLQ_CONTINUATION_BIT; + } + encoded += base64.encode(digit); + } while (vlq > 0); + + return encoded; + }; + + /** + * Decodes the next base 64 VLQ value from the given string and returns the + * value and the rest of the string. + */ + exports.decode = function base64VLQ_decode(aStr) { + var i = 0; + var strLen = aStr.length; + var result = 0; + var shift = 0; + var continuation, digit; + + do { + if (i >= strLen) { + throw new Error("Expected more digits in base 64 VLQ value."); + } + digit = base64.decode(aStr.charAt(i++)); + continuation = !!(digit & VLQ_CONTINUATION_BIT); + digit &= VLQ_BASE_MASK; + result = result + (digit << shift); + shift += VLQ_BASE_SHIFT; + } while (continuation); + + return { + value: fromVLQSigned(result), + rest: aStr.slice(i) + }; + }; + + }); + /* -*- Mode: js; js-indent-level: 2; -*- */ + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + define('source-map/base64', ['require', 'exports', 'module',], function (require, exports, module) { + + var charToIntMap = {}; + var intToCharMap = {}; + + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + .split('') + .forEach(function (ch, index) { + charToIntMap[ch] = index; + intToCharMap[index] = ch; + }); + + /** + * Encode an integer in the range of 0 to 63 to a single base 64 digit. + */ + exports.encode = function base64_encode(aNumber) { + if (aNumber in intToCharMap) { + return intToCharMap[aNumber]; + } + throw new TypeError("Must be between 0 and 63: " + aNumber); + }; + + /** + * Decode a single base 64 digit to an integer. + */ + exports.decode = function base64_decode(aChar) { + if (aChar in charToIntMap) { + return charToIntMap[aChar]; + } + throw new TypeError("Not a valid base 64 digit: " + aChar); + }; + + }); + /* -*- Mode: js; js-indent-level: 2; -*- */ + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + define('source-map/util', ['require', 'exports', 'module',], function (require, exports, module) { + + /** + * This is a helper function for getting values from parameter/options + * objects. + * + * @param args The object we are extracting values from + * @param name The name of the property we are getting. + * @param defaultValue An optional value to return if the property is missing + * from the object. If this is not specified and the property is missing, an + * error will be thrown. + */ + function getArg(aArgs, aName, aDefaultValue) { + if (aName in aArgs) { + return aArgs[aName]; + } else if (arguments.length === 3) { + return aDefaultValue; + } else { + throw new Error('"' + aName + '" is a required argument.'); + } + } + + exports.getArg = getArg; + + var urlRegexp = /([\w+\-.]+):\/\/((\w+:\w+)@)?([\w.]+)?(:(\d+))?(\S+)?/; + var dataUrlRegexp = /^data:.+\,.+/; + + function urlParse(aUrl) { + var match = aUrl.match(urlRegexp); + if (!match) { + return null; + } + return { + scheme: match[1], + auth: match[3], + host: match[4], + port: match[6], + path: match[7] + }; + } + + exports.urlParse = urlParse; + + function urlGenerate(aParsedUrl) { + var url = aParsedUrl.scheme + "://"; + if (aParsedUrl.auth) { + url += aParsedUrl.auth + "@" + } + if (aParsedUrl.host) { + url += aParsedUrl.host; + } + if (aParsedUrl.port) { + url += ":" + aParsedUrl.port + } + if (aParsedUrl.path) { + url += aParsedUrl.path; + } + return url; + } + + exports.urlGenerate = urlGenerate; + + function join(aRoot, aPath) { + var url; + + if (aPath.match(urlRegexp) || aPath.match(dataUrlRegexp)) { + return aPath; + } + + if (aPath.charAt(0) === '/' && (url = urlParse(aRoot))) { + url.path = aPath; + return urlGenerate(url); + } + + return aRoot.replace(/\/$/, '') + '/' + aPath; + } + + exports.join = join; + + /** + * Because behavior goes wacky when you set `__proto__` on objects, we + * have to prefix all the strings in our set with an arbitrary character. + * + * See https://github.com/mozilla/source-map/pull/31 and + * https://github.com/mozilla/source-map/issues/30 + * + * @param String aStr + */ + function toSetString(aStr) { + return '$' + aStr; + } + + exports.toSetString = toSetString; + + function fromSetString(aStr) { + return aStr.substr(1); + } + + exports.fromSetString = fromSetString; + + function relative(aRoot, aPath) { + aRoot = aRoot.replace(/\/$/, ''); + + var url = urlParse(aRoot); + if (aPath.charAt(0) == "/" && url && url.path == "/") { + return aPath.slice(1); + } + + return aPath.indexOf(aRoot + '/') === 0 + ? aPath.substr(aRoot.length + 1) + : aPath; + } + + exports.relative = relative; + + function strcmp(aStr1, aStr2) { + var s1 = aStr1 || ""; + var s2 = aStr2 || ""; + return (s1 > s2) - (s1 < s2); + } + + /** + * Comparator between two mappings where the original positions are compared. + * + * Optionally pass in `true` as `onlyCompareGenerated` to consider two + * mappings with the same original source/line/column, but different generated + * line and column the same. Useful when searching for a mapping with a + * stubbed out mapping. + */ + function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { + var cmp; + + cmp = strcmp(mappingA.source, mappingB.source); + if (cmp) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp || onlyCompareOriginal) { + return cmp; + } + + cmp = strcmp(mappingA.name, mappingB.name); + if (cmp) { + return cmp; + } + + cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp) { + return cmp; + } + + return mappingA.generatedColumn - mappingB.generatedColumn; + }; + exports.compareByOriginalPositions = compareByOriginalPositions; + + /** + * Comparator between two mappings where the generated positions are + * compared. + * + * Optionally pass in `true` as `onlyCompareGenerated` to consider two + * mappings with the same generated line and column, but different + * source/name/original line and column the same. Useful when searching for a + * mapping with a stubbed out mapping. + */ + function compareByGeneratedPositions(mappingA, mappingB, onlyCompareGenerated) { + var cmp; + + cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp || onlyCompareGenerated) { + return cmp; + } + + cmp = strcmp(mappingA.source, mappingB.source); + if (cmp) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); + }; + exports.compareByGeneratedPositions = compareByGeneratedPositions; + + }); + /* -*- Mode: js; js-indent-level: 2; -*- */ + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + define('source-map/array-set', ['require', 'exports', 'module', 'source-map/util'], function (require, exports, module) { + + var util = require('./util'); + + /** + * A data structure which is a combination of an array and a set. Adding a new + * member is O(1), testing for membership is O(1), and finding the index of an + * element is O(1). Removing elements from the set is not supported. Only + * strings are supported for membership. + */ + function ArraySet() { + this._array = []; + this._set = {}; + } + + /** + * Static method for creating ArraySet instances from an existing array. + */ + ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) { + var set = new ArraySet(); + for (var i = 0, len = aArray.length; i < len; i++) { + set.add(aArray[i], aAllowDuplicates); + } + return set; + }; + + /** + * Add the given string to this set. + * + * @param String aStr + */ + ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) { + var isDuplicate = this.has(aStr); + var idx = this._array.length; + if (!isDuplicate || aAllowDuplicates) { + this._array.push(aStr); + } + if (!isDuplicate) { + this._set[util.toSetString(aStr)] = idx; + } + }; + + /** + * Is the given string a member of this set? + * + * @param String aStr + */ + ArraySet.prototype.has = function ArraySet_has(aStr) { + return Object.prototype.hasOwnProperty.call(this._set, + util.toSetString(aStr)); + }; + + /** + * What is the index of the given string in the array? + * + * @param String aStr + */ + ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) { + if (this.has(aStr)) { + return this._set[util.toSetString(aStr)]; + } + throw new Error('"' + aStr + '" is not in the set.'); + }; + + /** + * What is the element at the given index? + * + * @param Number aIdx + */ + ArraySet.prototype.at = function ArraySet_at(aIdx) { + if (aIdx >= 0 && aIdx < this._array.length) { + return this._array[aIdx]; + } + throw new Error('No element indexed by ' + aIdx); + }; + + /** + * Returns the array representation of this set (which has the proper indices + * indicated by indexOf). Note that this is a copy of the internal array used + * for storing the members so that no one can mess with internal state. + */ + ArraySet.prototype.toArray = function ArraySet_toArray() { + return this._array.slice(); + }; + + exports.ArraySet = ArraySet; + + }); + /* -*- Mode: js; js-indent-level: 2; -*- */ + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + define('source-map/source-map-consumer', ['require', 'exports', 'module', 'source-map/util', 'source-map/binary-search', 'source-map/array-set', 'source-map/base64-vlq'], function (require, exports, module) { + + var util = require('./util'); + var binarySearch = require('./binary-search'); + var ArraySet = require('./array-set').ArraySet; + var base64VLQ = require('./base64-vlq'); + + /** + * A SourceMapConsumer instance represents a parsed source map which we can + * query for information about the original file positions by giving it a file + * position in the generated source. + * + * The only parameter is the raw source map (either as a JSON string, or + * already parsed to an object). According to the spec, source maps have the + * following attributes: + * + * - version: Which version of the source map spec this map is following. + * - sources: An array of URLs to the original source files. + * - names: An array of identifiers which can be referrenced by individual mappings. + * - sourceRoot: Optional. The URL root from which all sources are relative. + * - sourcesContent: Optional. An array of contents of the original source files. + * - mappings: A string of base64 VLQs which contain the actual mappings. + * - file: The generated file this source map is associated with. + * + * Here is an example source map, taken from the source map spec[0]: + * + * { + * version : 3, + * file: "out.js", + * sourceRoot : "", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AA,AB;;ABCDE;" + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# + */ + function SourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sources = util.getArg(sourceMap, 'sources'); + // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which + // requires the array) to play nice here. + var names = util.getArg(sourceMap, 'names', []); + var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); + var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); + var mappings = util.getArg(sourceMap, 'mappings'); + var file = util.getArg(sourceMap, 'file', null); + + // Once again, Sass deviates from the spec and supplies the version as a + // string rather than a number, so we use loose equality checking here. + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + // Pass `true` below to allow duplicate names and sources. While source maps + // are intended to be compressed and deduplicated, the TypeScript compiler + // sometimes generates source maps with duplicates in them. See Github issue + // #72 and bugzil.la/889492. + this._names = ArraySet.fromArray(names, true); + this._sources = ArraySet.fromArray(sources, true); + + this.sourceRoot = sourceRoot; + this.sourcesContent = sourcesContent; + this._mappings = mappings; + this.file = file; + } + + /** + * Create a SourceMapConsumer from a SourceMapGenerator. + * + * @param SourceMapGenerator aSourceMap + * The source map that will be consumed. + * @returns SourceMapConsumer + */ + SourceMapConsumer.fromSourceMap = + function SourceMapConsumer_fromSourceMap(aSourceMap) { + var smc = Object.create(SourceMapConsumer.prototype); + + smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); + smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); + smc.sourceRoot = aSourceMap._sourceRoot; + smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), + smc.sourceRoot); + smc.file = aSourceMap._file; + + smc.__generatedMappings = aSourceMap._mappings.slice() + .sort(util.compareByGeneratedPositions); + smc.__originalMappings = aSourceMap._mappings.slice() + .sort(util.compareByOriginalPositions); + + return smc; + }; + + /** + * The version of the source mapping spec that we are consuming. + */ + SourceMapConsumer.prototype._version = 3; + + /** + * The list of original sources. + */ + Object.defineProperty(SourceMapConsumer.prototype, 'sources', { + get: function () { + return this._sources.toArray().map(function (s) { + return this.sourceRoot ? util.join(this.sourceRoot, s) : s; + }, this); + } + }); + + // `__generatedMappings` and `__originalMappings` are arrays that hold the + // parsed mapping coordinates from the source map's "mappings" attribute. They + // are lazily instantiated, accessed via the `_generatedMappings` and + // `_originalMappings` getters respectively, and we only parse the mappings + // and create these arrays once queried for a source location. We jump through + // these hoops because there can be many thousands of mappings, and parsing + // them is expensive, so we only want to do it if we must. + // + // Each object in the arrays is of the form: + // + // { + // generatedLine: The line number in the generated code, + // generatedColumn: The column number in the generated code, + // source: The path to the original source file that generated this + // chunk of code, + // originalLine: The line number in the original source that + // corresponds to this chunk of generated code, + // originalColumn: The column number in the original source that + // corresponds to this chunk of generated code, + // name: The name of the original symbol which generated this chunk of + // code. + // } + // + // All properties except for `generatedLine` and `generatedColumn` can be + // `null`. + // + // `_generatedMappings` is ordered by the generated positions. + // + // `_originalMappings` is ordered by the original positions. + + SourceMapConsumer.prototype.__generatedMappings = null; + Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', { + get: function () { + if (!this.__generatedMappings) { + this.__generatedMappings = []; + this.__originalMappings = []; + this._parseMappings(this._mappings, this.sourceRoot); + } + + return this.__generatedMappings; + } + }); + + SourceMapConsumer.prototype.__originalMappings = null; + Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', { + get: function () { + if (!this.__originalMappings) { + this.__generatedMappings = []; + this.__originalMappings = []; + this._parseMappings(this._mappings, this.sourceRoot); + } + + return this.__originalMappings; + } + }); + + /** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ + SourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + var generatedLine = 1; + var previousGeneratedColumn = 0; + var previousOriginalLine = 0; + var previousOriginalColumn = 0; + var previousSource = 0; + var previousName = 0; + var mappingSeparator = /^[,;]/; + var str = aStr; + var mapping; + var temp; + + while (str.length > 0) { + if (str.charAt(0) === ';') { + generatedLine++; + str = str.slice(1); + previousGeneratedColumn = 0; + } + else if (str.charAt(0) === ',') { + str = str.slice(1); + } + else { + mapping = {}; + mapping.generatedLine = generatedLine; + + // Generated column. + temp = base64VLQ.decode(str); + mapping.generatedColumn = previousGeneratedColumn + temp.value; + previousGeneratedColumn = mapping.generatedColumn; + str = temp.rest; + + if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) { + // Original source. + temp = base64VLQ.decode(str); + mapping.source = this._sources.at(previousSource + temp.value); + previousSource += temp.value; + str = temp.rest; + if (str.length === 0 || mappingSeparator.test(str.charAt(0))) { + throw new Error('Found a source, but no line and column'); + } + + // Original line. + temp = base64VLQ.decode(str); + mapping.originalLine = previousOriginalLine + temp.value; + previousOriginalLine = mapping.originalLine; + // Lines are stored 0-based + mapping.originalLine += 1; + str = temp.rest; + if (str.length === 0 || mappingSeparator.test(str.charAt(0))) { + throw new Error('Found a source and line, but no column'); + } + + // Original column. + temp = base64VLQ.decode(str); + mapping.originalColumn = previousOriginalColumn + temp.value; + previousOriginalColumn = mapping.originalColumn; + str = temp.rest; + + if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) { + // Original name. + temp = base64VLQ.decode(str); + mapping.name = this._names.at(previousName + temp.value); + previousName += temp.value; + str = temp.rest; + } + } + + this.__generatedMappings.push(mapping); + if (typeof mapping.originalLine === 'number') { + this.__originalMappings.push(mapping); + } + } + } + + this.__originalMappings.sort(util.compareByOriginalPositions); + }; + + /** + * Find the mapping that best matches the hypothetical "needle" mapping that + * we are searching for in the given "haystack" of mappings. + */ + SourceMapConsumer.prototype._findMapping = + function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, + aColumnName, aComparator) { + // To return the position we are searching for, we must first find the + // mapping for the given position and then return the opposite position it + // points to. Because the mappings are sorted, we can use binary search to + // find the best mapping. + + if (aNeedle[aLineName] <= 0) { + throw new TypeError('Line must be greater than or equal to 1, got ' + + aNeedle[aLineName]); + } + if (aNeedle[aColumnName] < 0) { + throw new TypeError('Column must be greater than or equal to 0, got ' + + aNeedle[aColumnName]); + } + + return binarySearch.search(aNeedle, aMappings, aComparator); + }; + + /** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ + SourceMapConsumer.prototype.originalPositionFor = + function SourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + var mapping = this._findMapping(needle, + this._generatedMappings, + "generatedLine", + "generatedColumn", + util.compareByGeneratedPositions); + + if (mapping) { + var source = util.getArg(mapping, 'source', null); + if (source && this.sourceRoot) { + source = util.join(this.sourceRoot, source); + } + return { + source: source, + line: util.getArg(mapping, 'originalLine', null), + column: util.getArg(mapping, 'originalColumn', null), + name: util.getArg(mapping, 'name', null) + }; + } + + return { + source: null, + line: null, + column: null, + name: null + }; + }; + + /** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * availible. + */ + SourceMapConsumer.prototype.sourceContentFor = + function SourceMapConsumer_sourceContentFor(aSource) { + if (!this.sourcesContent) { + return null; + } + + if (this.sourceRoot) { + aSource = util.relative(this.sourceRoot, aSource); + } + + if (this._sources.has(aSource)) { + return this.sourcesContent[this._sources.indexOf(aSource)]; + } + + var url; + if (this.sourceRoot + && (url = util.urlParse(this.sourceRoot))) { + // XXX: file:// URIs and absolute paths lead to unexpected behavior for + // many users. We can help them out when they expect file:// URIs to + // behave like it would if they were running a local HTTP server. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. + var fileUriAbsPath = aSource.replace(/^file:\/\//, ""); + if (url.scheme == "file" + && this._sources.has(fileUriAbsPath)) { + return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] + } + + if ((!url.path || url.path == "/") + && this._sources.has("/" + aSource)) { + return this.sourcesContent[this._sources.indexOf("/" + aSource)]; + } + } + + throw new Error('"' + aSource + '" is not in the SourceMap.'); + }; + + /** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ + SourceMapConsumer.prototype.generatedPositionFor = + function SourceMapConsumer_generatedPositionFor(aArgs) { + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: util.getArg(aArgs, 'line'), + originalColumn: util.getArg(aArgs, 'column') + }; + + if (this.sourceRoot) { + needle.source = util.relative(this.sourceRoot, needle.source); + } + + var mapping = this._findMapping(needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions); + + if (mapping) { + return { + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null) + }; + } + + return { + line: null, + column: null + }; + }; + + SourceMapConsumer.GENERATED_ORDER = 1; + SourceMapConsumer.ORIGINAL_ORDER = 2; + + /** + * Iterate over each mapping between an original source/line/column and a + * generated line/column in this source map. + * + * @param Function aCallback + * The function that is called with each mapping. + * @param Object aContext + * Optional. If specified, this object will be the value of `this` every + * time that `aCallback` is called. + * @param aOrder + * Either `SourceMapConsumer.GENERATED_ORDER` or + * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to + * iterate over the mappings sorted by the generated file's line/column + * order or the original's source/line/column order, respectively. Defaults to + * `SourceMapConsumer.GENERATED_ORDER`. + */ + SourceMapConsumer.prototype.eachMapping = + function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) { + var context = aContext || null; + var order = aOrder || SourceMapConsumer.GENERATED_ORDER; + + var mappings; + switch (order) { + case SourceMapConsumer.GENERATED_ORDER: + mappings = this._generatedMappings; + break; + case SourceMapConsumer.ORIGINAL_ORDER: + mappings = this._originalMappings; + break; + default: + throw new Error("Unknown order of iteration."); + } + + var sourceRoot = this.sourceRoot; + mappings.map(function (mapping) { + var source = mapping.source; + if (source && sourceRoot) { + source = util.join(sourceRoot, source); + } + return { + source: source, + generatedLine: mapping.generatedLine, + generatedColumn: mapping.generatedColumn, + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: mapping.name + }; + }).forEach(aCallback, context); + }; + + exports.SourceMapConsumer = SourceMapConsumer; + + }); + /* -*- Mode: js; js-indent-level: 2; -*- */ + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + define('source-map/binary-search', ['require', 'exports', 'module',], function (require, exports, module) { + + /** + * Recursive implementation of binary search. + * + * @param aLow Indices here and lower do not contain the needle. + * @param aHigh Indices here and higher do not contain the needle. + * @param aNeedle The element being searched for. + * @param aHaystack The non-empty array being searched. + * @param aCompare Function which takes two elements and returns -1, 0, or 1. + */ + function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare) { + // This function terminates when one of the following is true: + // + // 1. We find the exact element we are looking for. + // + // 2. We did not find the exact element, but we can return the next + // closest element that is less than that element. + // + // 3. We did not find the exact element, and there is no next-closest + // element which is less than the one we are searching for, so we + // return null. + var mid = Math.floor((aHigh - aLow) / 2) + aLow; + var cmp = aCompare(aNeedle, aHaystack[mid], true); + if (cmp === 0) { + // Found the element we are looking for. + return aHaystack[mid]; + } + else if (cmp > 0) { + // aHaystack[mid] is greater than our needle. + if (aHigh - mid > 1) { + // The element is in the upper half. + return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare); + } + // We did not find an exact match, return the next closest one + // (termination case 2). + return aHaystack[mid]; + } + else { + // aHaystack[mid] is less than our needle. + if (mid - aLow > 1) { + // The element is in the lower half. + return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare); + } + // The exact needle element was not found in this haystack. Determine if + // we are in termination case (2) or (3) and return the appropriate thing. + return aLow < 0 + ? null + : aHaystack[aLow]; + } + } + + /** + * This is an implementation of binary search which will always try and return + * the next lowest value checked if there is no exact hit. This is because + * mappings between original and generated line/col pairs are single points, + * and there is an implicit region between each of them, so a miss just means + * that you aren't on the very start of a region. + * + * @param aNeedle The element you are looking for. + * @param aHaystack The array that is being searched. + * @param aCompare A function which takes the needle and an element in the + * array and returns -1, 0, or 1 depending on whether the needle is less + * than, equal to, or greater than the element, respectively. + */ + exports.search = function search(aNeedle, aHaystack, aCompare) { + return aHaystack.length > 0 + ? recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, aCompare) + : null; + }; + + }); + /* -*- Mode: js; js-indent-level: 2; -*- */ + /* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + define('source-map/source-node', ['require', 'exports', 'module', 'source-map/source-map-generator', 'source-map/util'], function (require, exports, module) { + + var SourceMapGenerator = require('./source-map-generator').SourceMapGenerator; + var util = require('./util'); + + /** + * SourceNodes provide a way to abstract over interpolating/concatenating + * snippets of generated JavaScript source code while maintaining the line and + * column information associated with the original source code. + * + * @param aLine The original line number. + * @param aColumn The original column number. + * @param aSource The original source's filename. + * @param aChunks Optional. An array of strings which are snippets of + * generated JS, or other SourceNodes. + * @param aName The original identifier. + */ + function SourceNode(aLine, aColumn, aSource, aChunks, aName) { + this.children = []; + this.sourceContents = {}; + this.line = aLine === undefined ? null : aLine; + this.column = aColumn === undefined ? null : aColumn; + this.source = aSource === undefined ? null : aSource; + this.name = aName === undefined ? null : aName; + if (aChunks != null) this.add(aChunks); + } + + /** + * Creates a SourceNode from generated code and a SourceMapConsumer. + * + * @param aGeneratedCode The generated code + * @param aSourceMapConsumer The SourceMap for the generated code + */ + SourceNode.fromStringWithSourceMap = + function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer) { + // The SourceNode we want to fill with the generated code + // and the SourceMap + var node = new SourceNode(); + + // The generated code + // Processed fragments are removed from this array. + var remainingLines = aGeneratedCode.split('\n'); + + // We need to remember the position of "remainingLines" + var lastGeneratedLine = 1, lastGeneratedColumn = 0; + + // The generate SourceNodes we need a code range. + // To extract it current and last mapping is used. + // Here we store the last mapping. + var lastMapping = null; + + aSourceMapConsumer.eachMapping(function (mapping) { + if (lastMapping === null) { + // We add the generated code until the first mapping + // to the SourceNode without any mapping. + // Each line is added as separate string. + while (lastGeneratedLine < mapping.generatedLine) { + node.add(remainingLines.shift() + "\n"); + lastGeneratedLine++; + } + if (lastGeneratedColumn < mapping.generatedColumn) { + var nextLine = remainingLines[0]; + node.add(nextLine.substr(0, mapping.generatedColumn)); + remainingLines[0] = nextLine.substr(mapping.generatedColumn); + lastGeneratedColumn = mapping.generatedColumn; + } + } else { + // We add the code from "lastMapping" to "mapping": + // First check if there is a new line in between. + if (lastGeneratedLine < mapping.generatedLine) { + var code = ""; + // Associate full lines with "lastMapping" + do { + code += remainingLines.shift() + "\n"; + lastGeneratedLine++; + lastGeneratedColumn = 0; + } while (lastGeneratedLine < mapping.generatedLine); + // When we reached the correct line, we add code until we + // reach the correct column too. + if (lastGeneratedColumn < mapping.generatedColumn) { + var nextLine = remainingLines[0]; + code += nextLine.substr(0, mapping.generatedColumn); + remainingLines[0] = nextLine.substr(mapping.generatedColumn); + lastGeneratedColumn = mapping.generatedColumn; + } + // Create the SourceNode. + addMappingWithCode(lastMapping, code); + } else { + // There is no new line in between. + // Associate the code between "lastGeneratedColumn" and + // "mapping.generatedColumn" with "lastMapping" + var nextLine = remainingLines[0]; + var code = nextLine.substr(0, mapping.generatedColumn - + lastGeneratedColumn); + remainingLines[0] = nextLine.substr(mapping.generatedColumn - + lastGeneratedColumn); + lastGeneratedColumn = mapping.generatedColumn; + addMappingWithCode(lastMapping, code); + } + } + lastMapping = mapping; + }, this); + // We have processed all mappings. + // Associate the remaining code in the current line with "lastMapping" + // and add the remaining lines without any mapping + addMappingWithCode(lastMapping, remainingLines.join("\n")); + + // Copy sourcesContent into SourceNode + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content) { + node.setSourceContent(sourceFile, content); + } + }); + + return node; + + function addMappingWithCode(mapping, code) { + if (mapping === null || mapping.source === undefined) { + node.add(code); + } else { + node.add(new SourceNode(mapping.originalLine, + mapping.originalColumn, + mapping.source, + code, + mapping.name)); + } + } + }; + + /** + * Add a chunk of generated JS to this source node. + * + * @param aChunk A string snippet of generated JS code, another instance of + * SourceNode, or an array where each member is one of those things. + */ + SourceNode.prototype.add = function SourceNode_add(aChunk) { + if (Array.isArray(aChunk)) { + aChunk.forEach(function (chunk) { + this.add(chunk); + }, this); + } + else if (aChunk instanceof SourceNode || typeof aChunk === "string") { + if (aChunk) { + this.children.push(aChunk); + } + } + else { + throw new TypeError( + "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk + ); + } + return this; + }; + + /** + * Add a chunk of generated JS to the beginning of this source node. + * + * @param aChunk A string snippet of generated JS code, another instance of + * SourceNode, or an array where each member is one of those things. + */ + SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) { + if (Array.isArray(aChunk)) { + for (var i = aChunk.length - 1; i >= 0; i--) { + this.prepend(aChunk[i]); + } + } + else if (aChunk instanceof SourceNode || typeof aChunk === "string") { + this.children.unshift(aChunk); + } + else { + throw new TypeError( + "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk + ); + } + return this; + }; + + /** + * Walk over the tree of JS snippets in this node and its children. The + * walking function is called once for each snippet of JS and is passed that + * snippet and the its original associated source's line/column location. + * + * @param aFn The traversal function. + */ + SourceNode.prototype.walk = function SourceNode_walk(aFn) { + var chunk; + for (var i = 0, len = this.children.length; i < len; i++) { + chunk = this.children[i]; + if (chunk instanceof SourceNode) { + chunk.walk(aFn); + } + else { + if (chunk !== '') { + aFn(chunk, { + source: this.source, + line: this.line, + column: this.column, + name: this.name + }); + } + } + } + }; + + /** + * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between + * each of `this.children`. + * + * @param aSep The separator. + */ + SourceNode.prototype.join = function SourceNode_join(aSep) { + var newChildren; + var i; + var len = this.children.length; + if (len > 0) { + newChildren = []; + for (i = 0; i < len - 1; i++) { + newChildren.push(this.children[i]); + newChildren.push(aSep); + } + newChildren.push(this.children[i]); + this.children = newChildren; + } + return this; + }; + + /** + * Call String.prototype.replace on the very right-most source snippet. Useful + * for trimming whitespace from the end of a source node, etc. + * + * @param aPattern The pattern to replace. + * @param aReplacement The thing to replace the pattern with. + */ + SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) { + var lastChild = this.children[this.children.length - 1]; + if (lastChild instanceof SourceNode) { + lastChild.replaceRight(aPattern, aReplacement); + } + else if (typeof lastChild === 'string') { + this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement); + } + else { + this.children.push(''.replace(aPattern, aReplacement)); + } + return this; + }; + + /** + * Set the source content for a source file. This will be added to the SourceMapGenerator + * in the sourcesContent field. + * + * @param aSourceFile The filename of the source file + * @param aSourceContent The content of the source file + */ + SourceNode.prototype.setSourceContent = + function SourceNode_setSourceContent(aSourceFile, aSourceContent) { + this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent; + }; + + /** + * Walk over the tree of SourceNodes. The walking function is called for each + * source file content and is passed the filename and source content. + * + * @param aFn The traversal function. + */ + SourceNode.prototype.walkSourceContents = + function SourceNode_walkSourceContents(aFn) { + for (var i = 0, len = this.children.length; i < len; i++) { + if (this.children[i] instanceof SourceNode) { + this.children[i].walkSourceContents(aFn); + } + } + + var sources = Object.keys(this.sourceContents); + for (var i = 0, len = sources.length; i < len; i++) { + aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]); + } + }; + + /** + * Return the string representation of this source node. Walks over the tree + * and concatenates all the various snippets together to one string. + */ + SourceNode.prototype.toString = function SourceNode_toString() { + var str = ""; + this.walk(function (chunk) { + str += chunk; + }); + return str; + }; + + /** + * Returns the string representation of this source node along with a source + * map. + */ + SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) { + var generated = { + code: "", + line: 1, + column: 0 + }; + var map = new SourceMapGenerator(aArgs); + var sourceMappingActive = false; + var lastOriginalSource = null; + var lastOriginalLine = null; + var lastOriginalColumn = null; + var lastOriginalName = null; + this.walk(function (chunk, original) { + generated.code += chunk; + if (original.source !== null + && original.line !== null + && original.column !== null) { + if (lastOriginalSource !== original.source + || lastOriginalLine !== original.line + || lastOriginalColumn !== original.column + || lastOriginalName !== original.name) { + map.addMapping({ + source: original.source, + original: { + line: original.line, + column: original.column + }, + generated: { + line: generated.line, + column: generated.column + }, + name: original.name + }); + } + lastOriginalSource = original.source; + lastOriginalLine = original.line; + lastOriginalColumn = original.column; + lastOriginalName = original.name; + sourceMappingActive = true; + } else if (sourceMappingActive) { + map.addMapping({ + generated: { + line: generated.line, + column: generated.column + } + }); + lastOriginalSource = null; + sourceMappingActive = false; + } + chunk.split('').forEach(function (ch) { + if (ch === '\n') { + generated.line++; + generated.column = 0; + } else { + generated.column++; + } + }); + }); + this.walkSourceContents(function (sourceFile, sourceContent) { + map.setSourceContent(sourceFile, sourceContent); + }); + + return {code: generated.code, map: map}; + }; + + exports.SourceNode = SourceNode; + + }); + /* -*- Mode: js; js-indent-level: 2; -*- */ +/////////////////////////////////////////////////////////////////////////////// + + this.sourceMap = { + SourceMapConsumer: require('source-map/source-map-consumer').SourceMapConsumer, + SourceMapGenerator: require('source-map/source-map-generator').SourceMapGenerator, + SourceNode: require('source-map/source-node').SourceNode + }; + +// footer to wrap "source-map" module + return this.sourceMap; + }(); +})(); + + +/* Less.js v1.7.5 RHINO | Copyright (c) 2009-2014, Alexis Sellier */ + +/*global name:true, less, loadStyleSheet, os */ + +function formatError(ctx, options) { + options = options || {}; + + var message = ""; + var extract = ctx.extract; + var error = []; + +// var stylize = options.color ? require('./lessc_helper').stylize : function (str) { return str; }; + var stylize = function (str) { + return str; + }; + + // only output a stack if it isn't a less error + if (ctx.stack && !ctx.type) { + return stylize(ctx.stack, 'red'); + } + + if (!ctx.hasOwnProperty('index') || !extract) { + return ctx.stack || ctx.message; + } + + if (typeof(extract[0]) === 'string') { + error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey')); + } + + if (typeof(extract[1]) === 'string') { + var errorTxt = ctx.line + ' '; + if (extract[1]) { + errorTxt += extract[1].slice(0, ctx.column) + + stylize(stylize(stylize(extract[1][ctx.column], 'bold') + + extract[1].slice(ctx.column + 1), 'red'), 'inverse'); + } + error.push(errorTxt); + } + + 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 + + stylize(' on line ' + ctx.line + ', column ' + (ctx.column + 1) + ':', 'grey'); + } + + message += '\n' + error; + + if (ctx.callLine) { + message += stylize('from ', 'red') + (ctx.filename || '') + '/n'; + message += stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract + '/n'; + } + + return message; +} + +function writeError(ctx, options) { + options = options || {}; + if (options.silent) { + return; + } + var message = formatError(ctx, options); + throw new Error(message); +} + +function loadStyleSheet(sheet, callback, reload, remaining) { + var endOfPath = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')), + sheetName = name.slice(0, endOfPath + 1) + sheet.href, + contents = sheet.contents || {}, + input = readFile(sheetName); + + input = input.replace(/^\xEF\xBB\xBF/, ''); + + contents[sheetName] = input; + + var parser = new less.Parser({ + paths: [sheet.href.replace(/[\w\.-]+$/, '')], + contents: contents + }); + parser.parse(input, function (e, root) { + if (e) { + return writeError(e); + } + //try { + callback(e, root, input, sheet, {local: false, lastModified: 0, remaining: remaining}, sheetName); + //} catch (e) { + //writeError(e); + //log.error(e); + //} + }); +} + +less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { + + var href = file; + if (currentFileInfo && currentFileInfo.currentDirectory && !/^\//.test(file)) { + href = less.modules.path.join(currentFileInfo.currentDirectory, file); + } + + var path = less.modules.path.dirname(href); + + var newFileInfo = { + currentDirectory: path + '/', + filename: href + }; + + if (currentFileInfo) { + newFileInfo.entryPath = currentFileInfo.entryPath; + newFileInfo.rootpath = currentFileInfo.rootpath; + newFileInfo.rootFilename = currentFileInfo.rootFilename; + newFileInfo.relativeUrls = currentFileInfo.relativeUrls; + } else { + newFileInfo.entryPath = path; + newFileInfo.rootpath = less.rootpath || path; + newFileInfo.rootFilename = href; + newFileInfo.relativeUrls = env.relativeUrls; + } + + var j = file.lastIndexOf('/'); + 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; + newFileInfo.filename = href; + + var data = null; + try { + data = readFile(href); + } catch (e) { + log.error(e); + callback({type: 'File', message: "'" + less.modules.path.basename(href) + "' wasn't found"}); + return; + } + + try { + callback(null, data, href, newFileInfo, {lastModified: 0}); + } catch (e) { + callback(e, null, href); + } +}; + + +function writeFile(filename, content) { + var f = new File(filename); + f.open('w'); + f.write(content); + f.close(); +} + +// Command line integration via Rhino +var compile = function (args) { + + var options = { + depends: false, + compress: false, + cleancss: false, + max_line_len: -1, + optimization: 1, + silent: false, + verbose: false, + lint: false, + paths: [], + color: true, + strictImports: false, + rootpath: '', + relativeUrls: false, + ieCompat: true, + strictMath: false, + strictUnits: false + }; + var continueProcessing = true, + currentErrorcode; + + var checkArgFunc = function (arg, option) { + if (!option) { + print(arg + " option requires a parameter"); + continueProcessing = false; + return false; + } + return true; + }; + + 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"); + continueProcessing = false; + return false; + } + return Boolean(onOff[2]); + }; + + var warningMessages = ""; + var sourceMapFileInline = false; + + args = args.filter(function (arg) { + var 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]; + } // was (?:=([^\s]*)), check! + else { + return arg; + } + + switch (arg) { + case 'v': + case 'version': + console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]"); + continueProcessing = false; + break; + case 'verbose': + options.verbose = true; + break; + case 's': + case 'silent': + options.silent = true; + break; + case 'l': + case 'lint': + options.lint = true; + break; + case 'strict-imports': + options.strictImports = true; + break; + case 'h': + case 'help': + //TODO +// require('../lib/less/lessc_helper').printUsage(); + continueProcessing = false; + break; + case 'x': + case 'compress': + options.compress = true; + break; + case 'M': + case 'depends': + options.depends = true; + break; + case 'yui-compress': + warningMessages += "yui-compress option has been removed. assuming clean-css."; + options.cleancss = true; + break; + case 'clean-css': + options.cleancss = 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); + return p; + } + }); + } + break; + case 'O0': + options.optimization = 0; + break; + case 'O1': + options.optimization = 1; + break; + case 'O2': + options.optimization = 2; + break; + case 'line-numbers': + if (checkArgFunc(arg, match[2])) { + options.dumpLineNumbers = match[2]; + } + break; + case 'source-map': + if (!match[2]) { + options.sourceMap = true; + } else { + options.sourceMap = match[2]; + } + break; + case 'source-map-rootpath': + if (checkArgFunc(arg, match[2])) { + options.sourceMapRootpath = match[2]; + } + break; + case 'source-map-basepath': + if (checkArgFunc(arg, match[2])) { + options.sourceMapBasepath = match[2]; + } + break; + case 'source-map-map-inline': + sourceMapFileInline = true; + options.sourceMap = true; + break; + case 'source-map-less-inline': + options.outputSourceFiles = true; + break; + case 'source-map-url': + if (checkArgFunc(arg, match[2])) { + options.sourceMapURL = match[2]; + } + break; + case 'source-map-output-map-file': + if (checkArgFunc(arg, match[2])) { + options.writeSourceMap = function (sourceMapContent) { + writeFile(match[2], sourceMapContent); + }; + } + 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; + default: + console.log('invalid option ' + arg); + continueProcessing = false; + } + }); + + if (!continueProcessing) { + return; + } + + var name = args[0]; + if (name && name != '-') { +// name = path.resolve(process.cwd(), name); + } + var output = args[1]; + var outputbase = args[1]; + if (output) { + options.sourceMapOutputFilename = output; +// output = path.resolve(process.cwd(), output); + if (warningMessages) { + console.log(warningMessages); + } + } + +// options.sourceMapBasepath = process.cwd(); +// options.sourceMapBasepath = ''; + + if (options.sourceMap === true) { + console.log("output: " + output); + if (!output && !sourceMapFileInline) { + console.log("the sourcemap option only has an optional filename if the css filename is given"); + return; + } + options.sourceMapFullFilename = options.sourceMapOutputFilename + ".map"; + options.sourceMap = less.modules.path.basename(options.sourceMapFullFilename); + } else if (options.sourceMap) { + options.sourceMapOutputFilename = options.sourceMap; + } + + + if (!name) { + console.log("lessc: no inout files"); + console.log(""); + // TODO +// require('../lib/less/lessc_helper').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"); + return; + } + console.log(outputbase + ": "); + } + + if (!name) { + console.log('No files present in the fileset'); + quit(1); + } + + var input = null; + try { + input = readFile(name, 'utf-8'); + } catch (e) { + log.error(e); + quit(1); + } + + options.filename = name; + var result; + var parser = new less.Parser(options); + parser.parse(input, function (e, root) { + if (e) { + log.info(e); + writeError(e, options); + quit(1); + } else { + result = root.toCSS(options); + if (output) { + writeFile(output, result); + console.log("Written to " + output); + } else { + print(result); + } + quit(0); + } + }, { + globalVars: {'self-class': fuseState.currentUnit + '-unit'} + }); +}; diff --git a/modules/distribution/src/repository/jaggeryapps/iot/lib/login.jag b/modules/distribution/src/repository/jaggeryapps/iot/lib/login.jag index 56201bcc..7b93c3c7 100644 --- a/modules/distribution/src/repository/jaggeryapps/iot/lib/login.jag +++ b/modules/distribution/src/repository/jaggeryapps/iot/lib/login.jag @@ -19,7 +19,7 @@ (function(){ var constants = require('/modules/constants.js'); if (!session.get(constants.USER_SESSION_KEY)) { - var dataConfig = require('/config/mdm-props.js').config(); + var dataConfig = require('/config/dc-props.js').config(); var sso = require('/modules/sso.js').sso; var keyStoreParams = { keyStoreName : dataConfig.ssoConfiguration.keyStoreName, diff --git a/modules/distribution/src/repository/jaggeryapps/iot/lib/logout.jag b/modules/distribution/src/repository/jaggeryapps/iot/lib/logout.jag index b5c4d811..4ae365d9 100644 --- a/modules/distribution/src/repository/jaggeryapps/iot/lib/logout.jag +++ b/modules/distribution/src/repository/jaggeryapps/iot/lib/logout.jag @@ -18,7 +18,7 @@ */ var constants = require('/modules/constants.js'); var user = session.get(constants.USER_SESSION_KEY); -var dataConfig = require('/config/mdm-props.js').config(); +var dataConfig = require('/config/dc-props.js').config(); var log = new Log(); if (user === null) { log.debug("Cannot perform logout. No user session found."); diff --git a/modules/distribution/src/repository/jaggeryapps/iot/modules/constants.js b/modules/distribution/src/repository/jaggeryapps/iot/modules/constants.js index 5850c38c..182d319e 100644 --- a/modules/distribution/src/repository/jaggeryapps/iot/modules/constants.js +++ b/modules/distribution/src/repository/jaggeryapps/iot/modules/constants.js @@ -15,9 +15,12 @@ * specific language governing permissions and limitations * under the License. */ -var WEB_APP_TITLE = "WSO2 Device Cloud"; + +var WEB_APP_TITLE = "WSO2 DC - Device Cloud"; var WEB_APP_CONTEXT = "/iot"; var USER_SESSION_KEY = "USER"; var UNSPECIFIED = "Unspecified"; - +var ERRORS = { + "USER_NOT_FOUND": "USER_NOT_FOUND" + }; diff --git a/modules/distribution/src/repository/jaggeryapps/iot/modules/download.js b/modules/distribution/src/repository/jaggeryapps/iot/modules/download.js new file mode 100644 index 00000000..5394c8bf --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/modules/download.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var downloadModule; +var Handlebars = require('../lib/handlebars-v2.0.0.js').Handlebars; +downloadModule = function () { + var log = new Log("modules/download.js"); + + var constants = require("/modules/constants.js"); + + var publicMethods = {}; + var privateMethods = {}; + + /** + * Downloading a specified sketch file. + * + * @param file File name or file object of the downloading file + * @param replaceParams + */ + publicMethods.downloadSketch = function (file, response, replaceParams) { + var file = new File("../sketch/" + file); + + file.open('r'); + log.debug("Reading file '" + file.getPath() + "'"); + var content = file.readAll().trim(); + file.close(); + + var downloadFile = privateMethods.allReplace(content,replaceParams); + + response.contentType = "application/octet-stream"; + response.addHeader("Content-Disposition", "attachment; filename='sketch.hbs'"); + response.addHeader("Content-Length", String(downloadFile.length)); + response.content = downloadFile; + }; + + /** + * Find and replace all occurrences. + * @param inStr input string + * @param replaceParams key value array + * @returns retStr replaced string + */ + privateMethods.allReplace = function (inStr, replaceParams) { + var retStr = inStr; + for (var x in replaceParams) { + retStr = retStr.replace(new RegExp(x, 'g'), replaceParams[x]) + } + return retStr; + }; + + return publicMethods; +}(); + + diff --git a/modules/distribution/src/repository/jaggeryapps/iot/modules/pinch.min.js b/modules/distribution/src/repository/jaggeryapps/iot/modules/pinch.min.js new file mode 100644 index 00000000..feccd2da --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/modules/pinch.min.js @@ -0,0 +1,27 @@ +/* +* Copyright (c) 2011 František Hába +* +* Permission is hereby granted, free of charge, to any person obtaining a copy of +* this software and associated documentation files (the 'Software'), to deal in +* the Software without restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +* Software, and to permit persons to whom the Software is furnished to do so, +* subject to the following conditions: +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Reference:- https://github.com/Baggz/Pinch +* Pinch is a small JavaScript utility which is able to replace any data in a JavaScript object (or JSON). +* */ +(function(){var k=function(a,c){return a.length!==c.length?!1:a.every(function(a,b){return c[b]===a})},j=function(a,c,d){var b,e;if("[object Array]"===Object.prototype.toString.call(a)){b=0;for(e=a.length;b -
- -
- {{unit "operation-bar"}} - {{unit "device-listing"}} -
- -
- + {{unit "showcase"}} {{/zone}} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/pages/login.hbs b/modules/distribution/src/repository/jaggeryapps/iot/pages/login.hbs index fc1255bf..716c163a 100644 --- a/modules/distribution/src/repository/jaggeryapps/iot/pages/login.hbs +++ b/modules/distribution/src/repository/jaggeryapps/iot/pages/login.hbs @@ -1,7 +1,8 @@ {{layout "fluid"}} {{#zone "title"}} - WSO2 Mobile Device Manager | Login + WSO2 Device Cloud | Login {{/zone}} {{#zone "body"}} + {{unit "appbar"}} {{unit "login"}} {{/zone}} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/pages/users.hbs b/modules/distribution/src/repository/jaggeryapps/iot/pages/users.hbs deleted file mode 100644 index b06fb457..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iot/pages/users.hbs +++ /dev/null @@ -1,9 +0,0 @@ -{{authorized}} -{{layout "fluid"}} -{{#zone "title"}} - User Management -{{/zone}} -{{#zone "body"}} - {{unit "appbar"}} - {{unit "user-listing"}} -{{/zone}} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/sketch/arduino-sketch.hbs b/modules/distribution/src/repository/jaggeryapps/iot/sketch/arduino-sketch.hbs new file mode 100644 index 00000000..ecce2e56 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iot/sketch/arduino-sketch.hbs @@ -0,0 +1,30 @@ +
+

Arduino Uno


+

Connect your Arduino Uno device to the IBM Internet of Things Foundation.

+ Arduino UNO Board
+

Prepare

+
Get your device ready
+
    +
  • Mount the Ethernet / Wifi shield on the Arduino Uno device.
  • +
  • Connect the Arduino Uno to the USB of your computer.
  • +
  • Connect an Ethernet cable to the Ethernet / WiFi Shield.
  • +
+ +

Connect (Quickstart)

+
Internet of Things Foundation Quickstart connection
+
    +
  • Use the following command to download the installer from GitHub:
  • +
  • curl -LO https://github.com/ibm-messaging/iot-arduino.git
  • +
  • Download the Sketch installer from the Arduino website http://arduino.cc/en/Main/Software
  • +
  • Install the Sketch program
  • +
  • Use the Sketch program to open the samples code samples/quickstart/quickstart.ino
  • +
  • View the lower part of the Sketch pad window to check that the COM connection is shown as active
  • +
  • Compile and deploy the sample code to the Arduino Uno device
  • +
+

+ +


+
\ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/test.jag b/modules/distribution/src/repository/jaggeryapps/iot/test.jag deleted file mode 100644 index c0097b63..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iot/test.jag +++ /dev/null @@ -1,12 +0,0 @@ -<% -var userModule = require("/modules/user.js").userModule; -userModule.addPermissions([{key: "device-mgt/", name: "Device Management"}], ""); -userModule.addPermissions([{key: "device-mgt/admin", name: "Device Management Admin"}], ""); -userModule.addPermissions([{key: "device-mgt/user", name: "Device Management User"}], ""); - -userModule.addPermissions([{key: "devices", name: "Device"}], "device-mgt/admin"); -userModule.addPermissions([{key: "devices", name: "Device"}], "device-mgt/user"); -userModule.addPermissions([{key: "devices/list", name: "List all Devices"}], "device-mgt/admin"); -userModule.addPermissions([{key: "devices/list", name: "List own Devices"}], "device-mgt/user"); -new Log().info(userModule.isAuthorized("/permission/device-mgt/admin/devices/list")); -%> \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.hbs b/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.hbs deleted file mode 100644 index ac13eaa9..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.hbs +++ /dev/null @@ -1,56 +0,0 @@ -{{#zone "main"}} - -
-
- -
-
-

Add User

- Please note that * sign represents required fields of data. -
- -
-
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
-
-
- -
-
- -
-
- -{{/zone}} -{{#zone "bottomJs"}} - -{{/zone}} - diff --git a/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.js b/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.js deleted file mode 100644 index 98dce470..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Returns the dynamic state to be populated by add-user page. - * - * @param context Object that gets updated with the dynamic state of this page to be presented - * @returns {*} A context object that returns the dynamic state of this page to be presented - */ -function onRequest(context) { - var carbon = require('carbon'); - var tenantId = carbon.server.tenantId(); - var url = carbon.server.address('https') + "/admin/services/"; - var server = new carbon.server.Server(url); - var userManager = new carbon.user.UserManager(server, tenantId); - - var allRoles = userManager.allRoles(); - var i = 0; - var filteredRoles = []; - while (allRoles[i]) { - if (allRoles[i] != "Internal/subscriber" && allRoles[i] != "Internal/everyone") { - filteredRoles.push(allRoles[i]); - } - i++; - } - - context.roles = filteredRoles; - return context; -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.json b/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.json deleted file mode 100644 index de7c5829..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/add-user.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "predicate": false -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/public/js/bottomJs.js b/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/public/js/bottomJs.js deleted file mode 100644 index 9075e2b9..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iot/units/add-user/public/js/bottomJs.js +++ /dev/null @@ -1,91 +0,0 @@ -$( document ).ready(function() { - $("select.select2").select2({ - placeholder : "Select..." - }); - - $("select.select2[multiple=multiple]").select2({ - placeholder : "Select...", - tags : true - }); -}); - -/** - * Checks if an email address has the valid format or not. - * - * @param email Email address - * @returns {boolean} true if email has the valid format, otherwise false. - */ -var emailIsValid = function(email) { - var atPosition = email.indexOf("@"); - var dotPosition = email.lastIndexOf("."); - return !(atPosition < 1 || ( dotPosition - atPosition < 2 )); -}; - -/** - * Following click function would execute - * when a user clicks on "Add User" button - * on Add User page in WSO2 MDM Console. - */ -$("button#add-user-btn").click(function() { - var username = $("input#username").val(); - var firstname = $("input#firstname").val(); - var lastname = $("input#lastname").val(); - var emailAddress = $("input#email").val(); - var userRoles = $("select#roles").val(); - - if (!username) { - $(".wr-validation-summary p").text("Username is a required field. It cannot be empty."); - $(".wr-validation-summary").removeClass("hidden"); - } else if (!firstname) { - $(".wr-validation-summary p").text("Firstname is a required field. It cannot be empty."); - $(".wr-validation-summary").removeClass("hidden"); - } else if (!lastname) { - $(".wr-validation-summary p").text("Lastname is a required field. It cannot be empty."); - $(".wr-validation-summary").removeClass("hidden"); - } else if (!emailAddress) { - $(".wr-validation-summary p").text("Email is a required field. It cannot be empty."); - $(".wr-validation-summary").removeClass("hidden"); - } else if (!emailIsValid(emailAddress)) { - $(".wr-validation-summary p").text("Email is not valid. Please enter a correct email address."); - $(".wr-validation-summary").removeClass("hidden"); - } else { - var addUserFormData = {}; - addUserFormData.username = username; - addUserFormData.firstname = firstname; - addUserFormData.lastname = lastname; - addUserFormData.emailAddress = emailAddress; - addUserFormData.userRoles = userRoles; - - var addUserAPI = "/mdm/api/users/add"; - - $.ajax({ - type : "POST", - url : addUserAPI, - contentType : "application/json", - data : JSON.stringify(addUserFormData), - success : function(data) { - if (data == 201) { - $(".wr-validation-summary p").text("User (" + username + ") was added. " + - "An invitation mail will also be sent to this user to initiate a device enrollment."); - // Clearing user input fields. - $("input#username").val(""); - $("input#firstname").val(""); - $("input#lastname").val(""); - $("input#email").val(""); - $("select#roles").select2("val", ""); - } else if (data == 400) { - $(".wr-validation-summary p").text("Exception occurred at backend."); - } else if (data == 403) { - $(".wr-validation-summary p").text("Action was not permitted."); - } else if (data == 409) { - $(".wr-validation-summary p").text("Sorry, User already exists."); - } - $(".wr-validation-summary").removeClass("hidden"); - }, - error : function() { - $(".wr-validation-summary p").text("An unexpected error occurred."); - $(".wr-validation-summary").removeClass("hidden"); - } - }); - } -}); \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iot/units/appbar/appbar.hbs b/modules/distribution/src/repository/jaggeryapps/iot/units/appbar/appbar.hbs index 7ad460e6..86c9867a 100755 --- a/modules/distribution/src/repository/jaggeryapps/iot/units/appbar/appbar.hbs +++ b/modules/distribution/src/repository/jaggeryapps/iot/units/appbar/appbar.hbs @@ -1,44 +1,159 @@ {{#zone "main"}} -