├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .eslintrc ├── fixtures ├── in │ ├── default.css │ └── options.css └── out │ ├── default.css │ └── options.css └── test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb/legacy", 3 | "rules": { 4 | "vars-on-top": 0, 5 | "comma-dangle": [2, "never"], 6 | "func-names": 0, 7 | "one-var": [2, { uninitialized: "always", initialized: "never" }], 8 | "no-param-reassign": [0], 9 | "no-use-before-define": [2, "nofunc"], 10 | "space-before-function-paren": 0, 11 | "object-curly-spacing": 0 12 | }, 13 | "env": { 14 | "mocha": true, 15 | "node": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | 3 | node_modules/ 4 | 5 | test/ 6 | .travis.yml 7 | 8 | gulpfile.js 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "4.2.4" 5 | - "0.12" 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.0 2 | * Upgrade to latest PostCSS 3 | * Use AirBnb ESLint config 4 | 5 | ## 0.1.0 6 | * Initial release 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015 Simon Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-generate-preset [![Build Status][ci-img]][ci] 2 | 3 | [PostCSS] plugin that allows quick generation of rules. Useful for quickly creating repetitive utilities. 4 | 5 | [PostCSS]: https://github.com/postcss/postcss 6 | [ci-img]: https://travis-ci.org/simonsmith/postcss-generate-preset.svg 7 | [ci]: https://travis-ci.org/simonsmith/postcss-generate-preset 8 | 9 | ## Installation 10 | 11 | ``` console 12 | $ npm install postcss-generate-preset 13 | ``` 14 | 15 | ## Usage 16 | 17 | **input.css** 18 | 19 | ``` css 20 | @generate-preset .u-mp margin padding 0 10px; 21 | @generate-preset .u-mt margin-top 10px 20px; 22 | ``` 23 | 24 | **output.css** 25 | 26 | ``` css 27 | .u-mp0 { 28 | margin: 0; 29 | padding: 0; 30 | } 31 | 32 | .u-mp10 { 33 | margin: 10px; 34 | padding: 10px; 35 | } 36 | 37 | .u-mt10 { 38 | margin-top: 10px; 39 | } 40 | 41 | .u-mt20 { 42 | margin-top: 20px; 43 | } 44 | ``` 45 | 46 | ``` js 47 | var presets = require('postcss-generate-preset'); 48 | postcss([ presets() ]) 49 | ``` 50 | 51 | ### Options 52 | 53 | #### `useImportant` (default: `false`) 54 | 55 | When set to `true` all declarations will use `!important`. Often useful when utility classes need to override component styles 56 | 57 | #### `zeroValue` (default): `false`) 58 | 59 | The default is to add zero to a selector. In some cases it might be desirable to display it differently 60 | 61 | ``` js 62 | presets({ zeroValue: 'Z' }); 63 | ``` 64 | 65 | ``` css 66 | @generate-preset .u-m margin 0; 67 | 68 | /* becomes */ 69 | 70 | .u-mZ { 71 | margin: 0; 72 | } 73 | ``` 74 | 75 | 76 | See [PostCSS] docs for examples for your environment. 77 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var postcss = require('postcss'); 2 | 3 | module.exports = postcss.plugin('postcss-generate-preset', generatePreset); 4 | 5 | function generatePreset(opts) { 6 | opts = opts || {}; 7 | 8 | return function(css) { 9 | css.walkAtRules(function (atRule) { 10 | if (atRule.name !== 'generate-preset') { 11 | return; 12 | } 13 | 14 | /** 15 | * Parse: `.u-mf margin font-size 5px 10px 15px` 16 | * Return: `['.u-mf', 'margin font-size', '5px 10px 15px']` 17 | */ 18 | var matches = /(^\.[a-z-_.]+)\s([a-z-\s]+)\s([a-z0-9\s\.]+)/ig.exec(atRule.params); 19 | if (!matches) { 20 | return; 21 | } 22 | 23 | var root = atRule.parent; 24 | var selector = matches[1]; 25 | var properties = postcss.list.space(matches[2]); 26 | var values = postcss.list.space(matches[3]); 27 | 28 | values.forEach(function(value) { 29 | var numeric = parseFloat(value, 10); 30 | 31 | // Reassign 0 with zeroValue if specified 32 | // Used for selectors like .u-mZ instead of .u-m0 33 | if (numeric === 0 && opts.zeroValue) { 34 | numeric = opts.zeroValue; 35 | } 36 | 37 | // Can't have decimal values in the selector, 38 | // so replace dots with dashes 39 | if (isFloat(numeric)) { 40 | numeric = (numeric + '').replace('.', '-'); 41 | } 42 | 43 | var rule = postcss.rule({ 44 | selector: selector + numeric 45 | }); 46 | 47 | if (opts.useImportant) { 48 | value += ' !important'; 49 | } 50 | 51 | properties.forEach(function(prop) { 52 | rule.append(postcss.decl({ 53 | prop: prop, 54 | value: value 55 | })); 56 | }); 57 | 58 | root.insertBefore(atRule, rule); 59 | }); 60 | 61 | atRule.remove(); 62 | }); 63 | }; 64 | } 65 | 66 | function isFloat(n) { 67 | return n === +n && n !== (n | 0); 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-generate-preset", 3 | "version": "0.2.0", 4 | "description": "PostCSS plugin that allows quick generation of similar rules", 5 | "keywords": [ 6 | "postcss", 7 | "css", 8 | "postcss-plugin", 9 | "suitcss", 10 | "utility" 11 | ], 12 | "author": "Simon Smith ", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/simonsmith/postcss-generate-preset.git" 17 | }, 18 | "dependencies": { 19 | "postcss": "^5.0.13" 20 | }, 21 | "devDependencies": { 22 | "chai": "3.4.1", 23 | "eslint": "^1.10.3", 24 | "eslint-config-airbnb": "^2.1.1", 25 | "mocha": "^2.3.4" 26 | }, 27 | "scripts": { 28 | "lint": "eslint index.js test/*.js", 29 | "test": "npm run lint && mocha --reporter spec" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | extends: "../.eslintrc", 3 | rules: { 4 | no-unused-expressions: 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/in/default.css: -------------------------------------------------------------------------------- 1 | .before { 2 | font-size: 10px; 3 | } 4 | 5 | @generate-preset .u-mp margin padding 0 10px 20px; 6 | @generate-preset .u-fontSize-lineHeight font-size line-height 14.5px; 7 | 8 | @media (min-width: 200px) { 9 | @generate-preset .u-m margin 10px 20px; 10 | } 11 | 12 | .after { 13 | font-size: 10px; 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/in/options.css: -------------------------------------------------------------------------------- 1 | .before { 2 | font-size: 10px; 3 | } 4 | 5 | @generate-preset .u-mt margin-top 0 10px; 6 | @generate-preset .u-p padding 0; 7 | 8 | .after { 9 | font-size: 10px; 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/out/default.css: -------------------------------------------------------------------------------- 1 | .before { 2 | font-size: 10px; 3 | } 4 | 5 | .u-mp0 { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | .u-mp10 { 11 | margin: 10px; 12 | padding: 10px; 13 | } 14 | 15 | .u-mp20 { 16 | margin: 20px; 17 | padding: 20px; 18 | } 19 | .u-fontSize-lineHeight14-5 { 20 | font-size: 14.5px; 21 | line-height: 14.5px; 22 | } 23 | 24 | @media (min-width: 200px) { 25 | .u-m10 { 26 | margin: 10px; 27 | } 28 | .u-m20 { 29 | margin: 20px; 30 | } 31 | } 32 | 33 | .after { 34 | font-size: 10px; 35 | } 36 | -------------------------------------------------------------------------------- /test/fixtures/out/options.css: -------------------------------------------------------------------------------- 1 | .before { 2 | font-size: 10px; 3 | } 4 | 5 | .u-mtZ { 6 | margin-top: 0 !important; 7 | } 8 | 9 | .u-mt10 { 10 | margin-top: 10px !important; 11 | } 12 | .u-pZ { 13 | padding: 0 !important; 14 | } 15 | 16 | .after { 17 | font-size: 10px; 18 | } 19 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var postcss = require('postcss'); 2 | var expect = require('chai').expect; 3 | var plugin = require('../'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | var test = function(input, output, opts, done) { 8 | input = fs.readFileSync(path.join('test/fixtures', input), 'utf-8'); 9 | output = fs.readFileSync(path.join('test/fixtures', output), 'utf-8'); 10 | 11 | postcss([plugin(opts)]).process(input).then(function(result) { 12 | expect(result.css).to.eql(output); 13 | expect(result.warnings()).to.be.empty; 14 | done(); 15 | }).catch(function (error) { 16 | done(error); 17 | }); 18 | }; 19 | 20 | describe('postcss-generate-preset', function() { 21 | it('should generate preset rules', function(done) { 22 | test('in/default.css', 'out/default.css', {}, done); 23 | }); 24 | 25 | it('should allow options', function(done) { 26 | test('in/options.css', 'out/options.css', { useImportant: true, zeroValue: 'Z' }, done); 27 | }); 28 | }); 29 | --------------------------------------------------------------------------------