├── .gitignore ├── .travis.yml ├── style-guide-default.png ├── test ├── input.css ├── color.css ├── output.html └── index.js ├── lib ├── markdown.js ├── utils.js ├── fileWriter.js ├── syntaxHighlight.js ├── template.js ├── colorPalette.js ├── analyzer.js └── params.js ├── .editorconfig ├── package.json ├── LICENSE ├── docs └── theme-guideline.md ├── index.js ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | styleguide 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.12" 5 | - "4" 6 | - "6" 7 | -------------------------------------------------------------------------------- /style-guide-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matype/postcss-style-guide/HEAD/style-guide-default.png -------------------------------------------------------------------------------- /test/input.css: -------------------------------------------------------------------------------- 1 | /* 2 | @styleguide 3 | 4 | @title input sample 5 | @mymeta test 6 | 7 | # h1 8 | */ 9 | 10 | .class { 11 | color: blue; 12 | } 13 | 14 | /* 15 | @doc 16 | ## h2 17 | */ 18 | .class { 19 | color: red; 20 | } 21 | -------------------------------------------------------------------------------- /test/color.css: -------------------------------------------------------------------------------- 1 | /* @start color */ 2 | :root { 3 | --red: #ff0000; 4 | } 5 | 6 | :root { 7 | --green: #00ff00; 8 | } 9 | /* @end color */ 10 | 11 | /* @start color */ 12 | :root { 13 | --blue: #0000ff; 14 | } 15 | /* @end color */ 16 | -------------------------------------------------------------------------------- /lib/markdown.js: -------------------------------------------------------------------------------- 1 | var hl = require('highlight.js'); 2 | 3 | var marked = require('marked'); 4 | marked.setOptions({ 5 | highlight: function (code) { 6 | return hl.highlightAuto(code).value; 7 | } 8 | }); 9 | 10 | module.exports = function (md) { 11 | return marked(md).trim(); 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 4 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [{package.json, .travis.yml}] 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var ObjProto = Object.prototype; 2 | var toString = ObjProto.toString; 3 | 4 | function isFunction(value) { 5 | return toString.call(value) === '[object Function]'; 6 | } 7 | module.exports.isFunction = isFunction; 8 | 9 | function result(obj) { 10 | if (isFunction(obj)) { 11 | return obj.apply(null, [].slice.call(arguments, 1)); 12 | } 13 | return obj; 14 | } 15 | module.exports.result = result; 16 | -------------------------------------------------------------------------------- /lib/fileWriter.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var mkdirp = require('mkdirp'); 4 | 5 | exports.write = function (filePath, str) { 6 | var dest = filePath; 7 | if (path.extname(filePath) !== '.html') { 8 | dest += '.html'; 9 | } 10 | var dir = path.dirname(dest); 11 | try { 12 | mkdirp.sync(dir); 13 | } catch (err) { 14 | throw err; 15 | } 16 | try { 17 | fs.writeFileSync(dest, str, 'utf8'); 18 | } catch (err) { 19 | throw err; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/syntaxHighlight.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var hl = require('highlight.js'); 3 | var nano = require('cssnano'); 4 | 5 | exports.highlight = function (css) { 6 | return hl.highlight('css', css).value; 7 | } 8 | 9 | exports.execute = function (params) { 10 | var src = params.src; 11 | var tmplStyle = params.tmplStyle; 12 | var codeStyle = fs.readFileSync(params.stylePath, 'utf-8'); 13 | return Promise.all([ 14 | nano.process(src), 15 | nano.process(tmplStyle), 16 | nano.process(codeStyle) 17 | ]); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /lib/template.js: -------------------------------------------------------------------------------- 1 | var ejs = require('ejs'); 2 | 3 | exports.rendering = function (maps, styles, params) { 4 | var project = params.project; 5 | var showCode = params.showCode; 6 | var tmpl = params.tmpl; 7 | var colorPalette = params.colorPalette; 8 | return ejs.render(tmpl, { 9 | projectName: project, 10 | processedCSS: styles[0].css, 11 | tmplStyle: styles[1].css, 12 | codeStyle: styles[2].css, 13 | showCode: showCode, 14 | colorPalette: colorPalette, 15 | maps: maps 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/colorPalette.js: -------------------------------------------------------------------------------- 1 | var annotationBlock = require('css-annotation-block'); 2 | var isColor = require('is-color'); 3 | 4 | exports.parse = function (css) { 5 | var results = annotationBlock(css); 6 | 7 | var colorRoot = [] 8 | var colorPalette = [] 9 | 10 | results.forEach(function (result) { 11 | if (result.name === 'color') { 12 | result.nodes.forEach(function (node) { 13 | colorRoot.push(node); 14 | }); 15 | } 16 | }); 17 | 18 | colorRoot.forEach(function (color) { 19 | color.walkDecls(function (decl) { 20 | if (isColor(decl.value)) { 21 | colorPalette.push({ 22 | name: decl.prop.replace(/^--/, ''), 23 | color: decl.value 24 | }); 25 | } 26 | }); 27 | }); 28 | 29 | return colorPalette; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-style-guide", 3 | "version": "0.14.0", 4 | "description": "PostCSS plugin to generate a style guide automatically", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tape test/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/morishitter/postcss-style-guide/git" 12 | }, 13 | "keywords": [ 14 | "style-guide", 15 | "postcss-plugin" 16 | ], 17 | "author": "Masaaki Morishita", 18 | "license": "MIT", 19 | "dependencies": { 20 | "css-annotation": "^0.6.0", 21 | "css-annotation-block": "^0.1.0", 22 | "cssnano": "^3.0.1", 23 | "ejs": "^2.3.1", 24 | "highlight.js": "^8.5.0", 25 | "is-color": "^0.2.0", 26 | "marked": "^0.3.3", 27 | "mkdirp": "^0.5.1", 28 | "postcss": "^5.0.2", 29 | "psg-theme-default": "^1.0.0" 30 | }, 31 | "devDependencies": { 32 | "tape": "^4.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Copyright (c) 2015 Masaaki Morishita 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 | -------------------------------------------------------------------------------- /docs/theme-guideline.md: -------------------------------------------------------------------------------- 1 | # Guideline to create themes 2 | 3 | `postcss-style-guide` is a PostCSS plugin that generate CSS stye guide automatically. 4 | 5 | Developers can set theme of the style guide by option of `postcss-style-guide`. 6 | 7 | ```js 8 | var postcss = require('postcss'); 9 | var styleGuide = require('postcss-style-guide'); 10 | var css = fs.readFileSync('input.css', 'utf-8'); 11 | 12 | var options = { 13 | theme: 'forest', 14 | name: 'project name' 15 | }; 16 | 17 | postcss() 18 | .use(styleGuide(options)) 19 | .process(css) 20 | .css; 21 | ``` 22 | 23 | ## Package name 24 | 25 | The name of `postcss-style-guide` theme is must be prefix `psg-theme-`. 26 | The prefix, `psg-` means `postcss-style-guide-`. 27 | 28 | Ex. `psg-theme-forest` 29 | 30 | ## How to create themes 31 | 32 | ### Template file name 33 | 34 | The template extension of `postcss-style-guide` must be named `template.ejs` file and stylesheet is `style.css` too. 35 | 36 | - Template: `template.ejs` 37 | - Stylesheet: `style.css` 38 | 39 | ### Set keyword in `package.json` 40 | 41 | Themes of `postcss-style-guide` must have the `psg-theme` keyword in their `package.json`. 42 | 43 | ### Screenshot image for example 44 | 45 | You should put a screenshot image for example. 46 | 47 | like this: 48 | 49 | ![Default style guide design](../style-guide-default.png) 50 | -------------------------------------------------------------------------------- /lib/analyzer.js: -------------------------------------------------------------------------------- 1 | var annotation = require('css-annotation'); 2 | 3 | exports.setModules = function (syntaxHighlighter, markdownParser) { 4 | this.syntaxHighlighter = syntaxHighlighter; 5 | this.markdownParser = markdownParser; 6 | } 7 | 8 | exports.analyze = function (root, opts) { 9 | var list = []; 10 | var linkId = 0; 11 | root.walkComments(function (comment) { 12 | var meta = annotation.read(comment.text); 13 | if (!meta.documents && !meta.document && !meta.docs && !meta.doc && !meta.styleguide) { 14 | return; 15 | } 16 | if (comment.parent.type !== 'root') { 17 | return; 18 | } 19 | var rules = []; 20 | var rule = comment.next(); 21 | while (rule && rule.type !== 'comment') { 22 | if (rule.type === 'rule' || rule.type === 'atrule') { 23 | rules.push(rule.toString()); 24 | } 25 | rule = rule.next(); 26 | } 27 | var joined = rules.join('\n\n'); 28 | var md = comment.text.replace(/(@document|@doc|@docs|@styleguide)\s*\n/, ''); 29 | md = md.replace(new RegExp('@(' + Object.keys(meta).join('|') + ')\\s.*\\n', 'g'), ''); 30 | 31 | md = md.replace(/@title\s.*\n/, ''); 32 | 33 | list.push({ 34 | meta: meta, 35 | rule: this.syntaxHighlighter.highlight(joined), 36 | html: this.markdownParser(md), 37 | link: { 38 | id: (meta.id || 'psg-link-' + linkId), 39 | title: meta.title || null 40 | } 41 | }); 42 | linkId++; 43 | }.bind(this)); 44 | return list; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var postcss = require('postcss'); 3 | 4 | var analyzer = require('./lib/analyzer'); 5 | var newParams = require('./lib/params'); 6 | var template = require('./lib/template'); 7 | var fileWriter = require('./lib/fileWriter'); 8 | var markdownParser = require('./lib/markdown'); 9 | var syntaxHighlighter = require('./lib/syntaxHighlight'); 10 | var colorPalette = require('./lib/colorPalette'); 11 | 12 | module.exports = postcss.plugin('postcss-style-guide', function (opts) { 13 | opts = opts || {}; 14 | analyzer.setModules(syntaxHighlighter, markdownParser); 15 | var func = function (root, result) { 16 | var resultOpts = result.opts || {}; 17 | try { 18 | var params = newParams(root, opts, resultOpts); 19 | } catch (err) { 20 | throw err; 21 | } 22 | var maps = analyzer.analyze(root, opts); 23 | var palette = colorPalette.parse(root.toString()); 24 | var promise = syntaxHighlighter.execute({ 25 | src: params.src, 26 | tmplStyle: params.style, 27 | stylePath: require.resolve('highlight.js/styles/github.css') 28 | }).then(function (styles) { 29 | var html = template.rendering(maps, styles, { 30 | project: params.project, 31 | showCode: params.showCode, 32 | tmpl: params.template, 33 | colorPalette: palette 34 | }); 35 | fileWriter.write(params.dest, html); 36 | 37 | if (!opts.silent) { 38 | console.log('Successfully created style guide at ' + path.relative(process.cwd(), params.dest) + '!'); 39 | } 40 | 41 | return root; 42 | }).catch(function (err) { 43 | console.error('generate err:', err); 44 | return root; 45 | }); 46 | return promise; 47 | }; 48 | return func; 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /lib/params.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var result = require('./utils').result; 4 | 5 | module.exports = function (root, opts, pluginOpts) { 6 | var params = {}; 7 | var cwd = process.cwd(); 8 | 9 | if (!opts.src) { 10 | params.src = root.toString(); 11 | } else { 12 | var src = path.resolve(cwd, opts.src); 13 | params.src = fs.readFileSync(src, 'utf8'); 14 | } 15 | 16 | if (opts.dest) { 17 | params.dest = path.resolve(cwd, result(opts.dest, opts, pluginOpts)); 18 | } else { 19 | var from = (pluginOpts || {}).from; // for the gulp and grunt 20 | var output = from ? path.basename(from, '.css') : 'index.html' 21 | params.dest = path.resolve(cwd, 'styleguide', output); 22 | } 23 | 24 | params.project = opts.project || 'Style Guide'; 25 | if (opts.showCode === undefined || opts.showCode == true) { 26 | params.showCode = true; 27 | } else { 28 | params.showCode = false; 29 | } 30 | 31 | var theme; 32 | if (opts.theme) { 33 | theme = 'psg-theme-' + opts.theme; 34 | } else { 35 | theme = 'psg-theme-default'; 36 | } 37 | 38 | var themePath; 39 | if (opts.themePath) { 40 | themePath = opts.themePath; 41 | } else { 42 | var isFind = module.paths.some(function (m) { 43 | var p = path.resolve(m, theme); 44 | if (!isExists(p)) { 45 | return false; 46 | } 47 | themePath = p; 48 | return true; 49 | }); 50 | if (!isFind) { 51 | throw new Error('specify theme is not found'); 52 | } 53 | } 54 | 55 | try { 56 | var templateFile = path.resolve(themePath, 'template.ejs'); 57 | params.template = fs.readFileSync(templateFile, 'utf-8'); 58 | } catch (err) { 59 | throw err; 60 | } 61 | try { 62 | var templateStyle = path.resolve(themePath, 'style.css'); 63 | params.style = fs.readFileSync(templateStyle, 'utf-8'); 64 | } catch (err) { 65 | throw err; 66 | } 67 | return params; 68 | } 69 | 70 | function isExists(dirPath) { 71 | try { 72 | fs.statSync(dirPath); 73 | } catch (err) { 74 | return false; 75 | } 76 | return true; 77 | } 78 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.14.0 2 | 3 | Support multi-file and meta improvements [#50](https://github.com/morishitter/postcss-style-guide/pull/50) by @dwightjack 4 | 5 | ## 0.13.0 6 | 7 | Update default theme. 8 | 9 | ## 0.12.1 10 | 11 | Fixe a bug. 12 | 13 | ## 0.12.0 14 | 15 | Can pass the color palette object that generate from CSS Custom Properties to the template file 16 | 17 | ## 0.11.1 18 | 19 | Fixed a bug [#36](https://github.com/morishitter/postcss-style-guide/issues/36). 20 | 21 | ## 0.11.0 22 | 23 | Introduce `@title` annotation to set the title of documents 24 | 25 | ## 0.10.1 26 | 27 | Fixed a bug. 28 | 29 | ## 0.10.0 30 | 31 | - Implemented as a asynchronous plugin (It returns `Promise` object). 32 | - Changed some option's name 33 | 34 | 35 | ## 0.9.7 36 | 37 | Fixed a bug. 38 | 39 | ## 0.9.6 40 | 41 | Fixed a bug. [#32](https://github.com/morishitter/postcss-style-guide/pull/32) 42 | 43 | ## 0.9.5 44 | 45 | Resolve path to `highlight.js` and `github.css`. [#31](https://github.com/morishitter/postcss-style-guide/pull/31) 46 | 47 | ## 0.9.4 48 | 49 | - Fix codeStyle path [#27](https://github.com/morishitter/postcss-style-guide/pull/27) 50 | - Add Gruntfile sample [#28](https://github.com/morishitter/postcss-style-guide/pull/28) 51 | 52 | Thanks [@mitsuruog](https://github.com/mitsuruog). 53 | 54 | ## 0.9.3 55 | 56 | - Fix codeStyle path 57 | - Fix [#26](https://github.com/morishitter/postcss-style-guide/pull/26) 58 | 59 | ## 0.9.2 60 | 61 | Fix [#22](https://github.com/morishitter/postcss-style-guide/issues/22), thanks @watilde . 62 | 63 | ## 0.9.1 64 | 65 | Bump `psg-theme-default` to v0.5.0. 66 | 67 | ## 0.9.0 68 | 69 | Generate styleguide from comments have the special annotation 70 | 71 | Annotations: 72 | 73 | - `@styleguide` 74 | - `@documents` 75 | - `@document` 76 | - `@docs` 77 | - `@doc` 78 | 79 | ## 0.8.0 80 | 81 | - Introduce `options.dir` 82 | 83 | ## 0.7.1 84 | 85 | - Bump cssnanot to v3 86 | 87 | ## 0.7.0 88 | 89 | - Moved `processedCSS` parameter in `options` 90 | 91 | ## 0.6.0 92 | 93 | - Remove `rootStyle` 94 | - Add `showCode` option 95 | 96 | ## 0.5.1 97 | 98 | - Fix #17 99 | 100 | ## 0.5.0 101 | 102 | - Update PostCSS to v5.0 103 | - Fix to check parameters 104 | 105 | ## 0.4.1 106 | 107 | - Fix a bug to use other postcss-style-guide themes 108 | 109 | ## 0.4.0 110 | 111 | - Intoroduce processed CSS as first parameter 112 | - Fix some bugs 113 | 114 | ## 0.3.1 115 | 116 | * Fix a [bug](https://github.com/morishitter/postcss-style-guide/issues/9) 117 | 118 | ## 0.3.0 119 | 120 | * Introdue themes 121 | * Initialize a document for building themes 122 | 123 | ## 0.2.1 124 | 125 | * Tweak template design 126 | 127 | ## 0.2 128 | 129 | * Change logic of generating style guide 130 | 131 | ## 0.1.1 132 | 133 | * Tweak template design 134 | 135 | ## 0.1 136 | 137 | * Initial release. 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-style-guide [![Build Status](https://travis-ci.org/morishitter/postcss-style-guide.svg)](https://travis-ci.org/morishitter/postcss-style-guide) 2 | 3 | [PostCSS](https://github.com/postcss/postcss) plugin to generate a style guide automatically. 4 | 5 | CSS comments will be parsed through Markdown and displayed in a generated HTML document. 6 | 7 | ## Install 8 | 9 | ```shell 10 | $ npm install postcss-style-guide 11 | ``` 12 | 13 | ## Example 14 | 15 | Node.js: 16 | 17 | ```js 18 | var fs = require('fs'); 19 | var postcss = require('postcss'); 20 | var styleguide = require('postcss-style-guide'); 21 | var input = fs.readFileSync('input.css', 'utf8'); 22 | 23 | var output = postcss([ 24 | styleguide 25 | ]).process(input) 26 | .then(function (reuslt) { 27 | var output = fs.readFileSync('styleGuide/index.html', 'utf8'); 28 | console.log('output:', output); 29 | }); 30 | ``` 31 | 32 | in [Gulp](https://github.com/gulpjs/gulp): 33 | 34 | ```js 35 | var gulp = require('gulp') 36 | 37 | gulp.task('build:css', function () { 38 | var concat = require('gulp-concat') 39 | var postcss = require('gulp-postcss') 40 | var autoprefixer = require('autoprefixer') 41 | var customProperties = require('postcss-custom-properties') 42 | var Import = require('postcss-import') 43 | var styleGuide = require('postcss-style-guide') 44 | var nano = require('cssnano') 45 | 46 | return gulp.src('./app.css') 47 | .pipe(postcss([ 48 | Import, 49 | customProperties({ preserve: true }), 50 | autoprefixer, 51 | styleGuide({ 52 | project: 'Project name', 53 | dest: 'styleguide/index.html', 54 | showCode: false, 55 | themePath: '../' 56 | }), 57 | nano 58 | ])) 59 | .pipe(concat('app.min.css')) 60 | .pipe(gulp.dest('dist/css')) 61 | }) 62 | ``` 63 | 64 | We can generate color palette from CSS Custom Properties with `@start color` and `@end color` annotations. 65 | 66 | `app.css`: 67 | 68 | ```css 69 | @import "color"; 70 | @import "button"; 71 | ``` 72 | 73 | `color.css`: 74 | 75 | ```css 76 | @import "button"; 77 | /* @start color */ 78 | :root { 79 | --red: #e23B00; 80 | --blue: #3f526b; 81 | --black: #000; 82 | --background: #faf8f5; 83 | } 84 | /* @end color */ 85 | ``` 86 | 87 | postcss-style-guide generate style guide from CSS comments that have special annotation(`@styleguide`). 88 | 89 | `@title`: set component name 90 | 91 | `button.css`: 92 | 93 | ```css 94 | /* 95 | @styleguide 96 | 97 | @title Button 98 | 99 | Use the button classes on and ``, ` 102 | 103 | 104 | 105 | 106 | 107 | 108 | */ 109 | .button { 110 | display: flex; 111 | align-items: center; 112 | justify-content: center; 113 | border-radius: 6px; 114 | cursor: pointer; 115 | } 116 | 117 | .button--large { 118 | width: 140px; 119 | height: 40px; 120 | font-size: 14px; 121 | } 122 | 123 | .button--red { 124 | color: #fff; 125 | background-color: var(--red); 126 | } 127 | 128 | .button--blue { 129 | color: #fff; 130 | background-color: var(--blue); 131 | } 132 | ``` 133 | 134 | You will get `styleguide/index.html` for the style guide. 135 | 136 | ![Default style guide design](./style-guide-default.png) 137 | 138 | 139 | ## Options 140 | 141 | - `options.src`: The path to the source CSS file. 142 | - `options.dest`: The path to style guide file. (default: `styleguide/index.html`) 143 | - `options.project`: Project name. (default: `Style Guide`) 144 | - `options.showCode`: The flag to show CSS code (default: `true`) 145 | - `options.theme`: Theme name. (default: `psg-theme-default`) 146 | - `options.themePath`: The path to theme file. (default: `node_modules/psg-theme-default`) 147 | 148 | ## Themes 149 | 150 | You can select a theme of style guide with `options.theme`. 151 | And you can also create original themes. 152 | When you create themes, please read [theme guideline](https://github.com/morishitter/postcss-style-guide/blob/master/docs/theme-guideline.md) 153 | 154 | All of postcss-style-guide themes that can be used are [here](https://www.npmjs.com/search?q=psg-theme). 155 | 156 | ### Themes list 157 | 158 | - [default](https://github.com/morishitter/psg-theme-default) 159 | - [sassline](https://github.com/sotayamashita/psg-theme-sassline) 160 | - [1column](https://github.com/seka/psg-theme-1column) 161 | - [forest](https://github.com/morishitter/psg-theme-forest) 162 | 163 | ### How to develop postcss-style-guide theme 164 | 165 | - [Yeoman Generator](https://github.com/sotayamashita/generator-psg-theme) 166 | 167 | ## License 168 | 169 | The MIT License (MIT) 170 | 171 | Copyright (c) 2015 Masaaki Morishita 172 | -------------------------------------------------------------------------------- /test/output.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Style Guide 6 | 7 | 8 | 9 | 10 |
11 | 34 | 35 |
36 |
37 |

Style Guide

38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 | 46 |
47 |

h1

48 |
49 |
50 |
51 | 52 |
53 | 54 | 55 |
56 |

h2

57 |
58 |
59 |
60 | 61 |
62 |
63 | 64 |
65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var test = require('tape'); 4 | var postcss = require('postcss'); 5 | 6 | var styleGuide = require('../'); 7 | var newParams = require('../lib/params'); 8 | var template = require('../lib/template'); 9 | var fileWriter = require('../lib/fileWriter'); 10 | var markdownParser = require('../lib/markdown'); 11 | var syntaxHighlighter = require('../lib/syntaxHighlight'); 12 | var analyzer = require('../lib/analyzer'); 13 | var colorPalette = require('../lib/colorPalette'); 14 | 15 | test('params: default options', function (t) { 16 | var src = 'test/input.css'; 17 | var actual = newParams({}, { 18 | src: src 19 | }); 20 | var cwd = process.cwd(); 21 | var themePath = path.resolve('node_modules', 'psg-theme-default'); 22 | var templateFile = path.resolve(themePath, 'template.ejs'); 23 | var templateStyle = path.resolve(themePath, 'style.css'); 24 | var expected = { 25 | src: fs.readFileSync(src, 'utf8'), 26 | dest: path.resolve(cwd, 'styleguide/index.html'), 27 | project: 'Style Guide', 28 | showCode: true, 29 | template: fs.readFileSync(templateFile, 'utf-8'), 30 | style: fs.readFileSync(templateStyle, 'utf-8') 31 | }; 32 | t.plan(1); 33 | t.deepEqual(actual, expected); 34 | t.end(); 35 | }); 36 | 37 | test('params: custom options', function (t) { 38 | var cwd = process.cwd(); 39 | var src = path.resolve(cwd, 'test/input.css'); 40 | var dest = path.resolve(cwd, 'test/dest/index.html'); 41 | var project = 'custom style guide'; 42 | var themePath = path.resolve('node_modules', 'psg-theme-default'); 43 | var actual = newParams({}, { 44 | src: src, 45 | dest: dest, 46 | project: project, 47 | showCode: false, 48 | themePath: themePath 49 | }); 50 | var templateFile = path.resolve(themePath, 'template.ejs'); 51 | var templateStyle = path.resolve(themePath, 'style.css'); 52 | var expected = { 53 | src: fs.readFileSync(src, 'utf8'), 54 | dest: dest, 55 | project: project, 56 | showCode: false, 57 | template: fs.readFileSync(templateFile, 'utf-8'), 58 | style: fs.readFileSync(templateStyle, 'utf-8') 59 | }; 60 | t.plan(1); 61 | t.deepEqual(actual, expected); 62 | t.end(); 63 | }); 64 | 65 | test('template: render html', function (t) { 66 | var themePath = path.resolve('node_modules', 'psg-theme-default'); 67 | var templateFile = path.resolve(themePath, 'template.ejs'); 68 | var params = { 69 | project: 'project', 70 | tmpl: fs.readFileSync(templateFile, 'utf8'), 71 | params: false 72 | }; 73 | var actual = template.rendering([], ['', '', ''], params); 74 | // FIXME: Generate dynamic code that is not desirable 75 | var expected = '\n\n \n \n project\n \n \n\n \n
\n \n\n
\n
\n

project

\n
\n\n
\n \n \n
\n
\n\n
\n\n \n\n'; 76 | t.plan(1); 77 | t.same(actual, expected); 78 | t.end(); 79 | }); 80 | 81 | test('fileWriter: write file', function (t) { 82 | var filePath = 'test/dest/write' 83 | var str = ''; 84 | fileWriter.write(filePath, str); 85 | var cwd = process.cwd(); 86 | var dest = path.resolve(cwd, filePath + '.html'); 87 | var actual = fs.existsSync(dest); 88 | var expected = true; 89 | t.plan(1); 90 | t.same(actual, expected); 91 | t.end(); 92 | }); 93 | 94 | test('fileWriter: confirm wrote item', function (t) { 95 | var filePath = 'test/dest/write' 96 | var str = 'Hello, World!'; 97 | fileWriter.write(filePath, str); 98 | var cwd = process.cwd(); 99 | var dest = path.resolve(cwd, filePath + '.html'); 100 | var actual = fs.readFileSync(dest, 'utf8'); 101 | var expected = str; 102 | t.plan(1); 103 | t.same(actual, expected); 104 | t.end(); 105 | }); 106 | 107 | test('analyzer: analyze root node', function (t) { 108 | var cwd = process.cwd(); 109 | var filePath = path.resolve(cwd, 'test/input.css'); 110 | var css = fs.readFileSync(filePath, 'utf8'); 111 | var root = postcss.parse(css); 112 | 113 | analyzer.setModules(syntaxHighlighter, markdownParser); 114 | var actual = analyzer.analyze(root); 115 | 116 | var expected = [{ 117 | meta: { styleguide: true, title: 'input sample', mymeta: 'test' }, 118 | rule: '.class {\n color: blue;\n}', 119 | html: '

h1

', 120 | link: { 121 | id: 'psg-link-0', 122 | title: 'input sample' 123 | } 124 | }, 125 | { 126 | meta: { doc: true }, 127 | rule: '.class {\n color: red;\n}', 128 | html: '

h2

', 129 | link: { 130 | id: 'psg-link-1', 131 | title: null 132 | } 133 | }]; 134 | t.plan(1); 135 | t.same(actual, expected); 136 | t.end(); 137 | }); 138 | 139 | test('colorPalette: generate color palette from custom properties', function (t) { 140 | var cwd = process.cwd(); 141 | var filePath = path.resolve(cwd, 'test/color.css'); 142 | var css = fs.readFileSync(filePath, 'utf8'); 143 | var actual = colorPalette.parse(css); 144 | var expected = [ 145 | { name: 'red', color: '#ff0000' }, 146 | { name: 'green', color: '#00ff00' }, 147 | { name: 'blue', color: '#0000ff' } 148 | ]; 149 | t.plan(1); 150 | t.same(actual, expected); 151 | t.end(); 152 | }); 153 | 154 | test('integration test: exist output', function (t) { 155 | var opts = { 156 | name: 'Default theme', 157 | src: 'test/input.css', 158 | dest: 'test/dest/exist/index.html', 159 | silent: true 160 | }; 161 | var cwd = process.cwd(); 162 | var src = path.resolve(cwd, 'test/input.css'); 163 | var css = fs.readFileSync(src, 'utf-8'); 164 | t.plan(1); 165 | postcss([styleGuide(opts)]) 166 | .process(css) 167 | .then(function () { 168 | var dest = path.resolve(cwd, 'test/dest/exist/index.html'); 169 | var actual = fs.existsSync(dest); 170 | var expected = true; 171 | t.same(actual, expected); 172 | t.end(); 173 | }) 174 | .catch(function (err) { 175 | t.error(err); 176 | t.end(); 177 | }); 178 | }); 179 | 180 | test('integration test: confirm output', function (t) { 181 | var opts = { 182 | name: 'Default theme', 183 | src: 'test/input.css', 184 | dest: 'test/dest/confirm/index.html' 185 | }; 186 | var cwd = process.cwd(); 187 | var src = path.resolve(cwd, 'test/input.css'); 188 | var css = fs.readFileSync(src, 'utf-8'); 189 | t.plan(1); 190 | postcss([styleGuide(opts)]) 191 | .process(css) 192 | .then(function () { 193 | var dest = path.resolve(cwd, 'test/dest/confirm/index.html'); 194 | var actual = fs.readFileSync(dest, 'utf8'); 195 | var expectedPath = path.resolve(cwd, 'test/output.html'); 196 | var expected = fs.readFileSync(expectedPath, 'utf8'); 197 | t.same(actual, expected); 198 | t.end(); 199 | }) 200 | .catch(function (err) { 201 | t.error(err); 202 | t.end(); 203 | }); 204 | }); 205 | 206 | test('async plugin test', function (t) { 207 | var starts = 0; 208 | var finish = 0; 209 | var asyncFunc = function (css) { 210 | return new Promise(function (resolve) { 211 | starts += 1; 212 | setTimeout(function () { 213 | finish += 1; 214 | css.append('a {}'); 215 | resolve(); 216 | }, 100); 217 | }); 218 | }; 219 | t.plan(3); 220 | postcss([asyncFunc, styleGuide, asyncFunc]) 221 | .process('') 222 | .then(function (result) { 223 | t.same(starts, 2); 224 | t.same(finish, 2); 225 | t.same(result.css, 'a {}\na {}'); 226 | t.end(); 227 | }) 228 | .catch(function (err) { 229 | t.error(err); 230 | t.end(); 231 | }); 232 | }); 233 | 234 | test.onFinish(function () { 235 | var cwd = process.cwd(); 236 | var dest = path.resolve(cwd, 'test/dest'); 237 | var recursiveDeleteDir = function (dest) { 238 | if (!isExists(dest)) { 239 | return 240 | } 241 | fs.readdirSync(dest).forEach(function (file) { 242 | var filePath = path.resolve(dest, file); 243 | if (fs.lstatSync(filePath).isDirectory()) { 244 | recursiveDeleteDir(filePath); 245 | } else { 246 | fs.unlinkSync(filePath); 247 | } 248 | }); 249 | fs.rmdirSync(dest); 250 | }; 251 | recursiveDeleteDir(dest); 252 | }); 253 | 254 | function isExists (dirPath) { 255 | try { 256 | fs.statSync(dirPath); 257 | } catch (err) { 258 | return false; 259 | } 260 | return true; 261 | } 262 | 263 | --------------------------------------------------------------------------------