├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── index.js ├── lib └── fs-promise.js ├── package.json ├── test.js └── test ├── basic-import.md ├── basic.css ├── basic.expect.css ├── extras └── README.md └── humans.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{json,yml}] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-shadow-restricted-names": [2], 4 | "computed-property-spacing": [2], 5 | "no-empty-character-class": [2], 6 | "no-irregular-whitespace": [2], 7 | "no-unexpected-multiline": [2], 8 | "no-multiple-empty-lines": [2], 9 | "space-return-throw-case": [2], 10 | "no-constant-condition": [2], 11 | "no-extra-boolean-cast": [2], 12 | "no-inner-declarations": [2], 13 | "no-this-before-super": [2], 14 | "no-array-constructor": [2], 15 | "object-curly-spacing": [2, "always"], 16 | "no-floating-decimal": [2], 17 | "no-warning-comments": [2], 18 | "handle-callback-err": [2], 19 | "no-unneeded-ternary": [2], 20 | "operator-assignment": [2], 21 | "space-before-blocks": [2], 22 | "no-native-reassign": [2], 23 | "no-trailing-spaces": [2], 24 | "operator-linebreak": [2, "after"], 25 | "consistent-return": [2], 26 | "no-duplicate-case": [2], 27 | "no-invalid-regexp": [2], 28 | "no-negated-in-lhs": [2], 29 | "constructor-super": [2], 30 | "no-nested-ternary": [0], 31 | "no-extend-native": [2], 32 | "block-scoped-var": [2], 33 | "no-control-regex": [2], 34 | "no-sparse-arrays": [2], 35 | "no-throw-literal": [2], 36 | "no-return-assign": [2], 37 | "no-const-assign": [2], 38 | "no-class-assign": [2], 39 | "no-extra-parens": [2], 40 | "no-regex-spaces": [2], 41 | "no-implied-eval": [2], 42 | "no-useless-call": [2], 43 | "no-self-compare": [2], 44 | "no-octal-escape": [2], 45 | "no-new-wrappers": [2], 46 | "no-process-exit": [2], 47 | "no-catch-shadow": [2], 48 | "linebreak-style": [2], 49 | "space-infix-ops": [2], 50 | "space-unary-ops": [2], 51 | "no-func-assign": [2], 52 | "no-unreachable": [2], 53 | "accessor-pairs": [2], 54 | "no-empty-label": [2], 55 | "no-fallthrough": [2], 56 | "no-path-concat": [2], 57 | "no-new-require": [2], 58 | "no-spaced-func": [2], 59 | "no-unused-vars": [2], 60 | "spaced-comment": [2], 61 | "no-delete-var": [2], 62 | "comma-spacing": [2], 63 | "no-extra-semi": [2], 64 | "no-extra-bind": [2], 65 | "arrow-spacing": [2], 66 | "prefer-spread": [0], 67 | "no-new-object": [2], 68 | "no-multi-str": [2], 69 | "semi-spacing": [2], 70 | "no-lonely-if": [2], 71 | "dot-notation": [2], 72 | "dot-location": [2, "property"], 73 | "comma-dangle": [2, "never"], 74 | "no-dupe-args": [2], 75 | "no-dupe-keys": [2], 76 | "no-ex-assign": [2], 77 | "no-obj-calls": [2], 78 | "valid-typeof": [2], 79 | "default-case": [2], 80 | "no-redeclare": [2], 81 | "no-div-regex": [2], 82 | "no-sequences": [2], 83 | "no-label-var": [2], 84 | "comma-style": [2], 85 | "brace-style": [2], 86 | "no-debugger": [2], 87 | "quote-props": [0], 88 | "no-iterator": [2], 89 | "no-new-func": [2], 90 | "key-spacing": [2, { "align": "value" }], 91 | "complexity": [2], 92 | "new-parens": [2], 93 | "no-eq-null": [2], 94 | "no-bitwise": [0], 95 | "wrap-iife": [2], 96 | "no-caller": [2], 97 | "use-isnan": [2], 98 | "no-labels": [2], 99 | "no-shadow": [2], 100 | "camelcase": [2], 101 | "eol-last": [2], 102 | "no-octal": [2], 103 | "no-empty": [2], 104 | "no-alert": [2], 105 | "no-proto": [2], 106 | "no-undef": [2], 107 | "no-eval": [2], 108 | "no-with": [2], 109 | "no-void": [2], 110 | "new-cap": [2], 111 | "eqeqeq": [2], 112 | "no-new": [2], 113 | "quotes": [2, "single"], 114 | "indent": [2, "tab"], 115 | "semi": [2, "always"], 116 | "yoda": [2, "never"] 117 | }, 118 | "env": { 119 | "mocha": true, 120 | "node": true 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | test/*.actual.css 4 | test/styleguide 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - stable 5 | - "0.12" 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.5.2 (2015-05-12) 2 | 3 | - Updated: Markdown loading 4 | - Removed: Trailing spaces 5 | - Fixed: Markdown file imports 6 | - Added: Relative markdown file paths 7 | 8 | ## 1.5.1 (2015-02-10) 9 | 10 | - Removed: `fs-promise` for internal method 11 | - Updated: Failed import warning message 12 | - Updated: Tests 13 | 14 | ## 1.5.0 (2015-01-29) 15 | 16 | - Added: Ability to copy additional assets to be copied to the destination 17 | - Updated: Clear destination before building styleguide 18 | - Updated: Documentation and tests 19 | - Updated: Package dependencies 20 | 21 | ## 1.4.1 (2015-01-28) 22 | 23 | - Updated: Handle comment lines with starting spaces 24 | - Updated: Moved tests 25 | 26 | ## 1.4.0 (2015-01-26) 27 | 28 | - Added: Automatically detects sibling markdown documentation 29 | - Added: Forward plugin options when theme-specific options are unspecified 30 | - Updated: Detects documentation relative to the original partial 31 | - Updated: Documentation 32 | 33 | ## 1.3.0 (2015-11-17) 34 | 35 | - Updated: Package dependencies 36 | - Updated: Documentation 37 | 38 | ## 1.2.0 (2015-11-09) 39 | 40 | - Updated: Package dependencies 41 | - Updated: Documentation and tests 42 | 43 | ## 1.1.1 (2015-11-02) 44 | 45 | - Updated: write style guides to directories not yet created 46 | 47 | ## 1.1.0 (2015-11-02) 48 | 49 | - Added: mdcss options are forwarded to theme 50 | - Added: `index` option from theme 51 | - Added: Moved some functionality from mdcss themes 52 | - Updated: Documentation and tests 53 | 54 | ## 1.0.1 (2015-10-30) 55 | 56 | - Added: Safely slash `destination` option 57 | 58 | ## 1.0.0 (2015-10-30) 59 | 60 | - Added: Initial release 61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | You want to help? You rock! Now, take a moment to be sure your contributions make sense to everyone else. 2 | 3 | ## Reporting Issues 4 | 5 | Found a problem? Want a new feature? 6 | 7 | - See if your issue or idea has [already been reported]. 8 | - Provide a [reduced test case] or a [live example]. 9 | 10 | Remember, a bug is a _demonstrable problem_ caused by _our_ code. 11 | 12 | ## Submitting Pull Requests 13 | 14 | Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits. 15 | 16 | 1. To begin, [fork this project], clone your fork, and add our upstream. 17 | ```bash 18 | # Clone your fork of the repo into the current directory 19 | git clone https://github.com//mdcss 20 | # Navigate to the newly cloned directory 21 | cd mdcss 22 | # Assign the original repo to a remote called "upstream" 23 | git remote add upstream https://github.com/jonathantneal/mdcss 24 | # Install the tools necessary for development 25 | npm install 26 | ``` 27 | 28 | 2. Create a branch for your feature or fix: 29 | ```bash 30 | # Move into a new branch for a feature 31 | git checkout -b feature/thing 32 | ``` 33 | ```bash 34 | # Move into a new branch for a fix 35 | git checkout -b fix/something 36 | ``` 37 | 38 | 3. Be sure your code follows our practices. 39 | ```bash 40 | # Test current code 41 | npm run test 42 | ``` 43 | 44 | 4. Push your branch up to your fork: 45 | ```bash 46 | # Push a feature branch 47 | git push origin feature/thing 48 | ``` 49 | ```bash 50 | # Push a fix branch 51 | git push origin fix/something 52 | ``` 53 | 54 | 5. Now [open a pull request] with a clear title and description. 55 | 56 | [already been reported]: issues 57 | [fork this project]: fork 58 | [live example]: http://codepen.io/pen 59 | [open a pull request]: https://help.github.com/articles/using-pull-requests/ 60 | [reduced test case]: https://css-tricks.com/reduced-test-cases/ 61 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # CC0 1.0 Universal License 2 | 3 | Public Domain Dedication 4 | 5 | The person(s) who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. 6 | 7 | You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. 8 | 9 | In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights. 10 | 11 | Unless expressly stated otherwise, the person(s) who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. 12 | 13 | When using or citing the work, you should not imply endorsement by the author or the affirmer. 14 | 15 | This is a [human-readable summary of the Legal Code](https://creativecommons.org/publicdomain/zero/1.0/) ([read the full text](https://creativecommons.org/publicdomain/zero/1.0/legalcode)). 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [![NPM Version][npm-img]][npm] [![Build Status][ci-img]][ci] 6 | 7 | [mdcss] lets you easily create and maintain style guides with CSS comments using Markdown. 8 | 9 | /*--- 10 | title: Buttons 11 | section: Base CSS 12 | --- 13 | 14 | Button styles can be applied to any element. Typically you'll want to use 15 | either a ` 19 | Some Page 20 | ``` 21 | */ 22 | 23 | .btn { 24 | background-color: black; 25 | color: white; 26 | } 27 | 28 | ## Usage 29 | 30 | Add [mdcss] to your build tool: 31 | 32 | ```bash 33 | npm install mdcss --save-dev 34 | ``` 35 | 36 | #### Node 37 | 38 | ```js 39 | require('mdcss').process(YOUR_CSS, { /* options */ }); 40 | ``` 41 | 42 | #### PostCSS 43 | 44 | Add [PostCSS] to your build tool: 45 | 46 | ```bash 47 | npm install postcss --save-dev 48 | ``` 49 | 50 | Load [mdcss] as a PostCSS plugin: 51 | 52 | ```js 53 | postcss([ 54 | require('mdcss')({ /* options */ }) 55 | ]); 56 | ``` 57 | 58 | #### Gulp 59 | 60 | Add [Gulp PostCSS] to your build tool: 61 | 62 | ```bash 63 | npm install gulp-postcss --save-dev 64 | ``` 65 | 66 | Enable [mdcss] within your Gulpfile: 67 | 68 | ```js 69 | var postcss = require('gulp-postcss'); 70 | 71 | gulp.task('css', function () { 72 | return gulp.src('./css/src/*.css').pipe( 73 | postcss([ 74 | require('mdcss')({ /* options */ }) 75 | ]) 76 | ).pipe( 77 | gulp.dest('./css') 78 | ); 79 | }); 80 | ``` 81 | 82 | #### Grunt 83 | 84 | Add [Grunt PostCSS] to your build tool: 85 | 86 | ```bash 87 | npm install grunt-postcss --save-dev 88 | ``` 89 | 90 | Enable [mdcss] within your Gruntfile: 91 | 92 | ```js 93 | grunt.loadNpmTasks('grunt-postcss'); 94 | 95 | grunt.initConfig({ 96 | postcss: { 97 | options: { 98 | processors: [ 99 | require('mdcss')({ /* options */ }) 100 | ] 101 | }, 102 | dist: { 103 | src: 'css/*.css' 104 | } 105 | } 106 | }); 107 | ``` 108 | 109 | ## Options 110 | 111 | #### `theme` 112 | 113 | Type: `NPM Repository` 114 | Default: `require('mdcss-theme-github')` 115 | 116 | The theme used by [mdcss] to create the style guide. 117 | 118 | ```js 119 | require('mdcss')({ 120 | theme: require('mdcss-theme-github') 121 | }) 122 | ``` 123 | 124 | Theme-specific options may also be passed in from the theme module itself, but note that any global options would then be ignored. 125 | 126 | ```js 127 | require('mdcss')({ 128 | theme: require('mdcss-theme-github')(/* options */) 129 | }) 130 | ``` 131 | 132 | #### `destination` 133 | 134 | Type: `String` 135 | Default: `'styleguide'` 136 | 137 | The directory to write the style guide to. 138 | 139 | #### `assets` 140 | 141 | Type: `Array` 142 | Default: `[]` 143 | 144 | The list of files or directories to copy into the style guide directory. 145 | 146 | #### `index` 147 | 148 | Type: `String` 149 | Default: `'index.html'` 150 | 151 | The file to write the style guide to. 152 | 153 | ## Writing documentation 154 | 155 | To add a section of documentation, write a CSS comment that starts with three dashes `---`. 156 | 157 | ```css 158 | /*--- 159 | 160 | This is documentation. 161 | 162 | */ 163 | ``` 164 | 165 | ```css 166 | /* 167 | 168 | This is not documentation 169 | 170 | */ 171 | ``` 172 | 173 | The contents of a section of documentation are parsed by Markdown and turned into HTML. 174 | 175 | ```css 176 | /*--- 177 | 178 | Button styles can be applied to **any** element. Typically you'll want to use 179 | either a ` 183 | Some Page 184 | ​``` 185 | 186 | */ 187 | ``` 188 | 189 | ```html 190 |

Button styles can be applied to any element. Typically you'll want to use 191 | either a <button> or an <a> element:

192 | 193 |
<button class="btn">Click</button>
194 | <a class="btn" href="/some-page">Some Page</a>
195 | 
196 | ``` 197 | 198 | The contents of a section may also be imported from another file. 199 | 200 | **buttons.md**: 201 | 202 | Button styles can be applied to **any** element. Typically you'll want to use 203 | either a ` 207 | Some Page 208 | ​``` 209 | 210 | **base.css**: 211 | 212 | ```css 213 | /*--- 214 | title: Buttons 215 | import: buttons.md 216 | ---*/ 217 | ``` 218 | 219 | The contents of a section may be automatically imported as well. For example, had the `import` been omitted, a sibling file of `base.buttons.md` or `base.md` would have been used (in that order of preference) if they existed. 220 | 221 | ### Details 222 | 223 | Additional heading details are added before a second set of three dashes `---` in a section. These heading details are parsed and added to the [`documentation` object](#documentation-object). 224 | 225 | ```css 226 | /*--- 227 | title: Buttons 228 | section: Base CSS 229 | --- 230 | 231 | Button styles can be applied to **any** element. 232 | 233 | */ 234 | ``` 235 | 236 | ```json 237 | { 238 | "title": "Buttons", 239 | "section": "Base CSS", 240 | "content": "

Button styles can be applied to any element.

" 241 | } 242 | ``` 243 | 244 | ## Writing themes 245 | 246 | Creating themes requires an understanding of [creating and publishing npm packages](https://docs.npmjs.com/misc/developers). 247 | 248 | The easiest way to create a new theme is to visit the [boilerplate theme] project page, fork and clone it, and then run `npm install`. 249 | 250 | To create a theme from scratch; create an `index.js` like this one in a new npm package directory: 251 | 252 | ```js 253 | module.exports = function (themeopts) { 254 | // initialize the theme 255 | // example usage: 256 | // 257 | // require('mdcss')({ 258 | // theme: require('mdcss-theme-mytheme')({ /* opts */ }) 259 | // }) 260 | 261 | // return the theme processor 262 | return function (docs) { 263 | // do things with the documentation object 264 | // remember to use __dirname to target this theme directory 265 | 266 | // return a promise 267 | return new Promise(function (resolve, reject) { 268 | // resolve an object with an assets path and a compiled template 269 | resolve({ 270 | assets: '', // directory of files to copy 271 | template: '' // contents of style guide to write 272 | }); 273 | }); 274 | }; 275 | }; 276 | 277 | // this is so mdcss can check whether the plugin has already been initialized 278 | module.exports.type = 'mdcss-theme'; 279 | ``` 280 | 281 | The `exports` function is where theme options are initialized. 282 | 283 | ```js 284 | require('mdcss')({ 285 | theme: require('mdcss-theme-mytheme')({ /* theme options */ }); 286 | }); 287 | ``` 288 | 289 | The `exports` function returns a theme processor. The theme processor is what receives the ordered list of all the parsed `documentation` objects as well as the [options](#options) originally passed into the [mdcss] plugin. 290 | 291 | ## Documentation object 292 | 293 | Each `documentation` object may contain the following properties: 294 | 295 | - **title**: The title of the current section of documentation. 296 | - **name**: A unique, hash-safe name of the current section of documentation. 297 | - **section**: The proper title of a parent section. 298 | - **content**: The body copy of the current section of documentation. 299 | - **parent**: The parent section. 300 | - **children**: An array of child sections. 301 | - **context**: The original [`Comment`](https://github.com/postcss/postcss/blob/master/docs/api.md#comment-node) node used to generate the current section of documentation. 302 | - **import**: A path to the file representing the content of the current section of documentation. 303 | 304 | In addition to these properties, a `documentation` object includes any additional [details](#details). 305 | 306 | ```css 307 | /*--- 308 | title: Buttons 309 | section: Base CSS 310 | yakkityyak: Don’t Talk Back 311 | --- 312 | 313 | Button styles can be applied to **any** element. 314 | 315 | */ 316 | ``` 317 | 318 | --- 319 | 320 | Have fun, and thanks for using [mdcss]. 321 | 322 | [ci]: https://travis-ci.org/jonathantneal/mdcss 323 | [ci-img]: https://img.shields.io/travis/jonathantneal/mdcss.svg 324 | [npm]: https://www.npmjs.com/package/mdcss 325 | [npm-img]: https://img.shields.io/npm/v/mdcss.svg 326 | 327 | [boilerplate theme]: https://github.com/jonathantneal/mdcss-theme 328 | 329 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss 330 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss 331 | [PostCSS]: https://github.com/postcss/postcss 332 | [mdcss]: https://github.com/jonathantneal/mdcss 333 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | fsp = require('fs-promise'), 3 | marked = require('marked'), 4 | path = require('path'); 5 | 6 | var isDoc = /\/*-{3}([\s\S]*?)-{3,}/; 7 | var isMeta = /([A-z][\w-]*)[ \t]*:[ \t]*([\w\-\.\/][^\n]*)/g; 8 | 9 | module.exports = require('postcss').plugin('mdcss', function (opts) { 10 | // set options object 11 | opts = Object(opts); 12 | 13 | /* set options */ 14 | opts.index = opts.index || 'index.html'; // index file 15 | opts.theme = opts.theme || require('mdcss-theme-github'); // theme or default 16 | opts.destination = path.join(process.cwd(), opts.destination || 'styleguide'); // destination path 17 | opts.assets = (opts.assets || []).map(function (src) { 18 | return path.join(process.cwd(), src); 19 | }); // additional assets path 20 | opts.markdownFiles = opts.markdownFiles || 'markdown'; // location of markdown import files 21 | if (typeof opts.theme !== 'function') throw Error('The theme failed to load'); // throw if theme is not a function 22 | if (opts.theme.type === 'mdcss-theme') opts.theme = opts.theme(opts); // conditionally set theme as executed theme 23 | 24 | // return plugin 25 | return function (css, result) { 26 | // set current css directory or current directory 27 | var dir = css.source.input.file ? path.dirname(css.source.input.file) : process.cwd(); 28 | 29 | // set documentation list, hash, and unique identifier 30 | var list = []; 31 | var hash = {}; 32 | var uniq = 0; 33 | 34 | // walk comments 35 | css.walkComments(function (comment) { 36 | // if comment is documentation 37 | if (isDoc.test(comment.text)) { 38 | // set documentation object 39 | var doc = {}; 40 | 41 | // filter documentation meta 42 | doc.content = comment.text.replace(isDoc, function (isDoc0, metas) { 43 | // push meta to documentation 44 | if (metas) metas.replace(isMeta, function (isMeta0, name, value) { 45 | 46 | doc[name] = value.trim(); 47 | }); 48 | 49 | // remove meta from documentation content 50 | return ''; 51 | }).trim(); 52 | 53 | // conditionally set the closest documentation name 54 | if (doc.title && !doc.name) { 55 | doc.name = doc.title; 56 | } 57 | // else if (doc.section && !doc.name) { doc.name = doc.section; } 58 | 59 | // conditionally import external content 60 | if (!doc.content) { 61 | // get comment source path 62 | var src = comment.source.input.file; 63 | 64 | // if the comment source path exists 65 | if (src) { 66 | // get the closest matching directory for this comment 67 | var localdir = src ? path.dirname(src) : dir, 68 | mdbase = doc.import, 69 | mdspec; 70 | 71 | // if there's no import specified, look for a md file with the title name inside the section folder 72 | if (!mdbase) { 73 | var mdFiles = opts.markdownFiles, 74 | mdSection = doc.section.replace(' ', '-').toLowerCase(), 75 | mdName = doc.title.replace(' ', '-').toLowerCase(); 76 | mdbase = mdFiles + '\/' + mdSection + '\/' + mdName + '.md'; 77 | // mdbase = mdspec = path.basename(src, path.extname(src)); 78 | 79 | if (doc.name) { 80 | mdspec += '.' + doc.name; 81 | } 82 | 83 | // mdbase += '.md'; 84 | // mdspec += '.md'; 85 | } 86 | 87 | // try to read the closest matching documentation 88 | try { 89 | if (mdspec) { 90 | doc.content = marked(fs.readFileSync(path.join(localdir, mdspec), 'utf8')); 91 | } else throw new Error(); 92 | } catch (error1) { 93 | try { 94 | doc.content = marked(fs.readFileSync(path.join(localdir, mdbase), 'utf8')); 95 | } catch (error2) { 96 | doc.content = ''; 97 | 98 | comment.warn(result, 'Documentation import "' + mdbase + '" could not be read.'); 99 | } 100 | } 101 | 102 | } 103 | } 104 | 105 | doc.content = marked(doc.content, opts.marked); 106 | 107 | // set documentation context 108 | doc.context = comment; 109 | 110 | // insure documentation has unique name 111 | // console.log(doc.name); 112 | var name = doc.name || 'section' + ++uniq; 113 | var uniqname = name; 114 | 115 | while (uniqname in hash) uniqname = name + --uniq; 116 | 117 | // push documentation to hash 118 | hash[uniqname] = doc; 119 | } 120 | }); 121 | 122 | // console.log(Object.keys(hash)); 123 | 124 | // walk hashes 125 | Object.keys(hash).forEach(function (name) { 126 | // set documentation 127 | var doc = hash[name]; 128 | 129 | // if documentation has a parent section 130 | if ('section' in doc) { 131 | // get parent section 132 | var title = doc.section; 133 | var sname = titleToName(title); 134 | var parent = hash[sname]; 135 | 136 | // if parent section does not exist 137 | if (!parent) { 138 | // create parent section 139 | parent = hash[sname] = { 140 | title: title, 141 | name: sname 142 | }; 143 | 144 | // add parent section to list 145 | list.push(parent); 146 | } 147 | 148 | if (!parent.children) parent.children = []; 149 | 150 | // make documentation a child of the parent section 151 | parent.children.push(doc); 152 | 153 | doc.parent = parent; 154 | } 155 | // otherwise make documentation a child of list 156 | else list.push(doc); 157 | }); 158 | 159 | // return theme executed with parsed list, destination 160 | return opts.theme({ 161 | list: list, 162 | opts: opts 163 | }).then(function (docs) { 164 | // empty the destination directory 165 | return fsp.emptyDir(opts.destination) 166 | // then copy the theme assets into the destination 167 | .then(function () { 168 | return fsp.copy(docs.assets, opts.destination); 169 | }) 170 | // then copy the compiled template into the destination 171 | .then(function () { 172 | return fsp.outputFile(path.join(opts.destination, opts.index), docs.template); 173 | }) 174 | // then copy any of the additional assets into the destination 175 | .then(function () { 176 | return Promise.all(opts.assets.map(function (src) { 177 | return fsp.copy(src, path.join(opts.destination, path.basename(src))); 178 | })); 179 | }); 180 | }); 181 | }; 182 | }); 183 | 184 | function titleToName(title) { 185 | return title.replace(/\s+/g, '-').replace(/[^A-z0-9_-]/g, '').toLowerCase(); 186 | } 187 | -------------------------------------------------------------------------------- /lib/fs-promise.js: -------------------------------------------------------------------------------- 1 | var slice = Array.prototype.slice; 2 | var fs = require('fs-extra'); 3 | 4 | [ 5 | 'access', 6 | 'readFile', 7 | 'close', 8 | 'open', 9 | 'read', 10 | 'write', 11 | 'rename', 12 | 'truncate', 13 | 'ftruncate', 14 | 'rmdir', 15 | 'fdatasync', 16 | 'fsync', 17 | 'mkdir', 18 | 'readdir', 19 | 'fstat', 20 | 'lstat', 21 | 'stat', 22 | 'readlink', 23 | 'symlink', 24 | 'link', 25 | 'unlink', 26 | 'fchmod', 27 | 'lchmod', 28 | 'chmod', 29 | 'lchown', 30 | 'fchown', 31 | 'chown', 32 | '_toUnixTimestamp', 33 | 'utimes', 34 | 'futimes', 35 | 'writeFile', 36 | 'appendFile', 37 | 'realpath', 38 | 'lutimes', 39 | 'gracefulify', 40 | 'copy', 41 | 'mkdirs', 42 | 'mkdirp', 43 | 'ensureDir', 44 | 'remove', 45 | 'readJson', 46 | 'readJSON', 47 | 'writeJson', 48 | 'writeJSON', 49 | 'outputJson', 50 | 'outputJSON', 51 | 'move', 52 | 'createOutputStream', 53 | 'emptyDir', 54 | 'emptydir', 55 | 'createFile', 56 | 'ensureFile', 57 | 'createLink', 58 | 'ensureLink', 59 | 'createSymlink', 60 | 'ensureSymlink', 61 | 'outputFile', 62 | 'walk' 63 | ].forEach(function (key) { 64 | module.exports[key] = function () { 65 | var args = slice.call(arguments); 66 | 67 | return new Promise(function (resolve, reject) { 68 | args.push(function(error, response) { 69 | if (error) { 70 | console.log(['error', error]); 71 | reject(error); 72 | } else { 73 | resolve(response); 74 | } 75 | }); 76 | 77 | fs[key].apply(fs, args); 78 | }); 79 | }; 80 | }); 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdcss", 3 | "version": "1.5.3", 4 | "description": "Easily create and maintain style guides with CSS comments using Markdown", 5 | "keywords": [ 6 | "postcss", 7 | "css", 8 | "postcss-plugin", 9 | "styles", 10 | "styleguides", 11 | "documentation", 12 | "docs", 13 | "notes", 14 | "texts", 15 | "markdown", 16 | "mds", 17 | "examples", 18 | "colors", 19 | "markups", 20 | "sections" 21 | ], 22 | "author": "Jonathan Neal ", 23 | "license": "CC0-1.0", 24 | "repository": "jonathantneal/mdcss", 25 | "homepage": "https://github.com/jonathantneal/mdcss", 26 | "bugs": "https://github.com/jonathantneal/mdcss/issues", 27 | "dependencies": { 28 | "fs-extra": "^0.26.5", 29 | "marked": "^0.3.5", 30 | "mdcss-theme-github": "^2.1.0", 31 | "postcss": "^5.0.14" 32 | }, 33 | "devDependencies": { 34 | "eslint": "^1.10.3", 35 | "fs-promise": "^1.0.0", 36 | "tap-spec": "^4.1.1", 37 | "tape": "^4.4.0" 38 | }, 39 | "scripts": { 40 | "lint": "eslint . --ignore-path .gitignore", 41 | "tape": "tape test.js | tap-spec", 42 | "test": "npm run lint && npm run tape" 43 | }, 44 | "engines": { 45 | "iojs": ">=2.0.0", 46 | "node": ">=0.12.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var tests = { 2 | 'mdcss': { 3 | 'basic': { 4 | message: 'supports basic usage', 5 | options: { 6 | theme: require('mdcss-theme-github'), 7 | destination: 'test/styleguide', 8 | title: 'Test Style Guide', 9 | examples: { 10 | css: [ 11 | '../basic.css' 12 | ] 13 | }, 14 | assets: [ 15 | 'test/humans.txt', 16 | 'test/extras' 17 | ] 18 | }, 19 | warning: 1 20 | } 21 | } 22 | }; 23 | 24 | var debug = true; 25 | var dir = './test/'; 26 | 27 | var fs = require('fs'); 28 | var path = require('path'); 29 | var plugin = require('./'); 30 | var test = require('tape'); 31 | 32 | Object.keys(tests).forEach(function (name) { 33 | var parts = tests[name]; 34 | 35 | test(name, function (t) { 36 | var fixtures = Object.keys(parts); 37 | 38 | t.plan(fixtures.length * 2); 39 | 40 | fixtures.forEach(function (fixture) { 41 | var message = parts[fixture].message; 42 | var options = parts[fixture].options || {}; 43 | var warning = parts[fixture].warning || 0; 44 | var warningMsg = message + ' (# of warnings)'; 45 | 46 | var baseName = fixture.split(':')[0]; 47 | var testName = fixture.split(':').join('.'); 48 | 49 | var inputPath = path.resolve(dir + baseName + '.css'); 50 | var expectPath = path.resolve(dir + testName + '.expect.css'); 51 | var actualPath = path.resolve(dir + testName + '.actual.css'); 52 | 53 | var inputCSS = ''; 54 | var expectCSS = ''; 55 | 56 | try { 57 | inputCSS = fs.readFileSync(inputPath, 'utf8'); 58 | } catch (error) { 59 | fs.writeFileSync(inputPath, inputCSS); 60 | } 61 | 62 | try { 63 | expectCSS = fs.readFileSync(expectPath, 'utf8'); 64 | } catch (error) { 65 | fs.writeFileSync(expectPath, expectCSS); 66 | } 67 | 68 | options.from = inputPath; 69 | options.to = actualPath; 70 | 71 | plugin.process(inputCSS, options).then(function (result) { 72 | var actualCSS = result.css; 73 | 74 | if (debug) fs.writeFileSync(actualPath, actualCSS); 75 | 76 | t.equal(actualCSS, expectCSS, message); 77 | 78 | t.equal(result.warnings().length, warning, warningMsg); 79 | }); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/basic-import.md: -------------------------------------------------------------------------------- 1 | You can also import content with the `import` header. 2 | 3 | ```css 4 | /*--- 5 | Title: Imported Content 6 | import: basic-import.md 7 | ---*/ 8 | ``` 9 | -------------------------------------------------------------------------------- /test/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is not documentation 4 | 5 | */ 6 | 7 | 8 | /*--- 9 | 10 | # Test Style Guide 11 | 12 | This is an example of a style guide written in [mdcss](https://github.com/jonathantneal/mdcss). 13 | 14 | */ 15 | 16 | /*--- 17 | title: Base CSS 18 | ---*/ 19 | 20 | :root { 21 | background-color: #fff; 22 | color: #000; 23 | } 24 | 25 | /*--- 26 | title: Buttons 27 | section: Base CSS 28 | --- 29 | 30 | Button styles can be applied to any element. Typically you'll want to use 31 | either a ` 35 | ``` 36 | */ 37 | 38 | .btn { 39 | background-color: black; 40 | color: white; 41 | } 42 | 43 | /*--- 44 | title: Links 45 | section: Base CSS 46 | --- 47 | 48 | Link styles can only be applied to `` elements. 49 | 50 | ```example:html 51 | Some page 52 | ``` 53 | 54 | */ 55 | 56 | a { 57 | color: red; 58 | text-decoration: none; 59 | } 60 | 61 | 62 | /*--- 63 | title: Captions 64 | --- 65 | 66 | Captions do their own things. 67 | 68 | ```example:html 69 |
70 | 71 |
This is a figure.
72 |
73 | ``` 74 | 75 | */ 76 | 77 | figure { 78 | display: block; 79 | } 80 | 81 | figcaption { 82 | font-style: italic; 83 | } 84 | 85 | /*--- 86 | title: Imported Content 87 | import: basic-import.md 88 | ---*/ 89 | 90 | /*--- 91 | 92 | Thanks for reading. 93 | 94 | */ 95 | -------------------------------------------------------------------------------- /test/basic.expect.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is not documentation 4 | 5 | */ 6 | 7 | 8 | /*--- 9 | 10 | # Test Style Guide 11 | 12 | This is an example of a style guide written in [mdcss](https://github.com/jonathantneal/mdcss). 13 | 14 | */ 15 | 16 | /*--- 17 | title: Base CSS 18 | ---*/ 19 | 20 | :root { 21 | background-color: #fff; 22 | color: #000; 23 | } 24 | 25 | /*--- 26 | title: Buttons 27 | section: Base CSS 28 | --- 29 | 30 | Button styles can be applied to any element. Typically you'll want to use 31 | either a ` 35 | ``` 36 | */ 37 | 38 | .btn { 39 | background-color: black; 40 | color: white; 41 | } 42 | 43 | /*--- 44 | title: Links 45 | section: Base CSS 46 | --- 47 | 48 | Link styles can only be applied to `` elements. 49 | 50 | ```example:html 51 | Some page 52 | ``` 53 | 54 | */ 55 | 56 | a { 57 | color: red; 58 | text-decoration: none; 59 | } 60 | 61 | 62 | /*--- 63 | title: Captions 64 | --- 65 | 66 | Captions do their own things. 67 | 68 | ```example:html 69 |
70 | 71 |
This is a figure.
72 |
73 | ``` 74 | 75 | */ 76 | 77 | figure { 78 | display: block; 79 | } 80 | 81 | figcaption { 82 | font-style: italic; 83 | } 84 | 85 | /*--- 86 | title: Imported Content 87 | import: basic-import.md 88 | ---*/ 89 | 90 | /*--- 91 | 92 | Thanks for reading. 93 | 94 | */ 95 | -------------------------------------------------------------------------------- /test/extras/README.md: -------------------------------------------------------------------------------- 1 | This directory is for for testing purposes. 2 | -------------------------------------------------------------------------------- /test/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | Jonathan Neal -- Explorer -- @jon_neal 7 | --------------------------------------------------------------------------------