├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── format.js ├── style.css └── thirdparty │ ├── codemirror.css │ ├── codemirror.js │ └── css.js ├── bin └── cssbeautify ├── cssbeautify.js ├── images └── ribbon.png ├── index.html ├── package.json └── test ├── index.html ├── runner.js └── test.js /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | index.html 3 | test/ 4 | assets/ 5 | images/ 6 | /node_modules/ 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | This page describes how to contribute changes to CSS Beautify. 4 | 5 | Please do **not** create a pull request without reading this guide first. Failure to do so may result in the **rejection** of the pull request. 6 | 7 | ## CLA 8 | 9 | Before we can accept any contributions, you need to sign [Contributor License Agreement](http://en.wikipedia.org/wiki/Contributor_License_Agreement). You can do that using Sencha Labs [online CLA](http://www.sencha.com/cla). 10 | 11 | ## Coding Policies 12 | 13 | Make sure that your code passes [JSLint](http://jslint.com) checks. 14 | 15 | Make sure your patch does break existing tests (open test/index.html in a web browser). 16 | 17 | If you add a new feature, create a new test associated with that. Feature or enhancement pull request without a corresponding test will **not** be merged. 18 | 19 | ## Pull Request 20 | 21 | For the actual contribution, please use [Github pull request](http://help.github.com/pull-requests/) workflow. 22 | 23 | Please do not create a pull request for multiple unrelated commits. It is strongly recommended to create a topic branch and make the commits as atomic as possible for the merge. This makes it easy to review all the changes. 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSS Beautify # 2 | 3 | CSS Beautify is a JavaScript implementation of reindenter and reformatter for styles written in [CSS](http://www.w3.org/Style/CSS/). 4 | 5 | Given the following style: 6 | 7 | ```css 8 | menu{color:red} navigation{background-color:#333} 9 | ``` 10 | 11 | CSS Beautify will produce: 12 | 13 | ```css 14 | menu { 15 | color: red 16 | } 17 | 18 | navigation { 19 | background-color: #333 20 | } 21 | ``` 22 | 23 | Try it online at [cssbeautify.com](http://cssbeautify.com). For the 24 | command-line use, install Node.js [cssbeautify](https://npmjs.org/package/cssbeautify) package. 25 | 26 | For more examples, see also its [test suite](http://cssbeautify.com/test/). 27 | 28 | ## Using cssbeautify() function ## 29 | 30 | Since CSS Beautify is written in pure JavaScript, it can run anywhere that JavaScript can run. 31 | 32 | The API is very simple: 33 | 34 | ```javascript 35 | var result = cssbeautify(style, options); 36 | ``` 37 | 38 | **options** is an optional object to adjust the formatting. Known options so far are: 39 | 40 | * indent is a string used for the indentation of the declaration (default is 4 spaces) 41 | * openbrace defines the placement of open curly brace, either *end-of-line* (default) or *separate-line*. 42 | * autosemicolon always inserts a semicolon after the last ruleset (default is *false*) 43 | 44 | Example call: 45 | 46 | ```javascript 47 | var beautified = cssbeautify('menu{opacity:.7}', { 48 | indent: ' ', 49 | openbrace: 'separate-line', 50 | autosemicolon: true 51 | }); 52 | ``` 53 | 54 | ## Contributing ## 55 | 56 | Contributions are welcomed! Please read the [Contribution Guide](https://github.com/senchalabs/cssbeautify/blob/master/CONTRIBUTING.md) for more info. 57 | 58 | ## License ## 59 | 60 | Copyright (C) 2012 Sencha Inc. 61 | Copyright (C) 2011 Sencha Inc. 62 | 63 | Permission is hereby granted, free of charge, to any person obtaining a copy 64 | of this software and associated documentation files (the "Software"), to deal 65 | in the Software without restriction, including without limitation the rights 66 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 67 | copies of the Software, and to permit persons to whom the Software is 68 | furnished to do so, subject to the following conditions: 69 | 70 | The above copyright notice and this permission notice shall be included in 71 | all copies or substantial portions of the Software. 72 | 73 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 74 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 75 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 76 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 77 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 78 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 79 | THE SOFTWARE. 80 | -------------------------------------------------------------------------------- /assets/format.js: -------------------------------------------------------------------------------- 1 | /*global cssbeautify:true, document:true, window:true, CodeMirror: true */ 2 | 3 | var editor, viewer, formatId; 4 | 5 | function format() { 6 | 'use strict'; 7 | if (formatId) { 8 | window.clearTimeout(formatId); 9 | } 10 | formatId = window.setTimeout(function () { 11 | var options, raw, beautified; 12 | 13 | options = { 14 | indent: ' ' 15 | }; 16 | 17 | if (document.getElementById('tab').checked) { 18 | options.indent = '\t'; 19 | } else if (document.getElementById('twospaces').checked) { 20 | options.indent = ' '; 21 | } 22 | 23 | if (document.getElementById('openbrace-separate-line').checked) { 24 | options.openbrace = 'separate-line'; 25 | } 26 | 27 | if (document.getElementById('autosemicolon').checked) { 28 | options.autosemicolon = true; 29 | } 30 | 31 | if (typeof editor === undefined) { 32 | raw = document.getElementById('raw').value; 33 | } else { 34 | raw = editor.getValue(); 35 | } 36 | 37 | beautified = cssbeautify(raw, options); 38 | 39 | if (typeof viewer === undefined) { 40 | document.getElementById('beautified').value = beautified; 41 | } else { 42 | viewer.setValue(beautified); 43 | } 44 | 45 | formatId = undefined; 46 | }, 42); 47 | } 48 | 49 | window.onload = function () { 50 | 'use strict'; 51 | 52 | editor = CodeMirror.fromTextArea(document.getElementById("raw"), { 53 | lineNumbers: true, 54 | matchBrackets: false, 55 | lineWrapping: true, 56 | tabSize: 8, 57 | onChange: format 58 | }); 59 | 60 | viewer = CodeMirror.fromTextArea(document.getElementById("beautified"), { 61 | lineNumbers: true, 62 | matchBrackets: false, 63 | lineWrapping: true, 64 | readOnly: true, 65 | tabSize: 8 66 | }); 67 | 68 | format(); 69 | }; 70 | 71 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-width: 960px; 3 | background-color: #ffffff; 4 | margin: 0; 5 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | font-size: 13px; 7 | font-weight: normal; 8 | line-height: 18px; 9 | color: #444; 10 | } 11 | 12 | p { 13 | font-size: 13px; 14 | font-weight: normal; 15 | line-height: 18px; 16 | margin-bottom: 9px; 17 | } 18 | 19 | a { 20 | text-decoration: none; 21 | } 22 | 23 | h1 { 24 | margin-bottom: 18px; 25 | font-size: 30px; 26 | line-height: 36px; 27 | font-weight: bold; 28 | color: #444; 29 | } 30 | 31 | h1 small { 32 | font-size: 18px; 33 | color: #ccc; 34 | } 35 | 36 | h3 { 37 | color: #555; 38 | } 39 | 40 | textarea { 41 | font-family: Inconsolata, Monaco, Consolas, "Lucida Console", monospace; 42 | font-size: 14px; 43 | color: #555; 44 | width: 340px; 45 | padding: 7px; 46 | -webkit-appearance: none; 47 | outline: 0; 48 | border: 1px solid #bbb; 49 | } 50 | 51 | .container { 52 | margin-left: auto; 53 | margin-right: auto; 54 | width: 960px; 55 | } 56 | 57 | .container .raw { 58 | width: 380px; 59 | display: inline; 60 | float: left; 61 | margin-left: 10px; 62 | margin-right: 10px; 63 | } 64 | 65 | .container .raw textarea { 66 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); 67 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); 68 | -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); 69 | transition: border linear 0.25s, box-shadow linear 0.25s; 70 | -webkit-transition: border linear 0.25s, box-shadow linear 0.25s; 71 | -moz-transition: border linear 0.25s, box-shadow linear 0.25s; 72 | -o-transition: border linear 0.25s, box-shadow linear 0.25s; 73 | } 74 | 75 | .container .raw textarea:focus { 76 | border-color: rgba(60, 200, 1, 0.8); 77 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(60, 200, 1, 0.4); 78 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(60, 200, 1, 0.4); 79 | -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(60, 200, 1, 0.4); 80 | } 81 | 82 | .container .formatted { 83 | width: 380px; 84 | display: inline; 85 | float: left; 86 | margin-left: 10px; 87 | margin-right: 10px; 88 | } 89 | 90 | .container .formatted textarea { 91 | color: #000; 92 | } 93 | 94 | .container .options { 95 | width: 140px; 96 | display: inline; 97 | float: left; 98 | margin-left: 10px; 99 | margin-right: 10px; 100 | } 101 | 102 | .container .options input { 103 | margin: 5px 5px 5px 0px; 104 | } 105 | 106 | .container .options input[type=radio] { 107 | margin-left: 10px; 108 | } 109 | 110 | .footer { 111 | margin-top: 25px; 112 | color: #555; 113 | text-align: center; 114 | } 115 | 116 | .CodeMirror { 117 | padding: 0; 118 | border: 1px solid #bbb; 119 | } 120 | 121 | .CodeMirror-scroll { 122 | height: 320px; 123 | } 124 | -------------------------------------------------------------------------------- /assets/thirdparty/codemirror.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | line-height: 1em; 3 | font-family: monospace; 4 | 5 | /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */ 6 | position: relative; 7 | /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */ 8 | overflow: hidden; 9 | } 10 | 11 | .CodeMirror-scroll { 12 | overflow: auto; 13 | height: 300px; 14 | /* This is needed to prevent an IE[67] bug where the scrolled content 15 | is visible outside of the scrolling box. */ 16 | position: relative; 17 | outline: none; 18 | } 19 | 20 | /* Vertical scrollbar */ 21 | .CodeMirror-scrollbar { 22 | position: absolute; 23 | right: 0; top: 0; 24 | overflow-x: hidden; 25 | overflow-y: scroll; 26 | z-index: 5; 27 | } 28 | .CodeMirror-scrollbar-inner { 29 | /* This needs to have a nonzero width in order for the scrollbar to appear 30 | in Firefox and IE9. */ 31 | width: 1px; 32 | } 33 | .CodeMirror-scrollbar.cm-sb-overlap { 34 | /* Ensure that the scrollbar appears in Lion, and that it overlaps the content 35 | rather than sitting to the right of it. */ 36 | position: absolute; 37 | z-index: 1; 38 | float: none; 39 | right: 0; 40 | min-width: 12px; 41 | } 42 | .CodeMirror-scrollbar.cm-sb-nonoverlap { 43 | min-width: 12px; 44 | } 45 | .CodeMirror-scrollbar.cm-sb-ie7 { 46 | min-width: 18px; 47 | } 48 | 49 | .CodeMirror-gutter { 50 | position: absolute; left: 0; top: 0; 51 | z-index: 10; 52 | background-color: #f7f7f7; 53 | border-right: 1px solid #eee; 54 | min-width: 2em; 55 | height: 100%; 56 | } 57 | .CodeMirror-gutter-text { 58 | color: #aaa; 59 | text-align: right; 60 | padding: .4em .2em .4em .4em; 61 | white-space: pre !important; 62 | cursor: default; 63 | } 64 | .CodeMirror-lines { 65 | padding: .4em; 66 | white-space: pre; 67 | cursor: text; 68 | } 69 | 70 | .CodeMirror pre { 71 | -moz-border-radius: 0; 72 | -webkit-border-radius: 0; 73 | -o-border-radius: 0; 74 | border-radius: 0; 75 | border-width: 0; margin: 0; padding: 0; background: transparent; 76 | font-family: inherit; 77 | font-size: inherit; 78 | padding: 0; margin: 0; 79 | white-space: pre; 80 | word-wrap: normal; 81 | line-height: inherit; 82 | color: inherit; 83 | } 84 | 85 | .CodeMirror-wrap pre { 86 | word-wrap: break-word; 87 | white-space: pre-wrap; 88 | word-break: normal; 89 | } 90 | .CodeMirror-wrap .CodeMirror-scroll { 91 | overflow-x: hidden; 92 | } 93 | 94 | .CodeMirror textarea { 95 | outline: none !important; 96 | } 97 | 98 | .CodeMirror pre.CodeMirror-cursor { 99 | z-index: 10; 100 | position: absolute; 101 | visibility: hidden; 102 | border-left: 1px solid black; 103 | border-right: none; 104 | width: 0; 105 | } 106 | .cm-keymap-fat-cursor pre.CodeMirror-cursor { 107 | width: auto; 108 | border: 0; 109 | background: transparent; 110 | background: rgba(0, 200, 0, .4); 111 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); 112 | } 113 | /* Kludge to turn off filter in ie9+, which also accepts rgba */ 114 | .cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { 115 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 116 | } 117 | .CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} 118 | .CodeMirror-focused pre.CodeMirror-cursor { 119 | visibility: visible; 120 | } 121 | 122 | div.CodeMirror-selected { background: #d9d9d9; } 123 | .CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } 124 | 125 | .CodeMirror-searching { 126 | background: #ffa; 127 | background: rgba(255, 255, 0, .4); 128 | } 129 | 130 | /* Default theme */ 131 | 132 | .cm-s-default span.cm-keyword {color: #708;} 133 | .cm-s-default span.cm-atom {color: #219;} 134 | .cm-s-default span.cm-number {color: #164;} 135 | .cm-s-default span.cm-def {color: #00f;} 136 | .cm-s-default span.cm-variable {color: black;} 137 | .cm-s-default span.cm-variable-2 {color: #05a;} 138 | .cm-s-default span.cm-variable-3 {color: #085;} 139 | .cm-s-default span.cm-property {color: black;} 140 | .cm-s-default span.cm-operator {color: black;} 141 | .cm-s-default span.cm-comment {color: #a50;} 142 | .cm-s-default span.cm-string {color: #a11;} 143 | .cm-s-default span.cm-string-2 {color: #f50;} 144 | .cm-s-default span.cm-meta {color: #555;} 145 | .cm-s-default span.cm-error {color: #f00;} 146 | .cm-s-default span.cm-qualifier {color: #555;} 147 | .cm-s-default span.cm-builtin {color: #30a;} 148 | .cm-s-default span.cm-bracket {color: #997;} 149 | .cm-s-default span.cm-tag {color: #170;} 150 | .cm-s-default span.cm-attribute {color: #00c;} 151 | .cm-s-default span.cm-header {color: blue;} 152 | .cm-s-default span.cm-quote {color: #090;} 153 | .cm-s-default span.cm-hr {color: #999;} 154 | .cm-s-default span.cm-link {color: #00c;} 155 | 156 | span.cm-header, span.cm-strong {font-weight: bold;} 157 | span.cm-em {font-style: italic;} 158 | span.cm-emstrong {font-style: italic; font-weight: bold;} 159 | span.cm-link {text-decoration: underline;} 160 | 161 | span.cm-invalidchar {color: #f00;} 162 | 163 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 164 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 165 | 166 | @media print { 167 | 168 | /* Hide the cursor when printing */ 169 | .CodeMirror pre.CodeMirror-cursor { 170 | visibility: hidden; 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /assets/thirdparty/css.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("css", function(config) { 2 | var indentUnit = config.indentUnit, type; 3 | 4 | var atMediaTypes = keySet([ 5 | "all", "aural", "braille", "handheld", "print", "projection", "screen", 6 | "tty", "tv", "embossed" 7 | ]); 8 | 9 | var atMediaFeatures = keySet([ 10 | "width", "min-width", "max-width", "height", "min-height", "max-height", 11 | "device-width", "min-device-width", "max-device-width", "device-height", 12 | "min-device-height", "max-device-height", "aspect-ratio", 13 | "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio", 14 | "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color", 15 | "max-color", "color-index", "min-color-index", "max-color-index", 16 | "monochrome", "min-monochrome", "max-monochrome", "resolution", 17 | "min-resolution", "max-resolution", "scan", "grid" 18 | ]); 19 | 20 | var propertyKeywords = keySet([ 21 | "align-content", "align-items", "align-self", "alignment-adjust", 22 | "alignment-baseline", "anchor-point", "animation", "animation-delay", 23 | "animation-direction", "animation-duration", "animation-iteration-count", 24 | "animation-name", "animation-play-state", "animation-timing-function", 25 | "appearance", "azimuth", "backface-visibility", "background", 26 | "background-attachment", "background-clip", "background-color", 27 | "background-image", "background-origin", "background-position", 28 | "background-repeat", "background-size", "baseline-shift", "binding", 29 | "bleed", "bookmark-label", "bookmark-level", "bookmark-state", 30 | "bookmark-target", "border", "border-bottom", "border-bottom-color", 31 | "border-bottom-left-radius", "border-bottom-right-radius", 32 | "border-bottom-style", "border-bottom-width", "border-collapse", 33 | "border-color", "border-image", "border-image-outset", 34 | "border-image-repeat", "border-image-slice", "border-image-source", 35 | "border-image-width", "border-left", "border-left-color", 36 | "border-left-style", "border-left-width", "border-radius", "border-right", 37 | "border-right-color", "border-right-style", "border-right-width", 38 | "border-spacing", "border-style", "border-top", "border-top-color", 39 | "border-top-left-radius", "border-top-right-radius", "border-top-style", 40 | "border-top-width", "border-width", "bottom", "box-decoration-break", 41 | "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", 42 | "caption-side", "clear", "clip", "color", "color-profile", "column-count", 43 | "column-fill", "column-gap", "column-rule", "column-rule-color", 44 | "column-rule-style", "column-rule-width", "column-span", "column-width", 45 | "columns", "content", "counter-increment", "counter-reset", "crop", "cue", 46 | "cue-after", "cue-before", "cursor", "direction", "display", 47 | "dominant-baseline", "drop-initial-after-adjust", 48 | "drop-initial-after-align", "drop-initial-before-adjust", 49 | "drop-initial-before-align", "drop-initial-size", "drop-initial-value", 50 | "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis", 51 | "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", 52 | "float", "float-offset", "font", "font-feature-settings", "font-family", 53 | "font-kerning", "font-language-override", "font-size", "font-size-adjust", 54 | "font-stretch", "font-style", "font-synthesis", "font-variant", 55 | "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", 56 | "font-variant-ligatures", "font-variant-numeric", "font-variant-position", 57 | "font-weight", "grid-cell", "grid-column", "grid-column-align", 58 | "grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow", 59 | "grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span", 60 | "grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens", 61 | "icon", "image-orientation", "image-rendering", "image-resolution", 62 | "inline-box-align", "justify-content", "left", "letter-spacing", 63 | "line-break", "line-height", "line-stacking", "line-stacking-ruby", 64 | "line-stacking-shift", "line-stacking-strategy", "list-style", 65 | "list-style-image", "list-style-position", "list-style-type", "margin", 66 | "margin-bottom", "margin-left", "margin-right", "margin-top", 67 | "marker-offset", "marks", "marquee-direction", "marquee-loop", 68 | "marquee-play-count", "marquee-speed", "marquee-style", "max-height", 69 | "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", 70 | "nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline", 71 | "outline-color", "outline-offset", "outline-style", "outline-width", 72 | "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", 73 | "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", 74 | "page", "page-break-after", "page-break-before", "page-break-inside", 75 | "page-policy", "pause", "pause-after", "pause-before", "perspective", 76 | "perspective-origin", "pitch", "pitch-range", "play-during", "position", 77 | "presentation-level", "punctuation-trim", "quotes", "rendering-intent", 78 | "resize", "rest", "rest-after", "rest-before", "richness", "right", 79 | "rotation", "rotation-point", "ruby-align", "ruby-overhang", 80 | "ruby-position", "ruby-span", "size", "speak", "speak-as", "speak-header", 81 | "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", 82 | "tab-size", "table-layout", "target", "target-name", "target-new", 83 | "target-position", "text-align", "text-align-last", "text-decoration", 84 | "text-decoration-color", "text-decoration-line", "text-decoration-skip", 85 | "text-decoration-style", "text-emphasis", "text-emphasis-color", 86 | "text-emphasis-position", "text-emphasis-style", "text-height", 87 | "text-indent", "text-justify", "text-outline", "text-shadow", 88 | "text-space-collapse", "text-transform", "text-underline-position", 89 | "text-wrap", "top", "transform", "transform-origin", "transform-style", 90 | "transition", "transition-delay", "transition-duration", 91 | "transition-property", "transition-timing-function", "unicode-bidi", 92 | "vertical-align", "visibility", "voice-balance", "voice-duration", 93 | "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", 94 | "voice-volume", "volume", "white-space", "widows", "width", "word-break", 95 | "word-spacing", "word-wrap", "z-index" 96 | ]); 97 | 98 | var colorKeywords = keySet([ 99 | "black", "silver", "gray", "white", "maroon", "red", "purple", "fuchsia", 100 | "green", "lime", "olive", "yellow", "navy", "blue", "teal", "aqua" 101 | ]); 102 | 103 | var valueKeywords = keySet([ 104 | "above", "absolute", "activeborder", "activecaption", "afar", 105 | "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", 106 | "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", 107 | "arabic-indic", "armenian", "asterisks", "auto", "avoid", "background", 108 | "backwards", "baseline", "below", "bidi-override", "binary", "bengali", 109 | "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", 110 | "both", "bottom", "break-all", "break-word", "button", "button-bevel", 111 | "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", 112 | "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", 113 | "cell", "center", "checkbox", "circle", "cjk-earthly-branch", 114 | "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", 115 | "col-resize", "collapse", "compact", "condensed", "contain", "content", 116 | "content-box", "context-menu", "continuous", "copy", "cover", "crop", 117 | "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", 118 | "decimal-leading-zero", "default", "default-button", "destination-atop", 119 | "destination-in", "destination-out", "destination-over", "devanagari", 120 | "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", 121 | "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", 122 | "element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", 123 | "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", 124 | "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", 125 | "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", 126 | "ethiopic-halehame-gez", "ethiopic-halehame-om-et", 127 | "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", 128 | "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", 129 | "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed", 130 | "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", 131 | "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", 132 | "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", 133 | "help", "hidden", "hide", "higher", "highlight", "highlighttext", 134 | "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", 135 | "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", 136 | "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", 137 | "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", 138 | "italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer", 139 | "landscape", "lao", "large", "larger", "left", "level", "lighter", 140 | "line-through", "linear", "lines", "list-item", "listbox", "listitem", 141 | "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", 142 | "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", 143 | "lower-roman", "lowercase", "ltr", "malayalam", "match", 144 | "media-controls-background", "media-current-time-display", 145 | "media-fullscreen-button", "media-mute-button", "media-play-button", 146 | "media-return-to-realtime-button", "media-rewind-button", 147 | "media-seek-back-button", "media-seek-forward-button", "media-slider", 148 | "media-sliderthumb", "media-time-remaining-display", "media-volume-slider", 149 | "media-volume-slider-container", "media-volume-sliderthumb", "medium", 150 | "menu", "menulist", "menulist-button", "menulist-text", 151 | "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", 152 | "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize", 153 | "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", 154 | "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", 155 | "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", 156 | "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", 157 | "outside", "overlay", "overline", "padding", "padding-box", "painted", 158 | "paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait", 159 | "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", 160 | "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", 161 | "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", 162 | "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", 163 | "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield", 164 | "searchfield-cancel-button", "searchfield-decoration", 165 | "searchfield-results-button", "searchfield-results-decoration", 166 | "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", 167 | "single", "skip-white-space", "slide", "slider-horizontal", 168 | "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", 169 | "small", "small-caps", "small-caption", "smaller", "solid", "somali", 170 | "source-atop", "source-in", "source-out", "source-over", "space", "square", 171 | "square-button", "start", "static", "status-bar", "stretch", "stroke", 172 | "sub", "subpixel-antialiased", "super", "sw-resize", "table", 173 | "table-caption", "table-cell", "table-column", "table-column-group", 174 | "table-footer-group", "table-header-group", "table-row", "table-row-group", 175 | "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", 176 | "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", 177 | "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", 178 | "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", 179 | "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", 180 | "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", 181 | "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", 182 | "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", 183 | "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", 184 | "window", "windowframe", "windowtext", "x-large", "x-small", "xor", 185 | "xx-large", "xx-small", "yellow" 186 | ]); 187 | 188 | function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) keys[array[i]] = true; return keys; } 189 | function ret(style, tp) {type = tp; return style;} 190 | 191 | function tokenBase(stream, state) { 192 | var ch = stream.next(); 193 | if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());} 194 | else if (ch == "/" && stream.eat("*")) { 195 | state.tokenize = tokenCComment; 196 | return tokenCComment(stream, state); 197 | } 198 | else if (ch == "<" && stream.eat("!")) { 199 | state.tokenize = tokenSGMLComment; 200 | return tokenSGMLComment(stream, state); 201 | } 202 | else if (ch == "=") ret(null, "compare"); 203 | else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); 204 | else if (ch == "\"" || ch == "'") { 205 | state.tokenize = tokenString(ch); 206 | return state.tokenize(stream, state); 207 | } 208 | else if (ch == "#") { 209 | stream.eatWhile(/[\w\\\-]/); 210 | return ret("atom", "hash"); 211 | } 212 | else if (ch == "!") { 213 | stream.match(/^\s*\w*/); 214 | return ret("keyword", "important"); 215 | } 216 | else if (/\d/.test(ch)) { 217 | stream.eatWhile(/[\w.%]/); 218 | return ret("number", "unit"); 219 | } 220 | else if (ch === "-") { 221 | if (/\d/.test(stream.peek())) { 222 | stream.eatWhile(/[\w.%]/); 223 | return ret("number", "unit"); 224 | } else if (stream.match(/^[^-]+-/)) { 225 | return ret("meta", type); 226 | } 227 | } 228 | else if (/[,+>*\/]/.test(ch)) { 229 | return ret(null, "select-op"); 230 | } 231 | else if (ch == "." && stream.match(/^\w+/)) { 232 | return ret("qualifier", type); 233 | } 234 | else if (ch == ":") { 235 | return ret("operator", ch); 236 | } 237 | else if (/[;{}\[\]\(\)]/.test(ch)) { 238 | return ret(null, ch); 239 | } 240 | else { 241 | stream.eatWhile(/[\w\\\-]/); 242 | return ret("property", "variable"); 243 | } 244 | } 245 | 246 | function tokenCComment(stream, state) { 247 | var maybeEnd = false, ch; 248 | while ((ch = stream.next()) != null) { 249 | if (maybeEnd && ch == "/") { 250 | state.tokenize = tokenBase; 251 | break; 252 | } 253 | maybeEnd = (ch == "*"); 254 | } 255 | return ret("comment", "comment"); 256 | } 257 | 258 | function tokenSGMLComment(stream, state) { 259 | var dashes = 0, ch; 260 | while ((ch = stream.next()) != null) { 261 | if (dashes >= 2 && ch == ">") { 262 | state.tokenize = tokenBase; 263 | break; 264 | } 265 | dashes = (ch == "-") ? dashes + 1 : 0; 266 | } 267 | return ret("comment", "comment"); 268 | } 269 | 270 | function tokenString(quote) { 271 | return function(stream, state) { 272 | var escaped = false, ch; 273 | while ((ch = stream.next()) != null) { 274 | if (ch == quote && !escaped) 275 | break; 276 | escaped = !escaped && ch == "\\"; 277 | } 278 | if (!escaped) state.tokenize = tokenBase; 279 | return ret("string", "string"); 280 | }; 281 | } 282 | 283 | return { 284 | startState: function(base) { 285 | return {tokenize: tokenBase, 286 | baseIndent: base || 0, 287 | stack: []}; 288 | }, 289 | 290 | token: function(stream, state) { 291 | 292 | // Use these terms when applicable (see http://www.xanthir.com/blog/b4E50) 293 | // 294 | // rule** or **ruleset: 295 | // A selector + braces combo, or an at-rule. 296 | // 297 | // declaration block: 298 | // A sequence of declarations. 299 | // 300 | // declaration: 301 | // A property + colon + value combo. 302 | // 303 | // property value: 304 | // The entire value of a property. 305 | // 306 | // component value: 307 | // A single piece of a property value. Like the 5px in 308 | // text-shadow: 0 0 5px blue;. Can also refer to things that are 309 | // multiple terms, like the 1-4 terms that make up the background-size 310 | // portion of the background shorthand. 311 | // 312 | // term: 313 | // The basic unit of author-facing CSS, like a single number (5), 314 | // dimension (5px), string ("foo"), or function. Officially defined 315 | // by the CSS 2.1 grammar (look for the 'term' production) 316 | // 317 | // 318 | // simple selector: 319 | // A single atomic selector, like a type selector, an attr selector, a 320 | // class selector, etc. 321 | // 322 | // compound selector: 323 | // One or more simple selectors without a combinator. div.example is 324 | // compound, div > .example is not. 325 | // 326 | // complex selector: 327 | // One or more compound selectors chained with combinators. 328 | // 329 | // combinator: 330 | // The parts of selectors that express relationships. There are four 331 | // currently - the space (descendant combinator), the greater-than 332 | // bracket (child combinator), the plus sign (next sibling combinator), 333 | // and the tilda (following sibling combinator). 334 | // 335 | // sequence of selectors: 336 | // One or more of the named type of selector chained with commas. 337 | 338 | if (stream.eatSpace()) return null; 339 | var style = state.tokenize(stream, state); 340 | 341 | // Changing style returned based on context 342 | var context = state.stack[state.stack.length-1]; 343 | if (style == "property") { 344 | if (context == "propertyValue"){ 345 | if (valueKeywords[stream.current()]) { 346 | style = "string-2"; 347 | } else if (colorKeywords[stream.current()]) { 348 | style = "keyword"; 349 | } else { 350 | style = "variable-2"; 351 | } 352 | } else if (context == "rule") { 353 | if (!propertyKeywords[stream.current()]) { 354 | style += " error"; 355 | } 356 | } else if (!context || context == "@media{") { 357 | style = "tag"; 358 | } else if (context == "@media") { 359 | if (atMediaTypes[stream.current()]) { 360 | style = "attribute"; // Known attribute 361 | } else if (/^(only|not)$/i.test(stream.current())) { 362 | style = "keyword"; 363 | } else if (stream.current().toLowerCase() == "and") { 364 | style = "error"; // "and" is only allowed in @mediaType 365 | } else if (atMediaFeatures[stream.current()]) { 366 | style = "error"; // Known property, should be in @mediaType( 367 | } else { 368 | // Unknown, expecting keyword or attribute, assuming attribute 369 | style = "attribute error"; 370 | } 371 | } else if (context == "@mediaType") { 372 | if (atMediaTypes[stream.current()]) { 373 | style = "attribute"; 374 | } else if (stream.current().toLowerCase() == "and") { 375 | style = "operator"; 376 | } else if (/^(only|not)$/i.test(stream.current())) { 377 | style = "error"; // Only allowed in @media 378 | } else if (atMediaFeatures[stream.current()]) { 379 | style = "error"; // Known property, should be in parentheses 380 | } else { 381 | // Unknown attribute or property, but expecting property (preceded 382 | // by "and"). Should be in parentheses 383 | style = "error"; 384 | } 385 | } else if (context == "@mediaType(") { 386 | if (propertyKeywords[stream.current()]) { 387 | // do nothing, remains "property" 388 | } else if (atMediaTypes[stream.current()]) { 389 | style = "error"; // Known property, should be in parentheses 390 | } else if (stream.current().toLowerCase() == "and") { 391 | style = "operator"; 392 | } else if (/^(only|not)$/i.test(stream.current())) { 393 | style = "error"; // Only allowed in @media 394 | } else { 395 | style += " error"; 396 | } 397 | } else { 398 | style = "error"; 399 | } 400 | } else if (style == "atom") { 401 | if(!context || context == "@media{") { 402 | style = "builtin"; 403 | } else if (context == "propertyValue") { 404 | if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { 405 | style += " error"; 406 | } 407 | } else { 408 | style = "error"; 409 | } 410 | } else if (context == "@media" && type == "{") { 411 | style = "error"; 412 | } 413 | 414 | // Push/pop context stack 415 | if (type == "{") { 416 | if (context == "@media" || context == "@mediaType") { 417 | state.stack.pop(); 418 | state.stack[state.stack.length-1] = "@media{"; 419 | } 420 | else state.stack.push("rule"); 421 | } 422 | else if (type == "}") { 423 | state.stack.pop(); 424 | if (context == "propertyValue") state.stack.pop(); 425 | } 426 | else if (type == "@media") state.stack.push("@media"); 427 | else if (context == "@media" && /\b(keyword|attribute)\b/.test(style)) 428 | state.stack.push("@mediaType"); 429 | else if (context == "@mediaType" && stream.current() == ",") state.stack.pop(); 430 | else if (context == "@mediaType" && type == "(") state.stack.push("@mediaType("); 431 | else if (context == "@mediaType(" && type == ")") state.stack.pop(); 432 | else if (context == "rule" && type == ":") state.stack.push("propertyValue"); 433 | else if (context == "propertyValue" && type == ";") state.stack.pop(); 434 | return style; 435 | }, 436 | 437 | indent: function(state, textAfter) { 438 | var n = state.stack.length; 439 | if (/^\}/.test(textAfter)) 440 | n -= state.stack[state.stack.length-1] == "propertyValue" ? 2 : 1; 441 | return state.baseIndent + n * indentUnit; 442 | }, 443 | 444 | electricChars: "}" 445 | }; 446 | }); 447 | 448 | CodeMirror.defineMIME("text/css", "css"); 449 | -------------------------------------------------------------------------------- /bin/cssbeautify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright (C) 2012 Sencha Inc. 4 | 5 | Author: Ariya Hidayat. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | 26 | /*jslint sloppy:true node:true */ 27 | 28 | var fs, cssbeautify, fname, content, options, style; 29 | 30 | fs = require('fs'); 31 | cssbeautify = require('cssbeautify'); 32 | 33 | function showUsage() { 34 | console.log('Usage:'); 35 | console.log(' cssbeautify [options] style.css'); 36 | console.log(); 37 | console.log('Available options:'); 38 | console.log(); 39 | console.log(' -v, --version Shows program version'); 40 | console.log(); 41 | process.exit(1); 42 | } 43 | 44 | if (process.argv.length <= 2) { 45 | showUsage(); 46 | } 47 | 48 | options = {}; 49 | 50 | process.argv.splice(2).forEach(function (entry) { 51 | 52 | if (entry === '-h' || entry === '--help') { 53 | showUsage(); 54 | } else if (entry === '-v' || entry === '--version') { 55 | // Keep in sync with package.json 56 | console.log('CSS Beautify version 0.3.0'); 57 | console.log(); 58 | process.exit(0); 59 | } else if (entry.slice(0, 2) === '--') { 60 | console.log('Error: unknown option ' + entry + '.'); 61 | process.exit(1); 62 | } else if (typeof fname === 'string') { 63 | console.log('Error: more than one input file.'); 64 | process.exit(1); 65 | } else { 66 | fname = entry; 67 | } 68 | }); 69 | 70 | if (typeof fname !== 'string') { 71 | console.log('Error: no input file.'); 72 | process.exit(1); 73 | } 74 | 75 | try { 76 | content = fs.readFileSync(fname, 'utf-8'); 77 | style = cssbeautify(content); 78 | console.log(style); 79 | } catch (e) { 80 | console.log('Error: ' + e.message); 81 | process.exit(1); 82 | } 83 | 84 | -------------------------------------------------------------------------------- /cssbeautify.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2013 Sencha Inc. 3 | Copyright (C) 2012 Sencha Inc. 4 | Copyright (C) 2011 Sencha Inc. 5 | 6 | Author: Ariya Hidayat. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | */ 26 | 27 | /*jslint continue: true, indent: 4 */ 28 | /*global exports:true, module:true, window:true */ 29 | 30 | (function () { 31 | 32 | 'use strict'; 33 | 34 | function cssbeautify(style, opt) { 35 | 36 | var options, index = 0, length = style.length, blocks, formatted = '', 37 | ch, ch2, str, state, State, depth, quote, comment, 38 | openbracesuffix = true, 39 | autosemicolon = false, 40 | trimRight; 41 | 42 | options = arguments.length > 1 ? opt : {}; 43 | if (typeof options.indent === 'undefined') { 44 | options.indent = ' '; 45 | } 46 | if (typeof options.openbrace === 'string') { 47 | openbracesuffix = (options.openbrace === 'end-of-line'); 48 | } 49 | if (typeof options.autosemicolon === 'boolean') { 50 | autosemicolon = options.autosemicolon; 51 | } 52 | 53 | function isWhitespace(c) { 54 | return (c === ' ') || (c === '\n') || (c === '\t') || (c === '\r') || (c === '\f'); 55 | } 56 | 57 | function isQuote(c) { 58 | return (c === '\'') || (c === '"'); 59 | } 60 | 61 | // FIXME: handle Unicode characters 62 | function isName(c) { 63 | return (ch >= 'a' && ch <= 'z') || 64 | (ch >= 'A' && ch <= 'Z') || 65 | (ch >= '0' && ch <= '9') || 66 | '-_*.:#[]'.indexOf(c) >= 0; 67 | } 68 | 69 | function appendIndent() { 70 | var i; 71 | for (i = depth; i > 0; i -= 1) { 72 | formatted += options.indent; 73 | } 74 | } 75 | 76 | function openBlock() { 77 | formatted = trimRight(formatted); 78 | if (openbracesuffix) { 79 | formatted += ' {'; 80 | } else { 81 | formatted += '\n'; 82 | appendIndent(); 83 | formatted += '{'; 84 | } 85 | if (ch2 !== '\n') { 86 | formatted += '\n'; 87 | } 88 | depth += 1; 89 | } 90 | 91 | function closeBlock() { 92 | var last; 93 | depth -= 1; 94 | formatted = trimRight(formatted); 95 | 96 | if (formatted.length > 0 && autosemicolon) { 97 | last = formatted.charAt(formatted.length - 1); 98 | if (last !== ';' && last !== '{') { 99 | formatted += ';'; 100 | } 101 | } 102 | 103 | formatted += '\n'; 104 | appendIndent(); 105 | formatted += '}'; 106 | blocks.push(formatted); 107 | formatted = ''; 108 | } 109 | 110 | if (String.prototype.trimRight) { 111 | trimRight = function (s) { 112 | return s.trimRight(); 113 | }; 114 | } else { 115 | // old Internet Explorer 116 | trimRight = function (s) { 117 | return s.replace(/\s+$/, ''); 118 | }; 119 | } 120 | 121 | State = { 122 | Start: 0, 123 | AtRule: 1, 124 | Block: 2, 125 | Selector: 3, 126 | Ruleset: 4, 127 | Property: 5, 128 | Separator: 6, 129 | Expression: 7, 130 | URL: 8 131 | }; 132 | 133 | depth = 0; 134 | state = State.Start; 135 | comment = false; 136 | blocks = []; 137 | 138 | // We want to deal with LF (\n) only 139 | style = style.replace(/\r\n/g, '\n'); 140 | 141 | while (index < length) { 142 | ch = style.charAt(index); 143 | ch2 = style.charAt(index + 1); 144 | index += 1; 145 | 146 | // Inside a string literal? 147 | if (isQuote(quote)) { 148 | formatted += ch; 149 | if (ch === quote) { 150 | quote = null; 151 | } 152 | if (ch === '\\' && ch2 === quote) { 153 | // Don't treat escaped character as the closing quote 154 | formatted += ch2; 155 | index += 1; 156 | } 157 | continue; 158 | } 159 | 160 | // Starting a string literal? 161 | if (isQuote(ch)) { 162 | formatted += ch; 163 | quote = ch; 164 | continue; 165 | } 166 | 167 | // Comment 168 | if (comment) { 169 | formatted += ch; 170 | if (ch === '*' && ch2 === '/') { 171 | comment = false; 172 | formatted += ch2; 173 | index += 1; 174 | } 175 | continue; 176 | } 177 | if (ch === '/' && ch2 === '*') { 178 | comment = true; 179 | formatted += ch; 180 | formatted += ch2; 181 | index += 1; 182 | continue; 183 | } 184 | 185 | if (state === State.Start) { 186 | 187 | if (blocks.length === 0) { 188 | if (isWhitespace(ch) && formatted.length === 0) { 189 | continue; 190 | } 191 | } 192 | 193 | // Copy white spaces and control characters 194 | if (ch <= ' ' || ch.charCodeAt(0) >= 128) { 195 | state = State.Start; 196 | formatted += ch; 197 | continue; 198 | } 199 | 200 | // Selector or at-rule 201 | if (isName(ch) || (ch === '@')) { 202 | 203 | // Clear trailing whitespaces and linefeeds. 204 | str = trimRight(formatted); 205 | 206 | if (str.length === 0) { 207 | // If we have empty string after removing all the trailing 208 | // spaces, that means we are right after a block. 209 | // Ensure a blank line as the separator. 210 | if (blocks.length > 0) { 211 | formatted = '\n\n'; 212 | } 213 | } else { 214 | // After finishing a ruleset or directive statement, 215 | // there should be one blank line. 216 | if (str.charAt(str.length - 1) === '}' || 217 | str.charAt(str.length - 1) === ';') { 218 | 219 | formatted = str + '\n\n'; 220 | } else { 221 | // After block comment, keep all the linefeeds but 222 | // start from the first column (remove whitespaces prefix). 223 | while (true) { 224 | ch2 = formatted.charAt(formatted.length - 1); 225 | if (ch2 !== ' ' && ch2.charCodeAt(0) !== 9) { 226 | break; 227 | } 228 | formatted = formatted.substr(0, formatted.length - 1); 229 | } 230 | } 231 | } 232 | formatted += ch; 233 | state = (ch === '@') ? State.AtRule : State.Selector; 234 | continue; 235 | } 236 | } 237 | 238 | if (state === State.AtRule) { 239 | 240 | // ';' terminates a statement. 241 | if (ch === ';') { 242 | formatted += ch; 243 | state = State.Start; 244 | continue; 245 | } 246 | 247 | // '{' starts a block 248 | if (ch === '{') { 249 | str = trimRight(formatted); 250 | openBlock(); 251 | state = (str === '@font-face') ? State.Ruleset : State.Block; 252 | continue; 253 | } 254 | 255 | formatted += ch; 256 | continue; 257 | } 258 | 259 | if (state === State.Block) { 260 | 261 | // Selector 262 | if (isName(ch)) { 263 | 264 | // Clear trailing whitespaces and linefeeds. 265 | str = trimRight(formatted); 266 | 267 | if (str.length === 0) { 268 | // If we have empty string after removing all the trailing 269 | // spaces, that means we are right after a block. 270 | // Ensure a blank line as the separator. 271 | if (blocks.length > 0) { 272 | formatted = '\n\n'; 273 | } 274 | } else { 275 | // Insert blank line if necessary. 276 | if (str.charAt(str.length - 1) === '}') { 277 | formatted = str + '\n\n'; 278 | } else { 279 | // After block comment, keep all the linefeeds but 280 | // start from the first column (remove whitespaces prefix). 281 | while (true) { 282 | ch2 = formatted.charAt(formatted.length - 1); 283 | if (ch2 !== ' ' && ch2.charCodeAt(0) !== 9) { 284 | break; 285 | } 286 | formatted = formatted.substr(0, formatted.length - 1); 287 | } 288 | } 289 | } 290 | 291 | appendIndent(); 292 | formatted += ch; 293 | state = State.Selector; 294 | continue; 295 | } 296 | 297 | // '}' resets the state. 298 | if (ch === '}') { 299 | closeBlock(); 300 | state = State.Start; 301 | continue; 302 | } 303 | 304 | formatted += ch; 305 | continue; 306 | } 307 | 308 | if (state === State.Selector) { 309 | 310 | // '{' starts the ruleset. 311 | if (ch === '{') { 312 | openBlock(); 313 | state = State.Ruleset; 314 | continue; 315 | } 316 | 317 | // '}' resets the state. 318 | if (ch === '}') { 319 | closeBlock(); 320 | state = State.Start; 321 | continue; 322 | } 323 | 324 | formatted += ch; 325 | continue; 326 | } 327 | 328 | if (state === State.Ruleset) { 329 | 330 | // '}' finishes the ruleset. 331 | if (ch === '}') { 332 | closeBlock(); 333 | state = State.Start; 334 | if (depth > 0) { 335 | state = State.Block; 336 | } 337 | continue; 338 | } 339 | 340 | // Make sure there is no blank line or trailing spaces inbetween 341 | if (ch === '\n') { 342 | formatted = trimRight(formatted); 343 | formatted += '\n'; 344 | continue; 345 | } 346 | 347 | // property name 348 | if (!isWhitespace(ch)) { 349 | formatted = trimRight(formatted); 350 | formatted += '\n'; 351 | appendIndent(); 352 | formatted += ch; 353 | state = State.Property; 354 | continue; 355 | } 356 | formatted += ch; 357 | continue; 358 | } 359 | 360 | if (state === State.Property) { 361 | 362 | // ':' concludes the property. 363 | if (ch === ':') { 364 | formatted = trimRight(formatted); 365 | formatted += ': '; 366 | state = State.Expression; 367 | if (isWhitespace(ch2)) { 368 | state = State.Separator; 369 | } 370 | continue; 371 | } 372 | 373 | // '}' finishes the ruleset. 374 | if (ch === '}') { 375 | closeBlock(); 376 | state = State.Start; 377 | if (depth > 0) { 378 | state = State.Block; 379 | } 380 | continue; 381 | } 382 | 383 | formatted += ch; 384 | continue; 385 | } 386 | 387 | if (state === State.Separator) { 388 | 389 | // Non-whitespace starts the expression. 390 | if (!isWhitespace(ch)) { 391 | formatted += ch; 392 | state = State.Expression; 393 | continue; 394 | } 395 | 396 | // Anticipate string literal. 397 | if (isQuote(ch2)) { 398 | state = State.Expression; 399 | } 400 | 401 | continue; 402 | } 403 | 404 | if (state === State.Expression) { 405 | 406 | // '}' finishes the ruleset. 407 | if (ch === '}') { 408 | closeBlock(); 409 | state = State.Start; 410 | if (depth > 0) { 411 | state = State.Block; 412 | } 413 | continue; 414 | } 415 | 416 | // ';' completes the declaration. 417 | if (ch === ';') { 418 | formatted = trimRight(formatted); 419 | formatted += ';\n'; 420 | state = State.Ruleset; 421 | continue; 422 | } 423 | 424 | formatted += ch; 425 | 426 | if (ch === '(') { 427 | if (formatted.charAt(formatted.length - 2) === 'l' && 428 | formatted.charAt(formatted.length - 3) === 'r' && 429 | formatted.charAt(formatted.length - 4) === 'u') { 430 | 431 | // URL starts with '(' and closes with ')'. 432 | state = State.URL; 433 | continue; 434 | } 435 | } 436 | 437 | continue; 438 | } 439 | 440 | if (state === State.URL) { 441 | 442 | 443 | // ')' finishes the URL (only if it is not escaped). 444 | if (ch === ')' && formatted.charAt(formatted.length - 1 !== '\\')) { 445 | formatted += ch; 446 | state = State.Expression; 447 | continue; 448 | } 449 | } 450 | 451 | // The default action is to copy the character (to prevent 452 | // infinite loop). 453 | formatted += ch; 454 | } 455 | 456 | formatted = blocks.join('') + formatted; 457 | 458 | return formatted; 459 | } 460 | 461 | if (typeof exports !== 'undefined') { 462 | // Node.js module. 463 | module.exports = exports = cssbeautify; 464 | } else if (typeof window === 'object') { 465 | // Browser loading. 466 | window.cssbeautify = cssbeautify; 467 | } 468 | 469 | }()); 470 | -------------------------------------------------------------------------------- /images/ribbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/senchalabs/cssbeautify/4a1f5d8aff8886c6790655acbccd9b478f8fee32/images/ribbon.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CSS Beautify 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |

CSS Beautify automatically formats your style to be consistent and easy to read

17 | 25 | 26 |

For a command-line use, install Node.js cssbeautify package.

27 | 28 |
29 |

Type any unformatted CSS:

30 | 31 |
32 | 33 |
34 |

Beautified CSS:

35 | 36 |
37 | 38 |
39 |

Options:

40 |

Indent with
41 |
42 |
43 | 44 |

45 |

Open curly brace:
46 |
47 | 48 |

49 |

50 |
51 | 52 |
53 | 54 | 55 |

Fork me on GitHub

56 |
57 | 58 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cssbeautify", 3 | "description": "Reindent and reformat CSS.", 4 | "version": "0.3.0", 5 | "homepage": "http://cssbeautify.com", 6 | "author": { 7 | "name": "Ariya Hidayat", 8 | "email": "ariya@sencha.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/senchalabs/cssbeautify" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/senchalabs/cssbeautify/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/senchalabs/cssbeautify/blob/master/README.md" 21 | } 22 | ], 23 | "main": "cssbeautify.js", 24 | "bin": "bin/cssbeautify", 25 | "engines": { 26 | "node": "*" 27 | }, 28 | "scripts": { 29 | "test": "node test/runner.js" 30 | }, 31 | "keywords": [ 32 | "cssbeautify", 33 | "css", 34 | "formatter" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test Suite for CSS Beautify 6 | 7 | 8 | 9 | 10 | 34 | 35 | 36 | 37 |
38 | 39 |

Test Suite for CSS Beautify

40 | 41 |
42 |
43 | 44 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /test/runner.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2012 Sencha Inc. 3 | 4 | Author: Ariya Hidayat. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | /*jslint browser:true node:true */ 26 | /*global cssbeautify:true, tests:true */ 27 | 28 | var cssbeautify; 29 | 30 | if (typeof require === 'function') { 31 | cssbeautify = require('../cssbeautify'); 32 | } 33 | 34 | function runTest(name, input, expected, options, reporter) { 35 | 'use strict'; 36 | var actual; 37 | 38 | if (typeof input !== 'string') { 39 | input = input.join('\n'); 40 | } 41 | if (typeof expected !== 'string') { 42 | expected = expected.join('\n'); 43 | } 44 | 45 | actual = cssbeautify(input, options); 46 | 47 | if (actual === expected) { 48 | reporter.reportSuccess(name, input, actual, expected); 49 | } else { 50 | reporter.reportFailure(name, input, actual, expected); 51 | } 52 | } 53 | 54 | function runTests(reporter) { 55 | 'use strict'; 56 | var i, entry, options, input, expected; 57 | 58 | for (i in tests) { 59 | if (tests.hasOwnProperty(i)) { 60 | entry = tests[i]; 61 | options = entry.options || {}; 62 | input = entry.input; 63 | expected = entry.expected; 64 | runTest(i, input, expected, options, reporter); 65 | } 66 | } 67 | } 68 | 69 | function executeBrowser() { 70 | 'use strict'; 71 | 72 | runTests({ 73 | reportSuccess: function (name, input, actual) { 74 | var e; 75 | 76 | e = document.createElement('h2'); 77 | e.textContent = name; 78 | document.getElementById('result').appendChild(e); 79 | 80 | e = document.createElement('pre'); 81 | e.textContent = input; 82 | e.setAttribute('class', 'source'); 83 | document.getElementById('result').appendChild(e); 84 | 85 | e = document.createElement('p'); 86 | e.textContent = 'Formatted to'; 87 | document.getElementById('result').appendChild(e); 88 | e = document.createElement('pre'); 89 | e.textContent = actual; 90 | //e.setAttribute('class', 'ref'); 91 | document.getElementById('result').appendChild(e); 92 | }, 93 | 94 | reportFailure: function (name, input, actual, expected) { 95 | var e; 96 | 97 | e = document.createElement('h2'); 98 | e.setAttribute('class', 'fail'); 99 | e.textContent = 'FAIL: ' + name; 100 | document.getElementById('result').appendChild(e); 101 | 102 | e = document.createElement('pre'); 103 | e.textContent = input; 104 | e.setAttribute('class', 'source'); 105 | document.getElementById('result').appendChild(e); 106 | 107 | e = document.createElement('p'); 108 | e.textContent = 'Expected:'; 109 | document.getElementById('result').appendChild(e); 110 | e = document.createElement('pre'); 111 | e.textContent = expected; 112 | e.setAttribute('class', 'ref'); 113 | document.getElementById('result').appendChild(e); 114 | 115 | e = document.createElement('p'); 116 | e.textContent = 'Actual:'; 117 | document.getElementById('result').appendChild(e); 118 | e = document.createElement('pre'); 119 | e.textContent = actual; 120 | e.setAttribute('class', 'result'); 121 | document.getElementById('result').appendChild(e); 122 | } 123 | }); 124 | } 125 | 126 | function executeCommandLine() { 127 | 'use strict'; 128 | 129 | var vm = require('vm'), 130 | fs = require('fs'); 131 | 132 | vm.runInThisContext(fs.readFileSync(__dirname + '/test.js', 'utf-8')); 133 | 134 | runTests({ 135 | reportSuccess: function (name, input, actual) { 136 | console.log('Testing', name, 'OK'); 137 | }, 138 | 139 | reportFailure: function (name, input, actual, expected) { 140 | console.log('Testing', name, 'FAILED'); 141 | console.log(); 142 | console.log('Style:'); 143 | console.log(input); 144 | console.log(); 145 | console.log('Expected:'); 146 | console.log(expected); 147 | console.log(); 148 | console.log('Actual:'); 149 | console.log(actual); 150 | console.log(); 151 | console.log(); 152 | } 153 | }); 154 | } 155 | 156 | if (typeof module !== 'undefined' && module.exports) { 157 | executeCommandLine(); 158 | console.log(); 159 | } 160 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2013 Sencha Inc. 3 | Copyright (C) 2012 Sencha Inc. 4 | Copyright (C) 2011 Sencha Inc. 5 | 6 | Author: Ariya Hidayat. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | */ 26 | 27 | var tests = { 28 | 29 | 'Simple style': { 30 | input: [ 31 | 'menu { color: blue; }', 32 | '', 33 | 'box { border-radius: 4px; background-color: red }', 34 | '', 35 | 'a { color: green }', 36 | 'b { color: red }' 37 | ], 38 | expected: [ 39 | 'menu {', 40 | ' color: blue;', 41 | '}', 42 | '', 43 | 'box {', 44 | ' border-radius: 4px;', 45 | ' background-color: red', 46 | '}', 47 | '', 48 | 'a {', 49 | ' color: green', 50 | '}', 51 | '', 52 | 'b {', 53 | ' color: red', 54 | '}' 55 | ] 56 | }, 57 | 58 | 'Block comment': { 59 | input: [ 60 | '/* line comment */', 61 | 'navigation { color: blue }', 62 | '', 63 | 'menu {', 64 | ' /* line comment inside */', 65 | ' border: 2px', 66 | '}', 67 | '', 68 | '/* block', 69 | ' comment */', 70 | 'sidebar { color: red }', 71 | '', 72 | 'invisible {', 73 | ' /* block', 74 | ' * comment', 75 | ' * inside */', 76 | ' color: #eee', 77 | '}' 78 | ], 79 | expected: [ 80 | '/* line comment */', 81 | 'navigation {', 82 | ' color: blue', 83 | '}', 84 | '', 85 | 'menu {', 86 | ' /* line comment inside */', 87 | ' border: 2px', 88 | '}', 89 | '', 90 | '/* block', 91 | ' comment */', 92 | 'sidebar {', 93 | ' color: red', 94 | '}', 95 | '', 96 | 'invisible {', 97 | ' /* block', 98 | ' * comment', 99 | ' * inside */', 100 | ' color: #eee', 101 | '}' 102 | ] 103 | }, 104 | 105 | 'Indentation': { 106 | input: [ 107 | ' navigation {', 108 | ' color: blue', 109 | ' }' 110 | ], 111 | expected: [ 112 | 'navigation {', 113 | ' color: blue', 114 | '}' 115 | ] 116 | }, 117 | 118 | 'Blank lines and spaces': { 119 | input: [ 120 | '/* only one blank line between */', 121 | 'menu { color: red }', 122 | '', 123 | '', 124 | '', 125 | '', 126 | 'navi { color: black }', 127 | '', 128 | '/* automatically insert a blank line */', 129 | 'button { border: 1px } sidebar { color: #ffe }', 130 | '', 131 | '/* always whitespace before { */', 132 | 'hidden{opacity:0%}', 133 | '', 134 | '/* no blank lines inside ruleset */', 135 | 'imprint {', 136 | ' color: blue;', 137 | '', 138 | '', 139 | ' opacity: 0.5;', 140 | '', 141 | ' font-size: small', 142 | '}', 143 | '', 144 | '/* before colon: no space, after colon: one space only */', 145 | 'footer {', 146 | ' font-family: Arial;', 147 | '', 148 | ' float :right;', 149 | ' }' 150 | ], 151 | expected: [ 152 | '/* only one blank line between */', 153 | 'menu {', 154 | ' color: red', 155 | '}', 156 | '', 157 | 'navi {', 158 | ' color: black', 159 | '}', 160 | '', 161 | '/* automatically insert a blank line */', 162 | 'button {', 163 | ' border: 1px', 164 | '}', 165 | '', 166 | 'sidebar {', 167 | ' color: #ffe', 168 | '}', 169 | '', 170 | '/* always whitespace before { */', 171 | 'hidden {', 172 | ' opacity: 0%', 173 | '}', 174 | '', 175 | '/* no blank lines inside ruleset */', 176 | 'imprint {', 177 | ' color: blue;', 178 | ' opacity: 0.5;', 179 | ' font-size: small', 180 | '}', 181 | '', 182 | '/* before colon: no space, after colon: one space only */', 183 | 'footer {', 184 | ' font-family: Arial;', 185 | ' float: right;', 186 | '}' 187 | ] 188 | }, 189 | 190 | 'Quoted string': { 191 | input: [ 192 | 'nav:after{content:\'}\'}', 193 | 'nav:before{content:"}"}' 194 | ], 195 | expected: [ 196 | 'nav:after {', 197 | ' content: \'}\'', 198 | '}', 199 | '', 200 | 'nav:before {', 201 | ' content: "}"', 202 | '}', 203 | ] 204 | }, 205 | 206 | 'Selectors': { 207 | input: [ 208 | '* { border: 0px solid blue; }', 209 | 'div[class="{}"] { color: red; }', 210 | 'a[id=\\"foo"] { padding: 0; }', 211 | '[id=\\"foo"] { margin: 0; }', 212 | '#menu, #nav, #footer { color: royalblue; }' 213 | ], 214 | expected: [ 215 | '* {', 216 | ' border: 0px solid blue;', 217 | '}', 218 | '', 219 | 'div[class="{}"] {', 220 | ' color: red;', 221 | '}', 222 | '', 223 | 'a[id=\\"foo"] {', 224 | ' padding: 0;', 225 | '}', 226 | '', 227 | '[id=\\"foo"] {', 228 | ' margin: 0;', 229 | '}', 230 | '', 231 | '#menu, #nav, #footer {', 232 | ' color: royalblue;', 233 | '}' 234 | ] 235 | }, 236 | 237 | 'Empty rule': { 238 | input: [ 239 | 'menu{}' 240 | ], 241 | options: { 242 | autosemicolon: true 243 | }, 244 | expected: [ 245 | 'menu {', 246 | '}' 247 | ] 248 | }, 249 | 250 | '@font-face directive': { 251 | input: [ 252 | '@font-face{ color: black; background-color:blue}' 253 | ], 254 | options: { 255 | autosemicolon: true 256 | }, 257 | expected: [ 258 | '@font-face {', 259 | ' color: black;', 260 | ' background-color: blue;', 261 | '}' 262 | ] 263 | }, 264 | 265 | '@import directive': { 266 | input: [ 267 | 'menu{background-color:red} @import url(\'foobar.css\') screen;', 268 | 'nav{margin:0}' 269 | ], 270 | expected: [ 271 | 'menu {', 272 | ' background-color: red', 273 | '}', 274 | '', 275 | '@import url(\'foobar.css\') screen;', 276 | '', 277 | 'nav {', 278 | ' margin: 0', 279 | '}' 280 | ] 281 | }, 282 | 283 | '@media directive': { 284 | input: [ 285 | '@import "subs.css";', 286 | '@import "print-main.css" print;', 287 | '@media print {', 288 | ' body { font-size: 10pt }', 289 | ' nav { color: blue; }', 290 | '}', 291 | 'h1 {color: red; }' 292 | ], 293 | expected: [ 294 | '@import "subs.css";', 295 | '', 296 | '@import "print-main.css" print;', 297 | '', 298 | '@media print {', 299 | ' body {', 300 | ' font-size: 10pt', 301 | ' }', 302 | '', 303 | ' nav {', 304 | ' color: blue;', 305 | ' }', 306 | '}', 307 | '', 308 | 'h1 {', 309 | ' color: red;', 310 | '}' 311 | ] 312 | }, 313 | 314 | '@media directive (auto-semicolon)': { 315 | input: [ 316 | '@media screen {', 317 | ' menu { color: navy }', 318 | '}' 319 | ], 320 | options: { 321 | autosemicolon: true 322 | }, 323 | expected: [ 324 | '@media screen {', 325 | ' menu {', 326 | ' color: navy;', 327 | ' }', 328 | '}' 329 | ] 330 | }, 331 | 332 | 'URL': { 333 | input: [ 334 | 'menu { background-image: url(data:image/png;base64,AAAAAAA); }' 335 | ], 336 | expected: [ 337 | 'menu {', 338 | ' background-image: url(data:image/png;base64,AAAAAAA);', 339 | '}' 340 | ] 341 | }, 342 | 343 | 'Animation keyframe': { 344 | input: [ 345 | '@-webkit-keyframes anim {', 346 | '0% { -webkit-transform: translate3d(0px, 0px, 0px); }', 347 | '100% { -webkit-transform: translate3d(150px, 0px, 0px) }}' 348 | ], 349 | expected: [ 350 | '@-webkit-keyframes anim {', 351 | ' 0% {', 352 | ' -webkit-transform: translate3d(0px, 0px, 0px);', 353 | ' }', 354 | '', 355 | ' 100% {', 356 | ' -webkit-transform: translate3d(150px, 0px, 0px)', 357 | ' }', 358 | '}' 359 | ] 360 | } 361 | 362 | }; 363 | 364 | --------------------------------------------------------------------------------