├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── contributing.md ├── lib └── index.js ├── package.json └── test ├── fixtures ├── bad_indents.expected.html ├── bad_indents.html ├── basic.expected.html ├── basic.html ├── custom_indent.expected.html ├── custom_indent.html ├── selfclosing.expected.html └── selfclosing.html └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = space 12 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .nyc_output 4 | coverage 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | contributing.md 3 | .editorconfig 4 | coverage 5 | .nyc_output 6 | .travis.yml 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - 6 5 | after_script: 6 | - npm run coveralls 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | License (MIT) 2 | ------------- 3 | 4 | Copyright (c) 2016 Jeff Escalante 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reshape Beautify 2 | 3 | [![npm](https://img.shields.io/npm/v/reshape-beautify.svg?style=flat-square)](https://npmjs.com/package/reshape-beautify) 4 | [![tests](https://img.shields.io/travis/reshape/beautify.svg?style=flat-square)](https://travis-ci.org/reshape/beautify?branch=master) 5 | [![dependencies](https://img.shields.io/david/reshape/beautify.svg?style=flat-square)](https://david-dm.org/reshape/beautify) 6 | [![coverage](https://img.shields.io/coveralls/reshape/beautify.svg?style=flat-square)](https://coveralls.io/r/reshape/beautify?branch=master) 7 | 8 | A reshape plugin that pretty-prints your html 9 | 10 | > **Note:** This project is in early development, and versioning is a little different. [Read this](http://markup.im/#q4_cRZ1Q) for more details. 11 | 12 | ### Installation 13 | 14 | `npm install reshape-beautify --save` 15 | 16 | > **Note:** This project is compatible with node v6+ only 17 | 18 | ### Usage 19 | 20 | Add it as a reshape plugin: 21 | 22 | ```js 23 | const reshape = require('reshape') 24 | const beautify = require('reshape-beautify') 25 | 26 | reshape({ plugins: beautify(/* options */) }) 27 | .process(htmlString) 28 | .then((res) => console.log(res.output())) 29 | ``` 30 | 31 | ...and that's it! You can specify any options as listed below to customize the behavior. 32 | 33 | This plugin will ensure that all tags are correctly indented. However, it will not make changes to any tag that contains a plain text node, because this could interfere with the way the content looks. For example, this crappy html: 34 | 35 | ```html 36 | 37 | bad indentation 38 | 39 | 40 |

hi there

41 |
42 | 43 | ``` 44 | 45 | Would be transformed as such: 46 | 47 | ```html 48 | 49 | bad indentation 50 | 51 | 52 | 53 |
54 |

hi there

55 |
56 | 57 | ``` 58 | 59 | Wow, so clean! It will do the same for minified html: 60 | 61 | ```html 62 |

hi there

this is minified
63 | ``` 64 | 65 | Is transformed to: 66 | 67 | ```html 68 | 69 |

hi there

70 |
this is minified
71 | 72 | ``` 73 | 74 | However, if you have something like this: 75 | 76 | ```html 77 |
78 | hello there! 79 | this is great 80 |
81 | ``` 82 | 83 | We won't alter the spaces inside your section content, because this would change the way it looks on the page, so it would turn out exactly the same as the input. 84 | 85 | ### Options 86 | 87 | | Name | Description | Default | 88 | | ---- | ----------- | ------- | 89 | | **indent** | Number of spaces to indent your elements | `2` | 90 | 91 | ### License & Contributing 92 | 93 | - Details on the license [can be found here](LICENSE.md) 94 | - Details on running tests and contributing [can be found here](contributing.md) 95 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to reshape-beautify 2 | 3 | Hello there! First of all, thanks for being interested in reshape-beautify and helping out. We all think you are awesome, and by contributing to open source projects, you are making the world a better place. That being said, there are a few ways to make the process of contributing code to reshape-beautify smoother, detailed below: 4 | 5 | ### Filing Issues 6 | 7 | If you are opening an issue about a bug, make sure that you include clear steps for how we can reproduce the problem. _If we can't reproduce it, we can't fix it_. If you are suggesting a feature, make sure your explanation is clear and detailed. 8 | 9 | ### Getting Set Up 10 | 11 | - Clone the project down 12 | - Make sure [nodejs](http://nodejs.org) has been installed and is above version `6.x` 13 | - Run `npm install` 14 | - Put in work 15 | 16 | ### Testing 17 | 18 | This project is constantly evolving, and to ensure that things are secure and working for everyone, we need to have tests. If you are adding a new feature, please make sure to add a test for it. The test suite for this project uses [ava](https://github.com/sindresorhus/ava). 19 | 20 | To run the test suite just use `npm test` or install ava globally and use the `ava` command to run the tests. 21 | 22 | ### Code Style 23 | 24 | This project uses ES6, interpreted directly by node.js. To keep a consistent coding style in the project, we are using [standard js](http://standardjs.com/). In order for tests to pass, all code must pass standard js linting. This project also uses an [editorconfig](http://editorconfig.org/). It will make life much easier if you have the [editorconfig plugin](http://editorconfig.org/#download) for your text editor. For any inline documentation in the code, we're using [JSDoc](http://usejsdoc.org/). 25 | 26 | ### Commit Cleanliness 27 | 28 | It's ok if you start out with a bunch of experimentation and your commit log isn't totally clean, but before any pull requests are accepted, we like to have a nice clean commit log. That means [well-written and clear commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) and commits that each do something significant, rather than being typo or bug fixes. 29 | 30 | If you submit a pull request that doesn't have a clean commit log, we will ask you to clean it up before we accept. This means being familiar with rebasing - if you are not, [this guide](https://help.github.com/articles/interactive-rebase) by github should help you to get started. And if you are still confused, feel free to ask! 31 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function reshapeBeautify (opts = {}) { 2 | const indentDefault = opts.indent || 2 3 | return function beautifyPlugin (tree) { 4 | const indent = 0 5 | // remove all indents from the tree to start 6 | tree = sanitize(tree) 7 | // run the indent processing 8 | tree = walk(tree, indent, indentDefault, false) 9 | // if there's a leading newline/indent, it can be sliced off 10 | if (tree[0].content.match(/^\s+$/)) { tree = tree.slice(1) } 11 | return tree 12 | } 13 | } 14 | 15 | function walk (tree, indent, indentDefault, adjust) { 16 | return tree.reduce((m, node, i, a) => { 17 | // return non-tags 18 | if (node.type !== 'tag') { m.push(node); return m } 19 | 20 | // add the newline/indent for the open tag 21 | if (!adjust) { 22 | m.push({ 23 | type: 'text', 24 | content: `\n${formatIndent(indent, indentDefault)}` 25 | }) 26 | } 27 | 28 | if (node.content) { 29 | // if there is text content inside a node, we don't touch the spacing 30 | const adj = !!node.content.find((n) => n.type === 'text') 31 | 32 | // recurse and add the tag itself 33 | node.content = walk(node.content, ++indent, indentDefault, adj) 34 | indent-- 35 | } 36 | m.push(node) 37 | 38 | // TODO deal with self-closing tags 39 | 40 | // last child adds an indent for the closing tag 41 | if (i === a.length - 1 && !adjust) { 42 | m.push({ 43 | type: 'text', 44 | content: `\n${formatIndent(indent - 1, indentDefault)}` 45 | }) 46 | } 47 | 48 | return m 49 | }, []) 50 | } 51 | 52 | function sanitize (tree) { 53 | return tree.reduce((m, node) => { 54 | // remove any existing indents 55 | if (node.type === 'text' && node.content.match(/^\s+$/)) { return m } 56 | 57 | // return non-tags 58 | if (node.type !== 'tag' || !node.content) { m.push(node); return m } 59 | 60 | // recurse 61 | node.content = sanitize(node.content) 62 | m.push(node); return m 63 | }, []) 64 | } 65 | 66 | function formatIndent (n, level) { 67 | const res = [] 68 | for (let i = 0; i < level * n; i++) { res.push(' ') } 69 | return res.join('') 70 | } 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reshape-beautify", 3 | "description": "a reshape plugin that pretty-prints your html", 4 | "version": "0.1.2", 5 | "author": "reshape", 6 | "ava": { 7 | "verbose": "true" 8 | }, 9 | "bugs": "https://github.com/reshape/beautify/issues", 10 | "devDependencies": { 11 | "ava": "^0.18.1", 12 | "coveralls": "^2.11.12", 13 | "nyc": "^10.0.0", 14 | "reshape": "^0.5.0", 15 | "snazzy": "^6.0.0", 16 | "standard": "^10.0.0" 17 | }, 18 | "engines": { 19 | "node": ">= 6" 20 | }, 21 | "homepage": "https://github.com/reshape/beautify", 22 | "keywords": [ 23 | "beautify", 24 | "indent", 25 | "reshape-plugin" 26 | ], 27 | "license": "MIT", 28 | "main": "lib", 29 | "repository": "reshape/beautify", 30 | "scripts": { 31 | "coverage": "nyc ava && nyc report --reporter=html && open ./coverage/index.html", 32 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 33 | "lint": "standard | snazzy", 34 | "test": "nyc ava" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/fixtures/bad_indents.expected.html: -------------------------------------------------------------------------------- 1 | 2 | wow 3 | 4 | 5 | 6 | wow 7 |

8 | hi 9 |

10 |

hello there!

11 |
12 |
wow
13 | wow amaze 14 |
15 | total junk 16 | hi 17 | 18 | -------------------------------------------------------------------------------- /test/fixtures/bad_indents.html: -------------------------------------------------------------------------------- 1 | 2 | wow 3 | 4 | 5 | 6 | 7 | 8 | wow 9 |

10 | hi 11 |

12 |

hello there!

13 |
14 |
wow
wow amaze
15 | total junk 16 | hi 17 | 18 | -------------------------------------------------------------------------------- /test/fixtures/basic.expected.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

hello world

4 |
5 |

wow

6 |
7 |
heyhey
8 |
9 |
10 | -------------------------------------------------------------------------------- /test/fixtures/basic.html: -------------------------------------------------------------------------------- 1 |

hello world

wow

heyhey
2 | -------------------------------------------------------------------------------- /test/fixtures/custom_indent.expected.html: -------------------------------------------------------------------------------- 1 |
2 |

hi

3 |
4 | -------------------------------------------------------------------------------- /test/fixtures/custom_indent.html: -------------------------------------------------------------------------------- 1 |
2 |

hi

3 |
4 | -------------------------------------------------------------------------------- /test/fixtures/selfclosing.expected.html: -------------------------------------------------------------------------------- 1 | 2 | hi 3 |

4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/selfclosing.html: -------------------------------------------------------------------------------- 1 | 2 | hi 3 |

4 | 5 | 6 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const beautify = require('..') 2 | const path = require('path') 3 | const {readFileSync} = require('fs') 4 | const reshape = require('reshape') 5 | const test = require('ava') 6 | const fixtures = path.join(__dirname, 'fixtures') 7 | 8 | test('basic', (t) => { 9 | return compare(t, 'basic') 10 | }) 11 | 12 | test('strips and replaces existing indentation', (t) => { 13 | return compare(t, 'bad_indents') 14 | }) 15 | 16 | test('self-closing tags', (t) => { 17 | return compare(t, 'selfclosing') 18 | }) 19 | 20 | test('custom indent level', (t) => { 21 | return compare(t, 'custom_indent', { indent: 4 }) 22 | }) 23 | 24 | function compare (t, name, opts) { 25 | const src = readFileSync(path.join(fixtures, `${name}.html`), 'utf8') 26 | const expected = readFileSync(path.join(fixtures, `${name}.expected.html`), 'utf8') 27 | 28 | return reshape({ plugins: beautify(opts) }) 29 | .process(src) 30 | .then((res) => res.output()) 31 | .tap((out) => t.is(expected, out)) 32 | } 33 | --------------------------------------------------------------------------------