├── messages ├── 1.5.0.txt ├── 1.6.1.txt ├── 1.6.0.txt ├── 1.5.1.txt ├── 1.4.0.txt ├── 1.7.0.txt └── 1.8.0.txt ├── Default (OSX).sublime-keymap ├── Default (Linux).sublime-keymap ├── Default (Windows).sublime-keymap ├── messages.json ├── eCSStractor.sublime-commands ├── Context.sublime-menu ├── LICENSE.md ├── eCSStractor.sublime-settings ├── Main.sublime-menu ├── README.md └── eCSStractor.py /messages/1.5.0.txt: -------------------------------------------------------------------------------- 1 | Added `destination` option to set result's destination: new tab or copy to clipboard. 2 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["super+shift+x"], 4 | "command": "ecsstractor" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+shift+x"], 4 | "command": "ecsstractor" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+shift+x"], 4 | "command": "ecsstractor" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /messages/1.6.1.txt: -------------------------------------------------------------------------------- 1 | Version 1.6.1 2 | 3 | * `attribute` option renamed to `attributes`. By default it's set to `["class", "className"]`. 4 | -------------------------------------------------------------------------------- /messages/1.6.0.txt: -------------------------------------------------------------------------------- 1 | Version 1.6.0 2 | 3 | * Add new option `attribute`. By default it's set to `class`, but can be set to `className` for JSX. 4 | -------------------------------------------------------------------------------- /messages/1.5.1.txt: -------------------------------------------------------------------------------- 1 | Version 1.5.1 2 | 3 | * Fix incorrect BEM naming splitting with key-value BEM natation (e. g., .block__element_modifier-key_modifier-value) 4 | -------------------------------------------------------------------------------- /messages/1.4.0.txt: -------------------------------------------------------------------------------- 1 | Add ability to explicit run eCSStractor with or without BEM Nesting regardless `bem_nesting` option from Command Palette, Menu or Context Menu. 2 | -------------------------------------------------------------------------------- /messages/1.7.0.txt: -------------------------------------------------------------------------------- 1 | Version 1.7.0 2 | 3 | * Added new option `brackets_newline_after`. Add new line after open bracket. 4 | * Added new option `empty_line_before_nested_selector`. Add empty line before nested element/modifier. 5 | 6 | Thanks, Nikolay Gromov, for these options! 7 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.4.0": "messages/1.4.0.txt", 3 | "1.5.0": "messages/1.5.0.txt", 4 | "1.5.1": "messages/1.5.1.txt", 5 | "1.6.0": "messages/1.6.0.txt", 6 | "1.6.1": "messages/1.6.1.txt", 7 | "1.7.0": "messages/1.7.0.txt", 8 | "1.8.0": "messages/1.8.0.txt" 9 | } 10 | -------------------------------------------------------------------------------- /messages/1.8.0.txt: -------------------------------------------------------------------------------- 1 | Version 1.8.0 2 | 3 | * Added new option `add_comments`. Works with BEM nesting enabled. Generate full class names as a comments before nested BEM elements and modifiers. This is useful for finding selectors by class names. 4 | * Added new option `comment_style` to control output when `add_comments` enabled. 5 | 6 | Thanks, Jaak Ritso! 7 | -------------------------------------------------------------------------------- /eCSStractor.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Run eCSStractor", 4 | "command": "ecsstractor" 5 | }, 6 | { 7 | "caption": "Run eCSStractor (with BEM Nesting)", 8 | "command": "ecsstractor", 9 | "args": { 10 | "bem_nesting": true 11 | } 12 | }, 13 | { 14 | "caption": "Run eCSStractor (with BEM Nesting and Comments)", 15 | "command": "ecsstractor", 16 | "args": { 17 | "bem_nesting": true, 18 | "add_comments": true 19 | } 20 | }, 21 | { 22 | "caption": "Run eCSStractor (without BEM Nesting)", 23 | "command": "ecsstractor", 24 | "args": { 25 | "bem_nesting": false 26 | } 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "eCSStractor", 4 | "children": 5 | [ 6 | { 7 | "caption": "Run", 8 | "command": "ecsstractor" 9 | }, 10 | { 11 | "caption": "Run (with BEM Nesting)", 12 | "command": "ecsstractor", 13 | "args": { 14 | "bem_nesting": true 15 | } 16 | }, 17 | { 18 | "caption": "Run (with BEM Nesting and Comments)", 19 | "command": "ecsstractor", 20 | "args": { 21 | "bem_nesting": true, 22 | "add_comments": true 23 | } 24 | }, 25 | { 26 | "caption": "Run (without BEM Nesting)", 27 | "command": "ecsstractor", 28 | "args": { 29 | "bem_nesting": false 30 | } 31 | } 32 | ] 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015-present Aleks Hudochenkov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /eCSStractor.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // Add brackets. Useful for Sass syntax and Stylus 3 | "brackets": true, 4 | 5 | // Add new line 6 | "brackets_newline_after": true, 7 | 8 | // Ignore classes 9 | // "ignore": ["clearfix", "hide"], 10 | 11 | // Ignore classes with RegEx patterns 12 | // "ignore_regex": ["^js-"], 13 | 14 | // Where to put result 15 | // "tab": new tab 16 | // "clipboard": copy to clipboard 17 | "destination": "tab", 18 | 19 | // Attribute name 20 | "attributes": [ 21 | "class", 22 | // For react jsx 23 | "className" 24 | ], 25 | 26 | // BEM Nesting. Generate nested stylesheet for preprocessors 27 | "bem_nesting": false, 28 | 29 | // These settings uses if BEM Nesting is on 30 | 31 | // Indentation 32 | // "\t" : one tab 33 | // " " : two spaces 34 | // " " : four spaces 35 | "indentation": "\t", 36 | 37 | // Separator between block and element names 38 | "bem.element_separator": "__", 39 | 40 | // Separator between block or element and they modifier 41 | "bem.modifier_separator": "--", 42 | 43 | // Parent symbol. Ex.: &__element {} 44 | "preprocessor.parent_symbol": "&", 45 | 46 | // Empty line before nested element/modifier 47 | "empty_line_before_nested_selector": false, 48 | 49 | // Add comments to nested stylesheets for preprocessors 50 | "add_comments": false, 51 | 52 | // Comment style. Either CSS (/* */) or SCSS (//) 53 | "comment_style": "CSS" 54 | } 55 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "tools", 4 | "children": 5 | [ 6 | { 7 | "id": "ecsstractor", 8 | "caption": "eCSStractor", 9 | "children": 10 | [ 11 | { 12 | "caption": "Run", 13 | "command": "ecsstractor" 14 | }, 15 | { 16 | "caption": "Run (with BEM Nesting)", 17 | "command": "ecsstractor", 18 | "args": { 19 | "bem_nesting": true 20 | } 21 | }, 22 | { 23 | "caption": "Run (with BEM Nesting and Comments)", 24 | "command": "ecsstractor", 25 | "args": { 26 | "bem_nesting": true, 27 | "add_comments": true 28 | } 29 | }, 30 | { 31 | "caption": "Run (without BEM Nesting)", 32 | "command": "ecsstractor", 33 | "args": { 34 | "bem_nesting": false 35 | } 36 | } 37 | ] 38 | } 39 | ] 40 | }, 41 | { 42 | "id": "preferences", 43 | "children": 44 | [ 45 | { 46 | "id": "package-settings", 47 | "children": 48 | [ 49 | { 50 | "caption": "eCSStractor", 51 | "children": 52 | [ 53 | { 54 | "command": "open_file", 55 | "args": { 56 | "file": "${packages}/eCSStractor/eCSStractor.sublime-settings" 57 | }, 58 | "caption": "Settings – Default" 59 | }, 60 | { 61 | "command": "open_file", 62 | "args": { 63 | "file": "${packages}/User/eCSStractor.sublime-settings" 64 | }, 65 | "caption": "Settings – User" 66 | }, 67 | { "caption": "-" }, 68 | { 69 | "command": "open_file", 70 | "args": { 71 | "file": "${packages}/eCSStractor/Default (OSX).sublime-keymap", 72 | "platform": "OSX" 73 | }, 74 | "caption": "Key Bindings – Default" 75 | }, 76 | { 77 | "command": "open_file", 78 | "args": { 79 | "file": "${packages}/eCSStractor/Default (Linux).sublime-keymap", 80 | "platform": "Linux" 81 | }, 82 | "caption": "Key Bindings – Default" 83 | }, 84 | { 85 | "command": "open_file", 86 | "args": { 87 | "file": "${packages}/eCSStractor/Default (Windows).sublime-keymap", 88 | "platform": "Windows" 89 | }, 90 | "caption": "Key Bindings – Default" 91 | }, 92 | { 93 | "command": "open_file", 94 | "args": { 95 | "file": "${packages}/User/Default (Windows).sublime-keymap", 96 | "platform": "Windows" 97 | }, 98 | "caption": "Key Bindings – User" 99 | }, 100 | { 101 | "command": "open_file", 102 | "args": { 103 | "file": "${packages}/User/Default (OSX).sublime-keymap", 104 | "platform": "OSX" 105 | }, 106 | "caption": "Key Bindings – User" 107 | }, 108 | { 109 | "command": "open_file", 110 | "args": { 111 | "file": "${packages}/User/Default (Linux).sublime-keymap", 112 | "platform": "Linux" 113 | }, 114 | "caption": "Key Bindings – User" 115 | }, 116 | { "caption": "-" } 117 | ] 118 | } 119 | ] 120 | } 121 | ] 122 | } 123 | ] 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eCSStractor 2 | 3 | Sublime Text plugin for extracting class names from HTML and generate CSS stylesheet for following work. 4 | 5 | Default extracting: 6 | 7 | ![ecss_normal](https://cloud.githubusercontent.com/assets/654597/5896783/5ac44e42-a54c-11e4-8981-75456ac98f0b.gif) 8 | 9 | With BEM nesting: 10 | 11 | ![ecss_bem](https://cloud.githubusercontent.com/assets/654597/5896785/60708c5c-a54c-11e4-963f-9e00ede168c3.gif) 12 | 13 | With BEM nesting and class names as comments: 14 | ![ecss_bem_comments](https://user-images.githubusercontent.com/654597/35009441-d1de8982-faff-11e7-8281-7d4e85d4dc5a.gif) 15 | 16 | ## Usage 17 | 18 | Open any document contain HTML and do one of the following: 19 | 20 | * Press `Cmd+Shift+X` on Mac OS X or `Ctrl+Shift+X` on Windows/Linux. 21 | * Go to **Tools → eCSStractor → Run** 22 | * Right click and select **eCSStractor → Run** 23 | 24 | Then you will see new tab with CSS selectors extracted from document. 25 | 26 | Plugin can process either selected text or whole file. 27 | 28 | You can explicit **Run (with BEM Nesting)** or **Run (without BEM Nesting)** regardless `bem_nesting` option from Command Palette, Menu or Context Menu. 29 | 30 | ## Options 31 | 32 | The default settings can be viewed by accessing the **Preferences → Package Settings → eCSStractor → Settings – Default** menu entry. To ensure settings are not lost when the package is upgraded, make sure all edits are saved to **Settings – User**. 33 | 34 | #### brackets 35 | 36 | Add brackets. Useful for Sass syntax and Stylus. 37 | 38 | _Default: **true**_ 39 | 40 | #### brackets_newline_after 41 | 42 | Add new line after open bracket. 43 | 44 | _Default: **true**_ 45 | 46 | #### attributes 47 | 48 | HTML node attributes from which class names should be extracted. 49 | 50 | _Default: **["class", "className"]**_ 51 | 52 | #### ignore 53 | 54 | List of classnames to ignore. Useful for helper classes, that probably already described. Ex., `clearfix`. See **Settings – Default** for example. 55 | 56 | _Default: **empty**_ 57 | 58 | #### ignore_regex 59 | 60 | Similar to `ignore` option, but use [RegEx](https://docs.python.org/3.4/library/re.html#regular-expression-syntax) to ignore. Ex., `^js-` will ingore all classes started with `js-`. See **Settings – Default** for example. 61 | 62 | _Default: **empty**_ 63 | 64 | #### destination 65 | 66 | Where to put result: new tab (`tab`) or copy to clipboard (`clipboard`) 67 | 68 | _Default: **tab**_ 69 | 70 | #### bem_nesting 71 | 72 | BEM Nesting. Generate nested stylesheet for preprocessors rather simple stylesheet. See the difference in the [Examples](#examples) section. 73 | 74 | _Default: **false**_ 75 | 76 | ### Options only for BEM Nesting is on 77 | 78 | #### indentation 79 | 80 | Indentation. 81 | 82 | _Default: **\t**_ 83 | 84 | #### bem.element_separator 85 | 86 | Separator between block and element names. 87 | 88 | _Default: ___ 89 | 90 | #### bem.modifier_separator 91 | 92 | Separator between block or element and they modifier. 93 | 94 | _Default: **--**_ 95 | 96 | #### preprocessor.parent_symbol 97 | 98 | Parent symbol. Ex.: `&__element {}` 99 | 100 | _Default: **&**_ 101 | 102 | #### empty_line_before_nested_selector 103 | 104 | Add empty line before nested element/modifier. 105 | 106 | _Default: **false**_ 107 | 108 | #### add_comments 109 | 110 | Generate full class names as a comments before nested BEM elements and modifiers. This is useful for finding selectors by class names. See the difference in the [Examples](#examples) section. 111 | 112 | _Default: **false**_ 113 | 114 | #### comment_style 115 | 116 | Comment style shows `CSS` (`/* */`) or `SCSS` (`//`) style comments. Works with `add_comments` enabled. 117 | 118 | _Default: **"CSS"**_ 119 | 120 | ## Examples 121 | 122 | Source: 123 | 124 | ```html 125 | 130 | ``` 131 | 132 | Run eCSStractor (BEM Nesting is off): 133 | 134 | ```css 135 | .nav { 136 | } 137 | .nav--main { 138 | } 139 | .nav__item { 140 | } 141 | .nav__link { 142 | } 143 | .nav__link--special { 144 | } 145 | ``` 146 | 147 | Run eCSStractor (BEM Nesting is on): 148 | 149 | ```scss 150 | .nav { 151 | &--main { 152 | } 153 | &__item { 154 | } 155 | &__link { 156 | &--special { 157 | } 158 | } 159 | } 160 | ``` 161 | 162 | Run eCSStractor (BEM Nesting and Comments are on): 163 | 164 | ```css 165 | .nav { 166 | /* .nav--main */ 167 | &--main { 168 | } 169 | /* .nav__item */ 170 | &__item { 171 | } 172 | /* .nav__link */ 173 | &__link { 174 | /* .nav__link--special */ 175 | &--special { 176 | } 177 | } 178 | } 179 | ``` 180 | 181 | Run eCSStractor (BEM Nesting and Comments are on and comment style is SCSS): 182 | 183 | ```scss 184 | .nav { 185 | // .nav--main 186 | &--main { 187 | } 188 | // .nav__item 189 | &__item { 190 | } 191 | // .nav__link 192 | &__link { 193 | // .nav__link--special 194 | &--special { 195 | } 196 | } 197 | } 198 | ``` 199 | 200 | # Installation 201 | 202 | Most simple way it's install with [Package Control](https://packagecontrol.io/). 203 | 204 | Open the Command Palette `Cmd+Shift+P` (OS X) or `Ctrl+Shift+P` (Linux/Windows) and select “Package Control: Install Package”, then search for `eCSStractor`. 205 | 206 | # Similar tool 207 | 208 | I've been inspired by [extractCSS](http://extractcss.com/) online tool. This tool have much more functions, but not very convenient for regular use. 209 | -------------------------------------------------------------------------------- /eCSStractor.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import re 4 | import os 5 | 6 | try: 7 | # ST2, Python 2 8 | from HTMLParser import HTMLParser 9 | except: 10 | # ST3, Python 3 11 | from html.parser import HTMLParser 12 | 13 | class EcsstractorCommand(sublime_plugin.WindowCommand): 14 | def run(self, bem_nesting = "default", add_comments = "default"): 15 | 16 | view = self.window.active_view() 17 | 18 | plugin_settings = sublime.load_settings('eCSStractor.sublime-settings') 19 | self.brackets = plugin_settings.get('brackets') 20 | self.brackets_newline_after = plugin_settings.get('brackets_newline_after') 21 | destination = plugin_settings.get('destination') 22 | 23 | if bem_nesting is "default": 24 | bem_nesting = plugin_settings.get('bem_nesting') 25 | 26 | if add_comments is "default": 27 | add_comments = plugin_settings.get('add_comments') 28 | 29 | syntax = 'Packages/CSS/CSS.tmLanguage' 30 | 31 | # if view have any selection then work with selection, else with whole view 32 | selection = view.sel()[0] 33 | 34 | if len(selection) > 0: 35 | 36 | for sel in view.sel(): 37 | region = sublime.Region( 38 | view.line(min(sel.a, sel.b)).a, # line start of first line 39 | view.line(max(sel.a, sel.b)).b # line end of last line 40 | ) 41 | 42 | else: 43 | region = sublime.Region(0, view.size()) 44 | 45 | self.source = view.substr(region) 46 | 47 | if bem_nesting: 48 | output = self.generateBEM(add_comments) 49 | 50 | # set sass syntax if proper package is installed 51 | scss_syntax = os.path.join(sublime.packages_path(), 'Syntax Highlighting for Sass', 'Syntaxes', 'SCSS.tmLanguage') 52 | 53 | if os.path.exists(scss_syntax): 54 | 55 | syntax = 'Packages/Syntax Highlighting for Sass/Syntaxes/SCSS.tmLanguage' 56 | 57 | else: 58 | output = self.generateOutput() 59 | 60 | # if output not empty 61 | if output: 62 | 63 | if destination == "tab": 64 | # create new view 65 | new_file = self.window.new_file() 66 | new_file.set_syntax_file(syntax) 67 | new_file.run_command("ecsstractor_insert", {"text": output}) 68 | 69 | elif destination == "clipboard": 70 | # copy to clipboard 71 | sublime.set_clipboard(output) 72 | sublime.status_message("eCSStractor: result was copied to clipboard") 73 | 74 | else: 75 | sublime.status_message("eCSStractor can't find any classes") 76 | 77 | def generateOutput(self): 78 | 79 | css_template = "selector {\n}" 80 | output = "" 81 | 82 | # parsing 83 | parsed = parser() 84 | parsed.feed(self.source) 85 | 86 | # format output 87 | for i in range(len(parsed.classes)): 88 | output += css_template.replace("selector", "." + parsed.classes[i]) + "\n" 89 | 90 | return output 91 | 92 | def generateBEM(self, add_comments = "default"): 93 | 94 | plugin_settings = sublime.load_settings('eCSStractor.sublime-settings') 95 | indentation = plugin_settings.get('indentation', '\t') 96 | element_separator = plugin_settings.get('bem.element_separator', '__') 97 | modifier_separator = plugin_settings.get('bem.modifier_separator', '--') 98 | parent_symbol = plugin_settings.get('preprocessor.parent_symbol', '&') 99 | empty_line_before_nested_selector = plugin_settings.get('empty_line_before_nested_selector') 100 | 101 | # Comment style 102 | comment_style = plugin_settings.get('comment_style', 'css').lower() 103 | comment_symbol_beginning = "/* " 104 | comment_symbol_end = " */" 105 | 106 | if comment_style == "scss": 107 | comment_symbol_beginning = "// " 108 | comment_symbol_end = "" 109 | 110 | output = "" 111 | selectors = [] 112 | 113 | # parsing 114 | parsed = parser() 115 | parsed.feed(self.source) 116 | 117 | # build tree 118 | for i in range(len(parsed.classes)): 119 | selector = parsed.classes[i] 120 | 121 | block = {} 122 | element = {} 123 | 124 | # if block has element 125 | if element_separator in selector: 126 | 127 | parts = selector.split(element_separator, maxsplit=1) 128 | 129 | # check if block with this name exist already 130 | hasBlock = self.hasChild(selectors, "name", parts[0]) 131 | 132 | # if block is exist link to list 133 | if hasBlock is not False: 134 | block = selectors[hasBlock] 135 | 136 | # if block is not exist give it name 137 | if hasBlock is False: 138 | block["name"] = parts[0] 139 | 140 | # if elements list exist in block 141 | if "elements" not in block: 142 | block["elements"] = [] 143 | 144 | # get element and his modifier 145 | elementParts = parts[1].split(modifier_separator, maxsplit=1) 146 | 147 | # check if element with this name exist in block already 148 | hasElement = self.hasChild(block["elements"], "name", elementParts[0]) 149 | 150 | # if element is exist link to list 151 | if hasElement is not False: 152 | element = block["elements"][hasElement] 153 | 154 | # if element is not exist give it name 155 | if hasElement is False: 156 | element["name"] = elementParts[0] 157 | 158 | # if element has modifier 159 | if len(elementParts) > 1: 160 | 161 | # if modifiers list exist in element 162 | if "modifiers" not in element: 163 | element["modifiers"] = [] 164 | 165 | # add modifier 166 | element["modifiers"].append(elementParts[1]) 167 | 168 | # if it is new element add it to block 169 | if hasElement is False: 170 | block["elements"].append(element) 171 | 172 | # if it is new block add it to list 173 | if hasBlock is False: 174 | selectors.append(block) 175 | 176 | # if block has modifier 177 | elif modifier_separator in selector: 178 | 179 | parts = selector.split(modifier_separator, maxsplit=1) 180 | 181 | hasBlock = self.hasChild(selectors, "name", parts[0]) 182 | 183 | if hasBlock is not False: 184 | block = selectors[hasBlock] 185 | 186 | if hasBlock is False: 187 | block["name"] = parts[0] 188 | 189 | if "modifiers" not in block: 190 | block["modifiers"] = [] 191 | 192 | block["modifiers"].append(parts[1]) 193 | 194 | if hasBlock is False: 195 | selectors.append(block) 196 | 197 | else: 198 | 199 | hasBlock = self.hasChild(selectors, "name", selector) 200 | 201 | if hasBlock is False: 202 | block["name"] = selector 203 | selectors.append(block) 204 | 205 | # format output 206 | for block in selectors: 207 | 208 | if self.brackets: 209 | output += "." + block["name"] + " {\n" 210 | else: 211 | output += "." + block["name"] + "\n" 212 | 213 | indent = indentation 214 | indent1 = indent * 1 215 | indent2 = indent * 2 216 | 217 | if empty_line_before_nested_selector: 218 | empty_line = "\n" 219 | else: 220 | empty_line = "" 221 | 222 | if "modifiers" in block: 223 | 224 | for modifier in block["modifiers"]: 225 | if self.brackets: 226 | if self.brackets_newline_after: 227 | if add_comments: 228 | output += empty_line + indent1 + comment_symbol_beginning + "." + block["name"] + modifier_separator + modifier + comment_symbol_end + "\n" 229 | 230 | output += empty_line + indent1 + parent_symbol + modifier_separator + modifier + " {\n" 231 | output += indent1 + "}\n" 232 | else: 233 | if add_comments: 234 | output += empty_line + indent1 + comment_symbol_beginning + "." + block["name"] + modifier_separator + modifier + comment_symbol_end + "\n" 235 | 236 | output += empty_line + indent1 + parent_symbol + modifier_separator + modifier + " {}\n" 237 | else: 238 | if add_comments: 239 | output += indent1 + comment_symbol_beginning + "." + block["name"] + modifier_separator + modifier + comment_symbol_end + "\n" 240 | 241 | output += indent1 + parent_symbol + modifier_separator + modifier + "\n" 242 | output += "\n" 243 | 244 | if "elements" in block: 245 | 246 | for element in block["elements"]: 247 | if self.brackets: 248 | if self.brackets_newline_after: 249 | if add_comments: 250 | output += empty_line + indent1 + comment_symbol_beginning + "." + block["name"] + element_separator + element["name"] + comment_symbol_end + "\n" 251 | 252 | output += empty_line + indent1 + parent_symbol + element_separator + element["name"] + " {\n" 253 | else: 254 | if add_comments: 255 | output += empty_line + indent1 + comment_symbol_beginning + "." + block["name"] + element_separator + element["name"] + comment_symbol_end + "\n" 256 | 257 | output += empty_line + indent1 + parent_symbol + element_separator + element["name"] + " {" 258 | else: 259 | if add_comments: 260 | output += empty_line + indent1 + comment_symbol_beginning + "." + block["name"] + element_separator + element["name"] + comment_symbol_end + "\n" 261 | 262 | output += empty_line + indent1 + parent_symbol + element_separator + element["name"] + "\n" 263 | 264 | if "modifiers" in element: 265 | if not self.brackets_newline_after: 266 | output += "\n" 267 | 268 | for modifier in element["modifiers"]: 269 | if self.brackets: 270 | if self.brackets_newline_after: 271 | if add_comments: 272 | output += empty_line + indent2 + comment_symbol_beginning + "." + block["name"] + element_separator + element["name"] + modifier_separator + modifier + comment_symbol_end + "\n" 273 | 274 | output += empty_line + indent2 + parent_symbol + modifier_separator + modifier + " {\n" 275 | output += indent2 + "}\n" 276 | else: 277 | if add_comments: 278 | output += empty_line + indent2 + comment_symbol_beginning + "." + block["name"] + element_separator + element["name"] + modifier_separator + modifier + comment_symbol_end + "\n" 279 | 280 | output += empty_line + indent2 + parent_symbol + modifier_separator + modifier + " {}\n" 281 | else: 282 | if add_comments: 283 | output += empty_line + indent2 + comment_symbol_beginning + "." + block["name"] + element_separator + element["name"] + modifier_separator + modifier + comment_symbol_end + "\n" 284 | 285 | output += empty_line + indent2 + parent_symbol + modifier_separator + modifier + "\n" 286 | output += "\n" 287 | 288 | if self.brackets: 289 | if self.brackets_newline_after: 290 | output += indent1 + "}\n" 291 | else: 292 | if "modifiers" in element: 293 | output += indent1 + "}\n" 294 | else: 295 | output += "}\n" 296 | else: 297 | output += "\n" 298 | 299 | if self.brackets: 300 | output += "}\n" 301 | else: 302 | output += "\n" 303 | 304 | if not self.brackets: 305 | output = output.replace("\n\n\n\n", "\n\n") 306 | output = output.replace("\n\n\n", "\n\n") 307 | 308 | return output 309 | 310 | # check existance of key_name:key in listo 311 | def hasChild(self, listo, key_name, key): 312 | 313 | for y in range(len(listo)): 314 | if listo[y][key_name] == key: 315 | return y 316 | 317 | return False 318 | 319 | class EcsstractorInsertCommand(sublime_plugin.TextCommand): 320 | def run(self, edit, text): 321 | self.view.insert(edit, 0, text) 322 | 323 | class parser(HTMLParser): 324 | def __init__(self): 325 | HTMLParser.__init__(self) 326 | self.classes = [] 327 | 328 | def handle_starttag(self, tag, attrs): 329 | 330 | ignore = sublime.load_settings('eCSStractor.sublime-settings').get('ignore', "") 331 | ignore_regex = sublime.load_settings('eCSStractor.sublime-settings').get('ignore_regex', "") 332 | attributes = sublime.load_settings('eCSStractor.sublime-settings').get('attributes', ["class", "className"]) 333 | 334 | for name, value in attrs: 335 | 336 | if name in attributes: 337 | 338 | # remove whitespaces before and after string 339 | value = value.strip(); 340 | 341 | # if class="" not empty 342 | if len(value) > 0: 343 | 344 | # split class string by whitespaces, in case multiple classes presented 345 | elementClasses = re.split("\s+", value) 346 | 347 | for i in range(len(elementClasses)): 348 | 349 | currentClass = elementClasses[i] 350 | 351 | # possible add class to list if it's already not there and not in ignore list 352 | if currentClass not in self.classes and currentClass not in ignore: 353 | 354 | # check if it's pass regex ignore list check 355 | itspass = True 356 | 357 | for y in range(len(ignore_regex)): 358 | 359 | if re.compile(ignore_regex[y]).match(currentClass): 360 | itspass = False 361 | break 362 | 363 | # add class to list if it's not already in the list, and pass both ignore list checks 364 | if itspass: 365 | self.classes.append(currentClass) 366 | --------------------------------------------------------------------------------