diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..8951c3929 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +tab_width = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..d52103feb --- /dev/null +++ b/.eslintrc @@ -0,0 +1,83 @@ +{ + "env": { + "node": true, + "builtin": true + }, + "globals": {}, + "rules": { + "block-scoped-var": 0, + "camelcase": 0, + "comma-spacing": [1, {"before": false, "after": true}], + "consistent-return": 2, + "curly": [2, "all"], + "dot-notation": [1, { "allowKeywords": true }], + "eqeqeq": [2, "allow-null"], + "global-strict": [0, "never"], + "guard-for-in": 2, + "indent": [2, 2, {"SwitchCase": 1, "VariableDeclarator": 1}], + "lines-around-comment": [2, { + "beforeBlockComment": true, + "beforeLineComment": true, + "allowBlockStart": true, + "allowObjectStart": true, + "allowArrayStart": true + }], + "key-spacing": 0, + "keyword-spacing": 1, + "new-cap": 0, + "no-alert": 2, + "no-bitwise": 2, + "no-caller": 2, + "no-cond-assign": [2, "except-parens"], + "no-debugger": 2, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-empty": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-parens": 0, + "no-extra-semi": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 1, + "no-iterator": 2, + "no-loop-func": 2, + "no-mixed-requires": 0, + "no-multi-str": 2, + "no-multi-spaces": 1, + "no-native-reassign": 2, + "no-new": 2, + "no-param-reassign": 1, + "no-proto": 2, + "no-redeclare": 0, + "no-script-url": 2, + "no-self-assign": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": 2, + "no-undef": 2, + "no-underscore-dangle": 0, + "no-unreachable": 1, + "no-unused-vars": 1, + "no-use-before-define": 1, + "no-useless-call": 2, + "no-useless-concat": 2, + "no-with": 2, + "quotes": [0, "single"], + "radix": 2, + "semi": [0, "never"], + "strict": 0, + "space-before-blocks": 1, + "space-before-function-paren": [1, { + "anonymous": "always", + "named": "never" + }], + "space-in-parens": [1, "never"], + "space-infix-ops": 1, + "valid-typeof": 2, + "vars-on-top": 0, + "wrap-iife": [2, "inside"] + } +} diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 7edd1a3d0..8598cddb2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,5 +1,5 @@ # Contributing to Patternlab Node -If you'd like to contribute to Pattern Lab Node, please do so! There is always a lot of ground to cover and something for your wheelhouse. +If you'd like to contribute to Pattern Lab Node, please do so! There is always a lot of ground to cover and something for your wheelhouse. No pull request is too small. Check out any [up for grabs issues](https://github.com/pattern-lab/patternlab-node/labels/up%20for%20grabs) as a good way to get your feet wet, or add some more unit tests. @@ -9,4 +9,7 @@ No pull request is too small. Check out any [up for grabs issues](https://github 3. If you can, add some unit tests using the existing patterns in the `./test` directory ##Coding style -Regarding code style like indentation and whitespace, follow the conventions you see used in the source already. Add enough source code comments to help the next person. +Two files combine within the project to define and maintain our coding style. + +* The `.editorconfig` controls spaces / tabs within supported editors. Check out their [site](http://editorconfig.org/). +* The `.eslintrc` defines our javascript standards. Some editors will evaluate this real-time - otherwise it's run using `grunt|gulp build` diff --git a/.gitignore b/.gitignore index f9d11769c..74ab03195 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,10 @@ node_modules/ .DS_Store -public/index.html -public/styleguide.html -public/styleguide/html/styleguide.html -public/css/* -public/data/* -public/fonts/* -public/js/* -public/images/* -public/patterns/* latest-change.txt patternlab.json .sass-cache/* /sass-cache -source/images/Thumbs.db -public/styleguide/css/static.css.map -public/styleguide/css/styleguide-specific.css.map -public/styleguide/css/styleguide.css.map +Thumbs.db source/css/style.css.map .idea/ -public/styleguide/ +public diff --git a/.jslintrc b/.jslintrc deleted file mode 100644 index 8ac6b2e37..000000000 --- a/.jslintrc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "ass": false, - "bitwise": false, - "browser": false, - "closure": false, - "continue": false, - "couch": false, - "debug": false, - "devel": true, - "eqeq": false, - "eval": true, - "evil": false, - "forin": false, - "indent": 2, - "maxerr": 50, - "maxlen": false, - "newcap": false, - "node": true, - "nomen": false, - "passfail": false, - "plusplus": false, - "predef": [ - "node", - "$", - "require" - ], - "regexp": false, - "rhino": false, - "sloppy": false, - "stupid": false, - "sub": false, - "todo": false, - "unparam": true, - "vars": false, - "white": true -} diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 8ad1f0731..000000000 --- a/CHANGELOG +++ /dev/null @@ -1,232 +0,0 @@ -THIS CHANGELOG IS AN ATTEMPT TO DOCUMENT CHANGES TO THIS PROJECT. - -PL-node-v1.1.3 -- FIX: Added pattern type view all pages to the navigation, which users view all 'Atoms/Molecules/Organisms' etc. -- THX: Thanks to @gael-boyenval for originally pointing out the omission. - -PL-node-v1.1.2 -- FIX: Greatly improved the browsersync configuration, so that it only fires on the iframe, preventing users from losing their scroll position. Also lightened up the styling and made it less obtrusive. -- THX: Thanks to @geoffp for taking the lead on this issue. -- THX: This release also marks the first with @geoff's more official involvement with the project as a core contributor. His work on the `pattern_engine` branch and configurable paths have and will continue to make Pattern Lab Node better. -- FIX: Replace `eval()` with a smarter `JSON.parse()` call within the `parameter_hunter.js` -- THX: Thanks to @e2tha-e for taking the high road! - -PL-node-v1.1.1 -- FIX: Fixed issue where alternate patterns are added to end of styleguide instead of inline with their parent pattern. - -PL-node-v1.1.0 -- FIX: Fixed issue where partials containing styleModifiers with integers were not found correctly under all circumstances -- FIX: Fixed issue where excluded patterns were still rendered on the Pattern Lab site. Now they do not directly get rendered via the menu, view all links, or the styleguide, but are accessible for inclusion as pattern partials, and can be accessed via lineage. -- THX: Thanks @theorise for reporting these issues. -- THX: Thanks @dmolsen for input on desired behavior. -- FIX: Fixed issue where style modifier partials within list item blocks where not uniquely being applied. this seems like a regression. added a unit test with fix -- ADD: Added fuzzy pattern matching support based on patternType-substring(patternName) to align with PL PHP -- FIX: Fixed issue with gulpfile not copying style.css and watching the wrong directory -- THX: Thanks @robinsonaaron for the issue and pull request! -- FIX: Prefer exact pattern key match over fuzzy matches inside getpatternbykey() -- THX: Thanks @EvanLovely for the suggestion -- ADD: Make all paths configurable -- THX: HUGE Thanks to @geoffp and @EvanLovely for their thoughts, time, and talent to make this a reality! -- FIX: Fix issue where absolute paths in the config path object would not resolve -- THX: Thanks to @geoffp and @EvanLovely for reporting, fixing and testing the issue in the dev branch. -- FIX: Typo in gulp instructions in README. -- THX: Thanks @simonknittel for the watchful eyes -- CHG: Changed locations of ./public/styleguide to ./core/styleguide to make ./public/ a cleaner distribution directory -- CHG: Removed scss files and config from project. This is in preparation for including the default asset repo in the future -- FIX: Fix issue where partials were not being sent to Mustache during pattern parameter parsing. -- THX: Thanks to @e2tha-e for reporting this issue. -- ADD: Now patterns and pseudopatterns can be linked from global or file data.json -- THX: Thanks @kylewelsby for the thoughtful enhancement - -PL-node-v1.0.0 -- FIX: Resolve issue with not hiding underscored patterns. -- THX: Thanks @ivancamilov for reporting this regression. -- FIX: Fix misapplied error input class -- THX: Thanks @johngerome for the pull request! -- ADD: Added a note in the README during installation to run with elevated privileges -- THX: Thanks @RichardBray for the heads up -- ADD: Added a Prerequisites section to the README -- ADD: Added unit tests for pattern states and pseudopatterns -- CHG: Changed pseudopattern generation to use config.patterns.source directory instead of hardcode -- CHG: Explicitly sorting patterns by name prior to building the UI -- ADD: Added ability to specify link.* urls inside data objects -- CHG: Incremented version to 1.0.0. Achieved near-parity with PL PHP 1! -- THX: Thanks to each and every person who cared about Pattern Lab Node! - Brian - -PL-node-v0.15.1 -- FIX: Resolve issue with styleModifiers not being replaced when the partial has spaces in it. -- ADD: Support multiple styleModifier classes using the | pipe syntax -- FIX: Resolve issue with styleModifiers not being applied correctly when mixed with pattern parameters -- THX: Thanks @theorise for the issue reports! - -PL-node-v0.15.0 -- CHG: Updated package.json devDependencies for Node 4.X and 5.X support. -- CHG: Updated package.gulp.json devDependencies for Node 4.X and 5.X support. - -PL-node-v0.14.0 -- ADD: Support for style modifiers -- ADD: Support for styleGuideExcludes -- THX: Thanks to @bramsmulders for the styleGuideExcludes pull request and @illepic for the original headsup -- FIX: Fix an issue where listitem blocks would only render if the pattern containing the block had a partial within it - -PL-node-v0.13.1 -- FIX: Allow verbose partials for list items -- THX: Thanks @e2tha-e - -PL-node-v0.13.0 -- FIX: Cleanup an old file and an incorrect entry in the .gitignore file -- CHG: Change order of pattern addition and ~variant pattern addition so they build naturally in the menu. -- THX: Thanks @e2tha-e for the flurry of pull requests! -- CHG: Update data merge function to prioritize handle pattern~variant.json files -- THX: Thanks @e2tha-e for finding, fixing, and unit testing the data merge issue. -- ADD: Support for recursive partial inclusion -- THX: Thanks @e2tha-e for making pattern inclusion a lot more robust. Great work!!! -- FIX: Improvements to style guide menu generation and capitalization. - -PL-node-v0.12.0 - - ADD: Gulp support arrives with an optional configuration - - ADD: Instructions how to install and run with Gulp - - DEL: Deleted the sass modules from `package.json` files - - CHG: Commented out all sass tasks in Grunt and Gulp files - - CHG: Changed static web server provider to BrowserSync - - CHG: Replaced the Style Guide toolbar Auto-Reload and Page Follow with a link to the BrowserSync UI - - THX: Thanks @oscar-g for the initial work on the gulpfile! - - ADD: An alternative, more verbose syntax for pattern partial inclusion - - THX: Thanks @e2tha-e for the thoughtful addition with unit test coverage - -PL-node-v0.11.0 - - ADD: Ignore pattern directories that start with an underscore. - - ADD: Support for lists with the listItems variable - - FIX: Resolved issue where pattern parameter data bled into global data object - - ADD: Support a fluid viewport - -PL-node-v0.10.1 - - ADD: Added more unit tests for recently more-modular code - - FIX: Lineage was not working for patterns with pattern parameters - -PL-node-v0.10.0 - - ADD: Added support for pattern parameters! Resolves #88 - - FIX: Data inheritance is now working as advertised. Resolves #127. This turned out to be a MAJOR thing, as I realized the home-page was not passing down any of its json data to partials. - - CHG: Refactored a lot of code out of patternlab.js and into separate files. Doing so should make everything dryer, more unit testable, and easier to understand I hope. - - ADD: Added proper styling for the homepage-emergency alert that is displayed for demo purposes - - ADD: Added a new comment organism, the sticky comment, to ship an example of pattern parameters - - CHG: Start some JS linting of the project. I don't quite agree with a lot of it, so am trying to set some smart configurations - - CHG: Wrapped some build messages in the patternlab.config.debug flag - - FIX: Allow users to set a base url path. Resolves #125 (testing in the wild requested) - - THX: Thanks @scottnath for the proposed base url solution and @jkbyln for discussion on the topic too! - -PL-node-v0.9.1 - - FIX: Fixed an issue with view all navigation not checking for index out of bounds cases - -PL-node-v0.9.0 - - FIX: Added grunt-contrib-copy args to copy all found source/css/*.css - - ADD: Added upgrade instructions to README - - FIX: Fix issue with styleguide accordions not closing upon click of a sibling menu. - - THX: @getsetbro for reporting more issues :) - - ADD: Added support for pattern search. This is 'in beta' and should have more testing applied to it. - - ADD: Added support for all keyboard shortcuts found in PL-PHP-v1.0.0 - - FIX: Fixed an issue where Hay mode and Disco mode did not stop one another when using keyboard shortcuts - - FIX: Fix location of paragraph closing tag to wrap citation - - THX: @laurendorman for the pull request! - - FIX: Removed some dead code. - - ADD: Added View All links in each menu subsection - - THX: BIG THANKS TO @walmokrani for this work! - -PL-node-v0.8.1 - - FIX: v8 is not pulling in values from global data.json - - FIX: MQ list breaks if media queries don't have spaces - - THX: @getsetbro for reporting these issues :D - - THX: @dmolsen for a cheat codes - -PL-node-v0.8.0 - - CHG: note the change in versioning to proper semver. wanted to do ths to inch closer to a 1.0.0 release - - DEL: deleted most of the lingering PHP sync listener code - - FIX: support for displaying the HTML and Mustache in the code viewer - - ADD: pattern link support - - CHG: updated included mustache templates to reflect pattern links in navigation and compiling pages direct from templates - - THX: @getsetbro for finding and fixing a typo - - FIX: fixed a bug preventing pattern states from displaying on the flat template/pages - - ADD: support for basic pseudo-patterns - - CHG: cleaned up patternlab.js a bit for future testing, a bit more DRYness. - -PL-node-v0.1.7 - - ADD: pattern export - - CHG: updated devDependencies - - FIX: fixed a type in the README and config - - THX: thanks @seanhussey for the pull request! - -PL-node-v0.1.6 - - ADD: media queries found in css added to ish controls - - ADD: basic lineage support - - ADD: more unit tests - -PL-node-v0.1.5 - - ADD: Live reload support for grunt serve task - - THX: thanks @marcinmodestowicz for the configuration fix. VERY useful :) - -PL-node-v0.1.4 - - FIX: Resolved issue with nested pattern rendering - - FIX: Supporting flat pattern name regex's in unix-systems - - THX: thanks @torbs for the issue and pull request - -PL-node-v0.1.3 - - ADD: Pattern states - - ADD: Ships with grunt connect if you are into that kinda thing - - FIX: Removed all grunt dependencies from patternlab.js - - FIX: Ignore dotfiles - - THX: thanks @bramsmulders for suggestion and help with mac - -PL-node-v0.1.2 - - ADD: Abstracted template rendering into a function for easier swapping of rendering engine - - ADD: Smarter filtering of files to support other templates Thanks - - ADD: Help command line agument - - ADD: Version command line argument - - ADD: Patterns only command line argument - - ADD: IshControlsVisible options. Can now hide any ishControls you like. - - ADD: Documented the command line interface - - CHG: Put debug flag in conf.json instead of package.json - - CHG: Aligned styleguide css with patternlab-php - - FIX: Removed node .8 from travis - - FIX: Code and annotation support in patternlab viewer - - THX: thanks @ivanmayes and Shoptology crew for contibutions! - -PL-node-v0.1.1 - - FIX: Removed View All Pattern SubItem Link Logic, no longer in reference implementation - - ADD: Flag for generating debug file - - ADD: Travis CI test support! - - ADD: Contributing file - - ADD: Repository to package.json - - FIX: Manage Mustache dependency using NPM - - THX: thank you @tbranyen for tip on repository, Mustache, and NPM! - -PL-node-v0.1.0 - - FIX: Links to patterns did not work when visited from a server - - FIX: Patterns with hyphens in the name were breaking the iframe messaging - - FIX: Added stlyeguide/js files that were ignored at one point - - FIX: Watch _data/*.json files too - - FIX: Copy images, in an attempt to exclude files like Thumbs.db - - FIX: Typos in CHANGELOG - - ADD: A banner to patternlab.js - -PL-node-v0.0.5 - - FIX: typo in organisms global header - - FIX: flat-structured pattern items rendered as if they had sub-menus #4 - - ADD: Load all grunt tasks using matchdep - - THX: thanks to @colynb for the typo heads up - - THX: shoutout to @joemcgill for better dependency loading - -PL-node-v0.0.4 - - ADD: An explicit MIT license - - FIX: Clean public/patterns/ before build - -PL-node-v0.0.3 - - FIX: Install documentation was incomplete, should not have assumed grunt - - FIX: Remove SASS/SCSS dependency which was causing clean installs from failing - -PL-node-v0.0.2 - - FIX: Sub Nav Items now strip hyphens and are styled like patternlab-php. - - FIX: Exclude patterns by using an underscore - - FIX: Grunt watching styleguide scss - -PL-node-v0.0.1 - - Minimum Viable Product! At this point, I feel you could use Pattern Lab Node to build an atomic design-driven website. diff --git a/Gruntfile.js b/Gruntfile.js index 06c6cf6d2..2fbe7d31f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,161 +1,168 @@ -module.exports = function(grunt) { +module.exports = function (grunt) { - var path = require('path'); + var path = require('path'); - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - concat: { - options: { - stripBanners: true, - banner: '/* \n * <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy") %> \n * \n * <%= pkg.author %>, and the web community.\n * Licensed under the <%= pkg.license %> license. \n * \n * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. \n *\n */\n\n', - }, - patternlab: { - src: './builder/patternlab.js', - dest: './builder/patternlab.js' - }, - object_factory: { - src: './builder/object_factory.js', - dest: './builder/object_factory.js' - }, - lineage: { - src: './builder/lineage_hunter.js', - dest: './builder/lineage_hunter.js' - }, - media_hunter: { - src: './builder/media_hunter.js', - dest: './builder/media_hunter.js' - }, - patternlab_grunt: { - src: './builder/patternlab_grunt.js', - dest: './builder/patternlab_grunt.js' - }, - patternlab_gulp: { - src: './builder/patternlab_gulp.js', - dest: './builder/patternlab_gulp.js' - }, - parameter_hunter: { - src: './builder/parameter_hunter.js', - dest: './builder/parameter_hunter.js' - }, - pattern_exporter: { - src: './builder/pattern_exporter.js', - dest: './builder/pattern_exporter.js' - }, - pattern_assembler: { - src: './builder/pattern_assembler.js', - dest: './builder/pattern_assembler.js' - }, - pseudopattern_hunter: { - src: './builder/pseudopattern_hunter.js', - dest: './builder/pseudopattern_hunter.js' - }, - list_item_hunter: { - src: './builder/list_item_hunter.js', - dest: './builder/list_item_hunter.js' - }, - style_modifier_hunter: { - src: './builder/style_modifier_hunter.js', - dest: './builder/style_modifier_hunter.js' - } - }, - copy: { - main: { - files: [ - { expand: true, cwd: path.resolve(paths().source.js), src: '*.js', dest: path.resolve(paths().public.js) }, - { expand: true, cwd: path.resolve(paths().source.css), src: '*.css', dest: path.resolve(paths().public.css) }, - { expand: true, cwd: path.resolve(paths().source.images), src: ['**/*.png', '**/*.jpg', '**/*.gif', '**/*.jpeg'], dest: path.resolve(paths().public.images) }, - { expand: true, cwd: path.resolve(paths().source.fonts), src: '*', dest: path.resolve(paths().public.fonts) }, - { expand: true, cwd: path.resolve(paths().source.data), src: 'annotations.js', dest: path.resolve(paths().public.data) } - ] - }, - styleguide: { - files: [ - { expand: true, cwd: path.resolve(paths().source.styleguide), src: ['*.*', '**/*.*'], dest: path.resolve(paths().public.styleguide) } - ] - } - }, - watch: { - all: { - files: [ - path.resolve(paths().source.css + '**/*.css'), - path.resolve(paths().source.styleguide + 'css/*.css'), - path.resolve(paths().source.patterns + '**/*.mustache'), - path.resolve(paths().source.patterns + '**/*.json'), - path.resolve(paths().source.fonts + '/*'), - path.resolve(paths().source.images + '/*'), - path.resolve(paths().source.data + '*.json') - ], - tasks: ['default', 'bsReload:css'] - } - }, - nodeunit: { - all: ['test/*_tests.js'] - }, - browserSync: { - dev: { - options: { - server: path.resolve(paths().public.root), - watchTask: true, - watchOptions: { - ignoreInitial: true, - ignored: '*.html' - }, - snippetOptions: { - // Ignore all HTML files within the templates folder - blacklist: ['/index.html', '/'] - }, - plugins: [ - { - module: 'bs-html-injector', - options: { - files: [path.resolve(paths().public.root + '/index.html'), path.resolve(paths().public.styleguide + '/styleguide.html')] - } - } - ], - notify: { - styles: [ - 'display: none', - 'padding: 15px', - 'font-family: sans-serif', - 'position: fixed', - 'font-size: 1em', - 'z-index: 9999', - 'bottom: 0px', - 'right: 0px', - 'border-top-left-radius: 5px', - 'background-color: #1B2032', - 'opacity: 0.4', - 'margin: 0', - 'color: white', - 'text-align: center' - ] - } - } - } - }, - bsReload: { - css: path.resolve(paths().public.root + '**/*.css') - } - }); + function paths() { + return require('./patternlab-config.json').paths; + } - function paths () { - return require('./config.json').paths; - } + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + concat: { + options: { + stripBanners: true, + banner: '/* \n * <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy") %> \n * \n * <%= pkg.author %>, and the web community.\n * Licensed under the <%= pkg.license %> license. \n * \n * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. \n *\n */\n\n', + }, + patternlab: { + src: './core/lib/patternlab.js', + dest: './core/lib/patternlab.js' + }, + object_factory: { + src: './core/lib/object_factory.js', + dest: './core/lib/object_factory.js' + }, + lineage: { + src: './core/lib/lineage_hunter.js', + dest: './core/lib/lineage_hunter.js' + }, + media_hunter: { + src: './core/lib/media_hunter.js', + dest: './core/lib/media_hunter.js' + }, + patternlab_grunt: { + src: './core/lib/patternlab_grunt.js', + dest: './core/lib/patternlab_grunt.js' + }, + patternlab_gulp: { + src: './core/lib/patternlab_gulp.js', + dest: './core/lib/patternlab_gulp.js' + }, + parameter_hunter: { + src: './core/lib/parameter_hunter.js', + dest: './core/lib/parameter_hunter.js' + }, + pattern_exporter: { + src: './core/lib/pattern_exporter.js', + dest: './core/lib/pattern_exporter.js' + }, + pattern_assembler: { + src: './core/lib/pattern_assembler.js', + dest: './core/lib/pattern_assembler.js' + }, + pseudopattern_hunter: { + src: './core/lib/pseudopattern_hunter.js', + dest: './core/lib/pseudopattern_hunter.js' + }, + list_item_hunter: { + src: './core/lib/list_item_hunter.js', + dest: './core/lib/list_item_hunter.js' + }, + style_modifier_hunter: { + src: './core/lib/style_modifier_hunter.js', + dest: './core/lib/style_modifier_hunter.js' + } + }, + copy: { + main: { + files: [ + { expand: true, cwd: path.resolve(paths().source.js), src: '*.js', dest: path.resolve(paths().public.js) }, + { expand: true, cwd: path.resolve(paths().source.css), src: '*.css', dest: path.resolve(paths().public.css) }, + { expand: true, cwd: path.resolve(paths().source.images), src: ['**/*.png', '**/*.jpg', '**/*.gif', '**/*.jpeg'], dest: path.resolve(paths().public.images) }, + { expand: true, cwd: path.resolve(paths().source.fonts), src: '*', dest: path.resolve(paths().public.fonts) }, + { expand: true, cwd: path.resolve(paths().source.data), src: 'annotations.js', dest: path.resolve(paths().public.data) } + ] + }, + styleguide: { + files: [ + { expand: true, cwd: path.resolve(paths().source.styleguide), src: ['*.*', '**/*.*'], dest: path.resolve(paths().public.styleguide) } + ] + } + }, + watch: { + all: { + files: [ + path.resolve(paths().source.css + '**/*.css'), + path.resolve(paths().source.styleguide + 'css/*.css'), + path.resolve(paths().source.patterns + '**/*.mustache'), + path.resolve(paths().source.patterns + '**/*.json'), + path.resolve(paths().source.fonts + '/*'), + path.resolve(paths().source.images + '/*'), + path.resolve(paths().source.data + '*.json'), + path.resolve(paths().source.js + '/*.js') + ], + tasks: ['default', 'bsReload:css'] + } + }, + nodeunit: { + all: ['test/*_tests.js'] + }, + browserSync: { + dev: { + options: { + server: path.resolve(paths().public.root), + watchTask: true, + watchOptions: { + ignoreInitial: true, + ignored: '*.html' + }, + snippetOptions: { + // Ignore all HTML files within the templates folder + blacklist: ['/index.html', '/', '/?*'] + }, + plugins: [ + { + module: 'bs-html-injector', + options: { + files: [path.resolve(paths().public.root + '/index.html'), path.resolve(paths().public.styleguide + '/styleguide.html')] + } + } + ], + notify: { + styles: [ + 'display: none', + 'padding: 15px', + 'font-family: sans-serif', + 'position: fixed', + 'font-size: 1em', + 'z-index: 9999', + 'bottom: 0px', + 'right: 0px', + 'border-top-left-radius: 5px', + 'background-color: #1B2032', + 'opacity: 0.4', + 'margin: 0', + 'color: white', + 'text-align: center' + ] + } + } + } + }, + eslint: { + options: { + configFile: './.eslintrc' + }, + target: ['./core/lib/*'] + }, + bsReload: { + css: path.resolve(paths().public.root + '**/*.css') + } + }); - // load all grunt tasks - require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); + // load all grunt tasks + require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); - //load the patternlab task - grunt.task.loadTasks('./builder/'); + //load the patternlab task + grunt.task.loadTasks('./core/lib/'); - grunt.registerTask('default', ['patternlab', 'copy:main', 'copy:styleguide']); + grunt.registerTask('default', ['patternlab', 'copy:main', 'copy:styleguide']); - //travis CI task - grunt.registerTask('travis', ['nodeunit', 'patternlab']); + //travis CI task + grunt.registerTask('travis', ['nodeunit', 'eslint', 'patternlab']); - grunt.registerTask('serve', ['patternlab', 'copy:main', 'copy:styleguide', 'browserSync', 'watch:all']); + grunt.registerTask('serve', ['patternlab', 'copy:main', 'copy:styleguide', 'browserSync', 'watch:all']); - grunt.registerTask('build', ['nodeunit', 'concat']); + grunt.registerTask('build', ['nodeunit', 'eslint', 'concat']); }; diff --git a/README.md b/README.md index 156abd8ac..307a5bd06 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ This repository ships with two `package.json` files, a `Gruntfile.js`, and a `gu To run patternlab-node using grunt, do the following in the directory you downloaded and extracted the zipped release: 1. Run `npm install` from the command line -2. Optionally, delete `package.gulp.json`, `gulpfile.js`, and `builder/patternlab_gulp.js` files if you are certain you don't need it. -* Not deleting `builder/patternlab_gulp.js` may cause a harmless error when running grunt. Delete it. +2. Optionally, delete `package.gulp.json`, `gulpfile.js`, and `core/lib/patternlab_gulp.js` files if you are certain you don't need it. +* Not deleting `core/lib/patternlab_gulp.js` may cause a harmless error when running grunt. Delete it. 3. Run `grunt` or `grunt serve` from the command line This creates all patterns, the styleguide, and the pattern lab site. It's strongly recommended to run `grunt serve` to have BrowserSync spin up and serve the files to you. @@ -86,7 +86,7 @@ Get more information about patternlab-node, pattern lab in general, and where to To have patternlab-node watch for changes to either a mustache template, data, or stylesheets, run `grunt|gulp watch` or `grunt|gulp serve`. The `Gruntfile|Gulpfile` governs what is watched. It should be easy to add scss or whatever preprocessor you fancy. ##### Configurable Paths -Pattern Lab Node ships with a particular source and public workflow intended to separate the code you work on with the code generated for consumption elsewhere. If you wish to change any paths, you may do so within `config.json`. The contents are here: +Pattern Lab Node ships with a particular source and public workflow intended to separate the code you work on with the code generated for consumption elsewhere. If you wish to change any paths, you may do so within `patternlab-config.json`. The contents are here: ``` "paths" : { @@ -117,7 +117,7 @@ Pattern Lab Node ships with a particular source and public workflow intended to Note the intentional repitition of the nested structure, made this way for maximum flexibility. Relative paths are default but absolute paths should work too. You may also use these paths within Grunt or Gulp files by referring to the paths() object. ##### Nav Bar Controls -If you don't have a need for some of the nav-bar tools in the Pattern Lab frontend, you can turn them off in `config.json`. +If you don't have a need for some of the nav-bar tools in the Pattern Lab frontend, you can turn them off in `patternlab-config.json`. The current selection is as follows. @@ -144,18 +144,55 @@ The current selection is as follows. } ``` ##### Pattern States -You can set the state of a pattern by including it in `config.json` too. The out of the box styles are in progress (orange), in review (yellow), and complete (green). -Pattern states should be lowercase and use hyphens where spaces are present. +You can set the state of a pattern by including its key in the `patternStates` object in `patternlab-config.json`, along with a style defined inside `patternStateCascade`. The out of the box styles are in progress (orange), in review (yellow), and complete (green). ``` "patternStates": { - "colors" : "inprogress", - "fonts" : "inreview", - "three-up" : "complete" + "atoms-colors" : "complete", + "molecules-primary-nav" : "inreview", + "organisms-header" : "inprogress" +} +``` + +Note that patterns inherit the lowest common denominator pattern state of their lineage. +Consider: +``` +"patternStates": { + "molecules-single-comment" : "complete", + "organisms-sticky-comment" : "inreview", + "templates-article" : "complete" +} +``` +In this case, two things are of note: + +* templates-article will display inreview since it inherits `organisms-sticky-comment` +* pages-article will not display any pattern state, as it does not define one + +The `patternStateCascade` array is important in that the order is hierarchical. +The default is below: + +``` +"patternStateCascade": ["inprogress", "inreview", "complete"], +``` + +which correspond to classes defined inside `./core/styleguide/css/styleguide.css` + +``` +/* pattern states */ +.inprogress:before { + color: #FF4136 !important; +} + +.inreview:before { + color: #FFCC00 !important; +} + +.complete:before { + color: #2ECC40 !important; } ``` ##### Pattern Export -`config.json` also has two properties that work together to export completed patterns for use in a production environment. Provide an array of keys and an output directory. Pattern Lab doesn't ship with any pattern export keys, but the default directory is `"./pattern_exports/"` created inside the install directory. +`patternlab-config.json` also has two properties that work together to export completed patterns for use in a production environment. Provide an array of keys and an output directory. Pattern Lab doesn't ship with any pattern export keys, but the default directory is `"./pattern_exports/"` created inside the install directory. ``` "patternExportKeys": ["molecules-primary-nav", "organisms-header", "organisms-header"], @@ -164,6 +201,28 @@ Pattern states should be lowercase and use hyphens where spaces are present. Coupled with exported css (much easier to extract with existing tools like [grunt-contrib-copy](https://github.com/gruntjs/grunt-contrib-copy)), pattern export can help to maintain the relevancy of the design system by directly placing partials in a directory of your choosing. +##### cacheBust +`patternlab-config.json` has this flag to instruct Pattern Lab to append a unique query string to Javascript and CSS assets throughout the frontend. + +``` +"cacheBust": true +``` + +Default: true + +##### defaultPattter +`patternlab-config.json` has an entry that allows you to specifiy a specific pattern upon launch of the main site. It works even without BrowserSync running. Set it like this: + +``` +"defaultPattern": "pages-homepage", +``` +Default: "all" +If running with BrowserSync, you may also set [this BrowserSync options](https://www.browsersync.io/docs/options/#option-startPath) to achieve the same result via your Gruntfile or Gulpfile. + +``` +startPath: '/?p=pages-homepage', +``` + ##### baseurl If your instance of Pattern Lab lives in a subdirectory of your server, for instance on github pages (ex: yourusername.github.io/patterns-demo/), then add the baseurl here. The baseurl is everything after the hostname - ie: `patterns-demo` @@ -182,7 +241,7 @@ You can also exclude complete directories by prepending the directory name with ##### Style Guide Excludes -Exclude whole pattern types from the "All patterns" styleguide by adding entries to `config.json`. This is quite useful to make speedier. Pattern Lab Node ships with the following: +Exclude whole pattern types from the "All patterns" styleguide by adding entries to `patternlab-config.json`. This is quite useful to make speedier. Pattern Lab Node ships with the following: ``` "styleGuideExcludes": [ @@ -193,7 +252,7 @@ Exclude whole pattern types from the "All patterns" styleguide by adding entries ##### Debug Mode -`patternlab.json` is a file created for debugging purposes. Set `debug` to true in `.config.json` to see all the secrets. +`patternlab.json` is a file created for debugging purposes. Set `debug` to true in `.patternlab-config.json` to see all the secrets. ##### Server & BrowserSync Running `grunt serve` or `gulp serve` will compile the Pattern Lab frontend and host it by default on http://localhost:3000 via [BrowserSync](http://www.browsersync.io/docs/). After it starts, templates, `data.json`, and scss/css changes in your source code will be automatically injected into the page. diff --git a/builder/lineage_hunter.js b/builder/lineage_hunter.js deleted file mode 100644 index 7325a5fde..000000000 --- a/builder/lineage_hunter.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var lineage_hunter = function(){ - - function findlineage(pattern, patternlab){ - - var pa = require('./pattern_assembler'); - var pattern_assembler = new pa(); - - //find the {{> template-name }} within patterns - var matches = pattern_assembler.find_pattern_partials(pattern); - if(matches !== null){ - matches.forEach(function(match, index, matches){ - //strip out the template cruft - var foundPatternKey = match.replace("{{> ", "").replace(" }}", "").replace("{{>", "").replace("}}", ""); - - // remove any potential pattern parameters. this and the above are rather brutish but I didn't want to do a regex at the time - if(foundPatternKey.indexOf('(') > 0){ - foundPatternKey = foundPatternKey.substring(0, foundPatternKey.indexOf('(')); - } - - //remove any potential stylemodifiers. - foundPatternKey = foundPatternKey.split(':')[0]; - - //get the ancestorPattern - var ancestorPattern = pattern_assembler.get_pattern_by_key(foundPatternKey, patternlab); - - if (ancestorPattern && pattern.lineageIndex.indexOf(ancestorPattern.key) === -1){ - - //add it since it didnt exist - pattern.lineageIndex.push(ancestorPattern.key); - //create the more complex patternLineage object too - var l = { - "lineagePattern": ancestorPattern.key, - "lineagePath": "../../patterns/" + ancestorPattern.patternLink - }; - pattern.lineage.push(JSON.stringify(l)); - - //also, add the lineageR entry if it doesn't exist - if (ancestorPattern.lineageRIndex.indexOf(pattern.key) === -1){ - ancestorPattern.lineageRIndex.push(pattern.key); - - //create the more complex patternLineage object in reverse - var lr = { - "lineagePattern": pattern.key, - "lineagePath": "../../patterns/" + pattern.patternLink - }; - ancestorPattern.lineageR.push(JSON.stringify(lr)); - } - } - }); - } - } - - return { - find_lineage: function(pattern, patternlab){ - findlineage(pattern, patternlab); - } - }; - - }; - - module.exports = lineage_hunter; - -}()); diff --git a/builder/list_item_hunter.js b/builder/list_item_hunter.js deleted file mode 100644 index 93aa57890..000000000 --- a/builder/list_item_hunter.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var list_item_hunter = function(){ - - var extend = require('util')._extend, - pa = require('./pattern_assembler'), - smh = require('./style_modifier_hunter'), - mustache = require('mustache'), - pattern_assembler = new pa(), - style_modifier_hunter = new smh(), - items = [ 'zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve','thirteen','fourteen','fifteen','sixteen','seventeen','eighteen','nineteen','twenty']; - - function processListItemPartials(pattern, patternlab){ - //find any listitem blocks - var matches = pattern_assembler.find_list_items(pattern, patternlab); - if(matches !== null){ - matches.forEach(function(liMatch, index, matches){ - - if(patternlab.config.debug){ - console.log('found listItem of size ' + liMatch + ' inside ' + pattern.key); - } - - //find the boundaries of the block - var loopNumberString = liMatch.split('.')[1].split('}')[0].trim(); - var end = liMatch.replace('#', '/'); - var patternBlock = pattern.template.substring(pattern.template.indexOf(liMatch) + liMatch.length, pattern.template.indexOf(end)).trim(); - //build arrays that repeat the block, however large we need to - var repeatedBlockTemplate = []; - var repeatedBlockHtml = ''; - for(var i = 0; i < items.indexOf(loopNumberString); i++){ - repeatedBlockTemplate.push(patternBlock); - } - - //check for a local listitems.json file - var listData = JSON.parse(JSON.stringify(patternlab.listitems)); - listData = pattern_assembler.merge_data(listData, pattern.listitems); - - //iterate over each copied block, rendering its contents along with pattenlab.listitems[i] - for(var i = 0; i < repeatedBlockTemplate.length; i++){ - - var thisBlockTemplate = repeatedBlockTemplate[i]; - var thisBlockHTML = ""; - - //combine listItem data with pattern data with global data - var itemData = listData['' + items.indexOf(loopNumberString)]; //this is a property like "2" - var globalData = JSON.parse(JSON.stringify(patternlab.data)); - var localData = JSON.parse(JSON.stringify(pattern.jsonFileData)); - - var allData = pattern_assembler.merge_data(globalData, localData); - allData = pattern_assembler.merge_data(allData, itemData != undefined ? itemData[i] : {}); //itemData could be undefined if the listblock contains no partial, just markup - allData.link = extend({}, patternlab.data.link); - - //check for partials within the repeated block - var foundPartials = pattern_assembler.find_pattern_partials({ 'template' : thisBlockTemplate }); - - if(foundPartials && foundPartials.length > 0){ - - for(var j = 0; j < foundPartials.length; j++){ - - //get the partial - var partialName = foundPartials[j].match(/([\w\-\.\/~]+)/g)[0]; - var partialPattern = pattern_assembler.get_pattern_by_key(partialName, patternlab); - - //create a copy of the partial so as to not pollute it after the get_pattern_by_key call. - var cleanPartialPattern = JSON.parse(JSON.stringify(partialPattern)); - - //if partial has style modifier data, replace the styleModifier value - if(foundPartials[j].indexOf(':') > -1){ - style_modifier_hunter.consume_style_modifier(cleanPartialPattern, foundPartials[j], patternlab); - } - - //replace its reference within the block with the extended template - thisBlockTemplate = thisBlockTemplate.replace(foundPartials[j], cleanPartialPattern.extendedTemplate); - } - - //render with data - thisBlockHTML = pattern_assembler.renderPattern(thisBlockTemplate, allData, patternlab.partials); - - } else{ - //just render with mergedData - thisBlockHTML = pattern_assembler.renderPattern(thisBlockTemplate, allData, patternlab.partials); - } - - //add the rendered HTML to our string - repeatedBlockHtml = repeatedBlockHtml + thisBlockHTML; - } - - //replace the block with our generated HTML - var repeatingBlock = pattern.extendedTemplate.substring(pattern.extendedTemplate.indexOf(liMatch), pattern.extendedTemplate.indexOf(end) + end.length); - pattern.extendedTemplate = pattern.extendedTemplate.replace(repeatingBlock, repeatedBlockHtml); - - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[pattern.key] = pattern.extendedTemplate; - - }); - } - } - - return { - process_list_item_partials: function(pattern, patternlab){ - processListItemPartials(pattern, patternlab); - } - }; - - }; - - module.exports = list_item_hunter; - -}()); diff --git a/builder/media_hunter.js b/builder/media_hunter.js deleted file mode 100644 index 3ee48f4fe..000000000 --- a/builder/media_hunter.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var diveSync = require('diveSync'), - path = require('path'), - fs = require('fs-extra'); - - var media_hunter = function(){ - - function findMediaQueries(dir, patternlab){ - patternlab.mediaQueries = []; - - diveSync(dir, function(err, file){ - if(path.extname(file) === '.css'){ - var contents = fs.readFileSync(file, 'utf8'); - var safeContents = contents.replace("\r", " ").replace("\n", " "); - var matches = safeContents.match(/\((min|max)-width:([ ]+)?(([0-9]{1,5})(\.[0-9]{1,20}|)(px|em))/g); - for(var i = 0; i < matches.length; i++){ - var breakpoint = matches[i].substring(matches[i].indexOf(':') + 1).trimLeft(); - if(patternlab.mediaQueries.indexOf(breakpoint) === -1){ - patternlab.mediaQueries.push(breakpoint); - } - } - } - }); - patternlab.mediaQueries.sort(function(a,b){ - var integerPartA = a.match(/(?:\d*\.)?\d+/g); - var integerPartB = b.match(/(?:\d*\.)?\d+/g); - return parseInt(a,10) > parseInt(b,10); - }); - } - - return { - find_media_queries: function(dir, patternlab){ - findMediaQueries(dir, patternlab); - } - }; - - }; - - module.exports = media_hunter; - -}()); diff --git a/builder/object_factory.js b/builder/object_factory.js deleted file mode 100644 index 5526bdc18..000000000 --- a/builder/object_factory.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var oPattern = function(abspath, subdir, filename, data){ - this.fileName = filename.substring(0, filename.indexOf('.')); - this.abspath = abspath; - this.subdir = subdir; - this.name = subdir.replace(/[\/\\]/g, '-') + '-' + this.fileName; //this is the unique name with the subDir - this.jsonFileData = data || {}; - this.patternName = this.fileName.replace(/^\d*\-/, ''); - this.patternDisplayName = this.patternName.split('-').reduce(function(val, working){ - return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1); - }, '').trim(); //this is the display name for the ui. strip numeric + hyphen prefixes - this.patternLink = this.name + '/' + this.name + '.html'; - this.patternGroup = this.name.substring(this.name.indexOf('-') + 1, this.name.indexOf('-', 4) + 1 - this.name.indexOf('-') + 1); - this.patternSubGroup = subdir.substring(subdir.indexOf('/') + 4); - this.flatPatternPath = subdir.replace(/[\/\\]/g, '-'); - this.key = this.patternGroup + '-' + this.patternName; - this.template = ''; - this.patternPartial = ''; - this.lineage = []; - this.lineageIndex = []; - this.lineageR = []; - this.lineageRIndex = []; - }; - - var oBucket = function(name){ - this.bucketNameLC = name; - this.bucketNameUC = name.split('-').reduce(function(val, working){ - return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1); - }, '').trim(); - this.navItems = []; - this.navItemsIndex = []; - this.patternItems = []; - this.patternItemsIndex = []; - }; - - var oNavItem = function(name){ - this.sectionNameLC = name; - this.sectionNameUC = name.split('-').reduce(function(val, working){ - return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1); - }, '').trim(); - this.navSubItems = []; - this.navSubItemsIndex = []; - }; - - var oNavSubItem = function(name){ - this.patternPath = ''; - this.patternPartial = ''; - this.patternName = name.split(' ').reduce(function(val, working){ - return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1); - }, '').trim(); - }; - - module.exports = { - oPattern: oPattern, - oBucket: oBucket, - oNavItem: oNavItem, - oNavSubItem: oNavSubItem - }; - -}()); diff --git a/builder/parameter_hunter.js b/builder/parameter_hunter.js deleted file mode 100644 index a3f574951..000000000 --- a/builder/parameter_hunter.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var parameter_hunter = function(){ - - var extend = require('util')._extend, - pa = require('./pattern_assembler'), - mustache = require('mustache'), - smh = require('./style_modifier_hunter'), - style_modifier_hunter = new smh(), - pattern_assembler = new pa(); - - function findparameters(pattern, patternlab){ - - if(pattern.parameteredPartials && pattern.parameteredPartials.length > 0){ - //compile this partial immeadiately, essentially consuming it. - pattern.parameteredPartials.forEach(function(pMatch, index, matches){ - //find the partial's name and retrieve it - var partialName = pMatch.match(/([\w\-\.\/~]+)/g)[0]; - var partialPattern = pattern_assembler.get_pattern_by_key(partialName, patternlab); - //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #190 - partialPattern.extendedTemplate = partialPattern.template; - - if(patternlab.config.debug){ - console.log('found patternParameters for ' + partialName); - } - - //strip out the additional data, convert string to JSON. - var leftParen = pMatch.indexOf('('); - var rightParen = pMatch.indexOf(')'); - var paramString = '{' + pMatch.substring(leftParen + 1, rightParen) + '}'; - //if param keys are wrapped in single quotes, replace with double quotes. - var paramStringWellFormed = paramString.replace(/(')([^']+)(')(\s*\:)/g, '"$2"$4'); - //if params keys are not wrapped in any quotes, wrap in double quotes. - var paramStringWellFormed = paramStringWellFormed.replace(/([\{|,]\s*)([^\s"'\:]+)(\s*\:)/g, '$1"$2"$3'); - //if param values are wrapped in single quotes, replace with double quotes. - var paramStringWellFormed = paramStringWellFormed.replace(/(\:\s*)(')([^']+)(')/g, '$1"$3"'); - - var paramData = {}; - var globalData = {}; - var localData = {}; - - try { - paramData = JSON.parse(paramStringWellFormed); - globalData = JSON.parse(JSON.stringify(patternlab.data)); - localData = JSON.parse(JSON.stringify(pattern.jsonFileData || {})); - } catch(e){ - console.log(e); - } - - var allData = pattern_assembler.merge_data(globalData, localData); - allData = pattern_assembler.merge_data(allData, paramData); - - //if partial has style modifier data, replace the styleModifier value - if(pattern.stylePartials && pattern.stylePartials.length > 0){ - style_modifier_hunter.consume_style_modifier(partialPattern, pMatch, patternlab); - } - - //extend pattern data links into link for pattern link shortcuts to work. we do this locally and globally - allData.link = extend({}, patternlab.data.link); - - var renderedPartial = pattern_assembler.renderPattern(partialPattern.extendedTemplate, allData, patternlab.partials); - - //remove the parameter from the partial and replace it with the rendered partial + paramData - pattern.extendedTemplate = pattern.extendedTemplate.replace(pMatch, renderedPartial); - - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[pattern.key] = pattern.extendedTemplate; - }); - } - } - - return { - find_parameters: function(pattern, patternlab){ - findparameters(pattern, patternlab); - } - }; - - }; - - module.exports = parameter_hunter; - -}()); diff --git a/builder/pattern_assembler.js b/builder/pattern_assembler.js deleted file mode 100644 index d200f059d..000000000 --- a/builder/pattern_assembler.js +++ /dev/null @@ -1,425 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var pattern_assembler = function(){ - - function isObjectEmpty(obj) { - for(var prop in obj) { - if(obj.hasOwnProperty(prop)) - return false; - } - - return true; - } - - // returns any patterns that match {{> value:mod }} or {{> value:mod(foo:"bar") }} within the pattern - function findPartialsWithStyleModifiers(pattern){ - var matches = pattern.template.match(/{{>([ ])?([\w\-\.\/~]+)(?!\()(\:[A-Za-z0-9-_|]+)+(?:(| )\(.*)?([ ])?}}/g); - return matches; - } - - // returns any patterns that match {{> value(foo:"bar") }} or {{> value:mod(foo:"bar") }} within the pattern - function findPartialsWithPatternParameters(pattern){ - var matches = pattern.template.match(/{{>([ ])?([\w\-\.\/~]+)(?:\:[A-Za-z0-9-_|]+)?(?:(| )\(.*)+([ ])?}}/g); - return matches; - } - - //find and return any {{> template-name* }} within pattern - function findPartials(pattern){ - var matches = pattern.template.match(/{{>([ ])?([\w\-\.\/~]+)(?:\:[A-Za-z0-9-_|]+)?(?:(| )\(.*)?([ ])?}}/g); - return matches; - } - - function findListItems(pattern){ - var matches = pattern.template.match(/({{#( )?)(list(I|i)tems.)(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)( )?}}/g); - return matches; - } - - function setState(pattern, patternlab){ - if(patternlab.config.patternStates && patternlab.config.patternStates[pattern.patternName]){ - pattern.patternState = patternlab.config.patternStates[pattern.patternName]; - } else{ - pattern.patternState = ""; - } - } - - function addPattern(pattern, patternlab){ - //add the link to the global object - patternlab.data.link[pattern.patternGroup + '-' + pattern.patternName] = '/patterns/' + pattern.patternLink; - - //only push to array if the array doesn't contain this pattern - var isNew = true; - for(var i = 0; i < patternlab.patterns.length; i++){ - //so we need the identifier to be unique, which patterns[i].abspath is - if(pattern.abspath === patternlab.patterns[i].abspath){ - //if abspath already exists, overwrite that element - patternlab.patterns[i] = pattern; - patternlab.partials[pattern.key] = pattern.extendedTemplate || pattern.template; - isNew = false; - break; - } - } - //if the pattern is new, just push to the array - if(isNew){ - patternlab.patterns.push(pattern); - patternlab.partials[pattern.key] = pattern.extendedTemplate || pattern.template; - } - } - - function renderPattern(template, data, partials) { - - var mustache = require('mustache'); - - if(partials) { - return mustache.render(template, data, partials); - } else{ - return mustache.render(template, data); - } - } - - function processPatternIterative(file, patternlab){ - var fs = require('fs-extra'), - of = require('./object_factory'), - path = require('path'); - - //extract some information - var subdir = path.dirname(path.relative(patternlab.config.paths.source.patterns, file)).replace('\\', '/'); - var filename = path.basename(file); - var ext = path.extname(filename); - - //ignore dotfiles, underscored files, and non-variant .json files - if(filename.charAt(0) === '.' || (ext === '.json' && filename.indexOf('~') === -1)){ - return; - } - - //make a new Pattern Object - var currentPattern = new of.oPattern(file, subdir, filename); - - //if file is named in the syntax for variants, no need to process further - //processPatternRecursive() will run find_pseudopatterns() and look at each pattern for a variant - if(ext === '.json' && filename.indexOf('~') > -1){ - return; - } - - //can ignore all non-mustache files at this point - if(ext !== '.mustache'){ - return; - } - - //see if this file has a state - setState(currentPattern, patternlab); - - //look for a json file for this template - try { - var jsonFilename = path.resolve(patternlab.config.paths.source.patterns, currentPattern.subdir, currentPattern.fileName + ".json"); - currentPattern.jsonFileData = fs.readJSONSync(jsonFilename); - if(patternlab.config.debug){ - console.log('found pattern-specific data.json for ' + currentPattern.key); - } - } - catch(e) { - } - - //look for a listitems.json file for this template - try { - var listJsonFileName = path.resolve(patternlab.config.paths.source.patterns, currentPattern.subdir,currentPattern.fileName + ".listitems.json"); - currentPattern.listitems = fs.readJSONSync(listJsonFileName); - buildListItems(currentPattern); - if(patternlab.config.debug){ - console.log('found pattern-specific listitems.json for ' + currentPattern.key); - } - } - catch(e) { - } - - //add the raw template to memory - currentPattern.template = fs.readFileSync(file, 'utf8'); - - //find any stylemodifiers that may be in the current pattern - currentPattern.stylePartials = findPartialsWithStyleModifiers(currentPattern); - - //find any pattern parameters that may be in the current pattern - currentPattern.parameteredPartials = findPartialsWithPatternParameters(currentPattern); - - //add currentPattern to patternlab.patterns array - addPattern(currentPattern, patternlab); - } - - function processPatternRecursive(file, patternlab, additionalData){ - - var fs = require('fs-extra'), - mustache = require('mustache'), - lh = require('./lineage_hunter'), - ph = require('./parameter_hunter'), - pph = require('./pseudopattern_hunter'), - lih = require('./list_item_hunter'), - smh = require('./style_modifier_hunter'), - path = require('path'); - - var parameter_hunter = new ph(), - lineage_hunter = new lh(), - list_item_hunter = new lih(), - style_modifier_hunter = new smh(), - pseudopattern_hunter = new pph(); - - //find current pattern in patternlab object using var file as a key - var currentPattern, - i; - - for(i = 0; i < patternlab.patterns.length; i++){ - if(patternlab.patterns[i].abspath === file){ - currentPattern = patternlab.patterns[i]; - break; - } - } - - //return if processing an ignored file - if(typeof currentPattern === 'undefined'){ - return; - } - - currentPattern.extendedTemplate = currentPattern.template; - - //find how many partials there may be for the given pattern - var foundPatternPartials = findPartials(currentPattern); - - if(foundPatternPartials !== null && foundPatternPartials.length > 0){ - - if(patternlab.config.debug){ - console.log('found partials for ' + currentPattern.key); - } - - //find any listItem blocks - list_item_hunter.process_list_item_partials(currentPattern, patternlab); - - //determine if the template contains any pattern parameters. if so they must be immediately consumed - parameter_hunter.find_parameters(currentPattern, patternlab); - - //do something with the regular old partials - for(i = 0; i < foundPatternPartials.length; i++){ - var partialKey = foundPatternPartials[i].replace(/{{>([ ])?([\w\-\.\/~]+)(:[A-z0-9-_|]+)?(?:\:[A-Za-z0-9-_]+)?(?:(| )\(.*)?([ ])?}}/g, '$2'); - - var partialPath; - - //identify which pattern this partial corresponds to - for(var j = 0; j < patternlab.patterns.length; j++){ - if(patternlab.patterns[j].key === partialKey || - patternlab.patterns[j].abspath.indexOf(partialKey) > -1) - { - partialPath = patternlab.patterns[j].abspath; - } - } - - //recurse through nested partials to fill out this extended template. - processPatternRecursive(partialPath, patternlab); - - //complete assembly of extended template - var partialPattern = getpatternbykey(partialKey, patternlab); - - //if partial has style modifier data, replace the styleModifier value - if(currentPattern.stylePartials && currentPattern.stylePartials.length > 0){ - style_modifier_hunter.consume_style_modifier(partialPattern, foundPatternPartials[i], patternlab); - } - - currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], partialPattern.extendedTemplate); - - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[currentPattern.key] = currentPattern.extendedTemplate; - } - - } else{ - //find any listItem blocks that within the pattern, even if there are no partials - list_item_hunter.process_list_item_partials(currentPattern, patternlab); - } - - //find pattern lineage - lineage_hunter.find_lineage(currentPattern, patternlab); - - //add to patternlab object so we can look these up later. - addPattern(currentPattern, patternlab); - - //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json - pseudopattern_hunter.find_pseudopatterns(currentPattern, patternlab); - } - - function getpatternbykey(key, patternlab){ - - //look for exact key matches - for(var i = 0; i < patternlab.patterns.length; i++){ - if(patternlab.patterns[i].key === key){ - return patternlab.patterns[i]; - } - } - - //else look by verbose syntax - for(var i = 0; i < patternlab.patterns.length; i++){ - switch(key){ - case patternlab.patterns[i].subdir + '/' + patternlab.patterns[i].fileName: - case patternlab.patterns[i].subdir + '/' + patternlab.patterns[i].fileName + '.mustache': - return patternlab.patterns[i]; - } - } - - //return the fuzzy match if all else fails - for(var i = 0; i < patternlab.patterns.length; i++){ - var keyParts = key.split('-'), - keyType = keyParts[0], - keyName = keyParts.slice(1).join('-'); - if(patternlab.patterns[i].key.split('-')[0] === keyType && patternlab.patterns[i].key.indexOf(keyName) > -1){ - return patternlab.patterns[i]; - } - } - throw 'Could not find pattern with key ' + key; - } - - function mergeData(obj1, obj2){ - if(typeof obj2 === 'undefined'){ - obj2 = {}; - } - for(var p in obj1){ - try { - // Only recurse if obj1[p] is an object. - if(obj1[p].constructor === Object){ - // Requires 2 objects as params; create obj2[p] if undefined. - if(typeof obj2[p] === 'undefined'){ - obj2[p] = {}; - } - obj2[p] = mergeData(obj1[p], obj2[p]); - // Pop when recursion meets a non-object. If obj1[p] is a non-object, - // only copy to undefined obj2[p]. This way, obj2 maintains priority. - } else if(typeof obj2[p] === 'undefined'){ - obj2[p] = obj1[p]; - } - } catch(e) { - // Property in destination object not set; create it and set its value. - if(typeof obj2[p] === 'undefined'){ - obj2[p] = obj1[p]; - } - } - } - return obj2; - } - - function buildListItems(container){ - //combine all list items into one structure - var list = []; - for (var item in container.listitems) { - if( container.listitems.hasOwnProperty(item)) { - list.push(container.listitems[item]); - } - } - container.listItemArray = shuffle(list); - - for(var i = 1; i <= container.listItemArray.length; i++){ - var tempItems = []; - if( i === 1){ - tempItems.push(container.listItemArray[0]); - container.listitems['' + i ] = tempItems; - } else{ - for(var c = 1; c <= i; c++){ - tempItems.push(container.listItemArray[c - 1]); - container.listitems['' + i ] = tempItems; - } - } - } - } - - //http://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript - function shuffle(o){ - for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); - return o; - } - - function parseDataLinksHelper (patternlab, obj, key) { - var linkRE, dataObjAsString, linkMatches, expandedLink; - - linkRE = /link\.[A-z0-9-_]+/g - dataObjAsString = JSON.stringify(obj); - linkMatches = dataObjAsString.match(linkRE) - - if(linkMatches) { - for (var i = 0; i < linkMatches.length; i++) { - expandedLink = patternlab.data.link[linkMatches[i].split('.')[1]]; - if (expandedLink) { - if(patternlab.config.debug){ - console.log('expanded data link from ' + linkMatches[i] + ' to ' + expandedLink + ' inside ' + key); - } - dataObjAsString = dataObjAsString.replace(linkMatches[i], expandedLink); - } - } - } - return JSON.parse(dataObjAsString) - } - //look for pattern links included in data files. - //these will be in the form of link.* WITHOUT {{}}, which would still be there from direct pattern inclusion - function parseDataLinks(patternlab) { - //look for link.* such as link.pages-blog as a value - - patternlab.data = parseDataLinksHelper(patternlab, patternlab.data, 'data.json') - - //loop through all patterns - for (var i = 0; i < patternlab.patterns.length; i++) { - patternlab.patterns[i].jsonFileData = parseDataLinksHelper(patternlab, patternlab.patterns[i].jsonFileData, patternlab.patterns[i].key) - } - } - - return { - find_pattern_partials: function(pattern){ - return findPartials(pattern); - }, - find_pattern_partials_with_style_modifiers: function(pattern){ - return findPartialsWithStyleModifiers(pattern); - }, - find_pattern_partials_with_parameters: function(pattern){ - return findPartialsWithPatternParameters(pattern); - }, - find_list_items: function(pattern){ - return findListItems(pattern) - }, - setPatternState: function(pattern, patternlab){ - setState(pattern, patternlab); - }, - addPattern: function(pattern, patternlab){ - addPattern(pattern, patternlab); - }, - renderPattern: function(template, data, partials){ - return renderPattern(template, data, partials); - }, - process_pattern_iterative: function(file, patternlab){ - processPatternIterative(file, patternlab); - }, - process_pattern_recursive: function(file, patternlab, additionalData){ - processPatternRecursive(file, patternlab, additionalData); - }, - get_pattern_by_key: function(key, patternlab){ - return getpatternbykey(key, patternlab); - }, - merge_data: function(existingData, newData){ - return mergeData(existingData, newData); - }, - combine_listItems: function(patternlab){ - buildListItems(patternlab); - }, - is_object_empty: function(obj){ - return isObjectEmpty(obj); - }, - parse_data_links: function(patternlab){ - parseDataLinks(patternlab); - } - }; - - }; - - module.exports = pattern_assembler; - -}()); diff --git a/builder/pattern_exporter.js b/builder/pattern_exporter.js deleted file mode 100644 index 3d04658d3..000000000 --- a/builder/pattern_exporter.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var fs = require('fs-extra'), - path = require('path'); - - var pattern_exporter = function(){ - - function exportPatterns(patternlab){ - - //read the config export options - var exportKeys = patternlab.config.patternExportKeys; - - //find the chosen patterns to export - for (var i = 0; i < exportKeys.length; i++){ - for (var j = 0; j < patternlab.patterns.length; j++){ - if(exportKeys[i] === patternlab.patterns[j].key){ - //write matches to the desired location - fs.outputFileSync(patternlab.config.patternExportDirectory + patternlab.patterns[j].key + '.html', patternlab.patterns[j].patternPartial); - } - } - } - } - - return { - export_patterns: function(patternlab){ - exportPatterns(patternlab); - } - }; - - }; - - module.exports = pattern_exporter; - -}()); diff --git a/builder/patternlab_grunt.js b/builder/patternlab_grunt.js deleted file mode 100644 index 93b50b1e7..000000000 --- a/builder/patternlab_grunt.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -var patternlab_engine = require('./patternlab.js'); - -module.exports = function(grunt) { - grunt.registerTask('patternlab', 'create design systems with atomic design', function(arg) { - - var patternlab = patternlab_engine(); - - if(arguments.length === 0){ - patternlab.build(true); - } - - if(arg && arg === 'v'){ - patternlab.version(); - } - - if(arg && arg === "only_patterns"){ - patternlab.build_patterns_only(true); - } - - if(arg && arg === "help"){ - patternlab.help(); - } - - if(arg && (arg !== "v" && arg !=="only_patterns" && arg !=="help")){ - patternlab.help(); - } - - }); - -}; diff --git a/builder/pseudopattern_hunter.js b/builder/pseudopattern_hunter.js deleted file mode 100644 index 77773040e..000000000 --- a/builder/pseudopattern_hunter.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var pseudopattern_hunter = function(){ - - function findpseudopatterns(currentPattern, patternlab){ - - var glob = require('glob'), - fs = require('fs-extra'), - pa = require('./pattern_assembler'), - lh = require('./lineage_hunter'), - of = require('./object_factory'), - path = require('path'); - - var pattern_assembler = new pa(); - var lineage_hunter = new lh(); - var paths = patternlab.config.paths; - - //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json - var needle = currentPattern.subdir + '/' + currentPattern.fileName + '~*.json'; - var pseudoPatterns = glob.sync(needle, { - cwd: paths.source.patterns, - debug: false, - nodir: true, - }); - - if(pseudoPatterns.length > 0){ - - for(var i = 0; i < pseudoPatterns.length; i++){ - - if(patternlab.config.debug){ - console.log('found pseudoPattern variant of ' + currentPattern.key); - } - - //we want to do everything we normally would here, except instead read the pseudoPattern data - var variantFileData = fs.readJSONSync(path.resolve(paths.source.patterns, pseudoPatterns[i])); - - //extend any existing data with variant data - variantFileData = pattern_assembler.merge_data(currentPattern.jsonFileData, variantFileData); - - var variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0]; - var variantFilePath = path.resolve(paths.source.patterns, currentPattern.subdir, currentPattern.fileName + '~' + variantName + '.json'); - var variantFileName = currentPattern.fileName + '-' + variantName + '.'; - var patternVariant = new of.oPattern(variantFilePath, currentPattern.subdir, variantFileName, variantFileData); - - //see if this file has a state - pattern_assembler.setPatternState(patternVariant, patternlab); - - //use the same template as the non-variant - patternVariant.template = currentPattern.template; - patternVariant.extendedTemplate = currentPattern.extendedTemplate; - - //find pattern lineage - lineage_hunter.find_lineage(patternVariant, patternlab); - - //add to patternlab object so we can look these up later. - pattern_assembler.addPattern(patternVariant, patternlab); - } - } - - } - - return { - find_pseudopatterns: function(pattern, patternlab){ - findpseudopatterns(pattern, patternlab); - } - }; - - }; - - module.exports = pseudopattern_hunter; - -}()); diff --git a/builder/style_modifier_hunter.js b/builder/style_modifier_hunter.js deleted file mode 100644 index a19fbf081..000000000 --- a/builder/style_modifier_hunter.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * patternlab-node - v1.1.3 - 2016 - * - * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -(function () { - "use strict"; - - var style_modifier_hunter = function(){ - - function consumestylemodifier(pattern, partial, patternlab){ - - //extract the classname from the stylemodifier which comes in the format of :className - var styleModifier = partial.match(/:([\w\-_|])+/g) ? partial.match(/:([\w\-_|])+/g)[0].slice(1) : null; - if(styleModifier){ - - //replace the special character pipe | used to separate multiple classes with a space - styleModifier = styleModifier.replace(/\|/g, ' '); - - if(patternlab.config.debug){ - console.log('found partial styleModifier within pattern ' + pattern.key); - } - - //replace the stylemodifier placeholder with the class name - pattern.extendedTemplate = pattern.extendedTemplate.replace(/{{[ ]?styleModifier[ ]?}}/i, styleModifier); - - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[pattern.key] = pattern.extendedTemplate; - } - } - - return { - consume_style_modifier: function(pattern, partial, patternlab){ - consumestylemodifier(pattern, partial, patternlab); - } - }; - - }; - - module.exports = style_modifier_hunter; - -}()); diff --git a/config.json b/config.json deleted file mode 100644 index 0ca6ef1f0..000000000 --- a/config.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "paths" : { - "source" : { - "root": "./source/", - "patterns" : "./source/_patterns/", - "data" : "./source/_data/", - "styleguide" : "./core/styleguide/", - "patternlabFiles" : "./source/_patternlab-files/", - "js" : "./source/js", - "images" : "./source/images", - "fonts" : "./source/fonts", - "css" : "./source/css/" - }, - "public" : { - "root" : "./public/", - "patterns" : "./public/patterns/", - "data" : "./public/data/", - "styleguide" : "./public/styleguide/", - "js" : "./public/js", - "images" : "./public/images", - "fonts" : "./public/fonts", - "css" : "./public/css" - } - }, - "styleGuideExcludes": [ - "templates", - "pages" - ], - "ignored-extensions" : ["scss", "DS_Store", "less"], - "ignored-directories" : ["scss"], - "debug": false, - "ishControlsVisible": { - "s": true, - "m": true, - "l": true, - "full": true, - "random": true, - "disco": true, - "hay": true, - "mqs": true, - "find": true, - "views-all": true, - "views-annotations": true, - "views-code": true, - "views-new": true, - "tools-all": true, - "tools-sync": true, - "tools-shortcuts": true, - "tools-docs": true - }, - "patternStates": { - "homepage-emergency" : "inprogress" - }, - "patternExportKeys": [], - "patternExportDirectory": "./pattern_exports/", - "baseurl" : "" - } diff --git a/core/lib/lineage_hunter.js b/core/lib/lineage_hunter.js new file mode 100644 index 000000000..e5367f2e0 --- /dev/null +++ b/core/lib/lineage_hunter.js @@ -0,0 +1,158 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var lineage_hunter = function () { + + var pa = require('./pattern_assembler'); + + function findlineage(pattern, patternlab) { + + var pattern_assembler = new pa(); + + //find the {{> template-name }} within patterns + var matches = pattern_assembler.find_pattern_partials(pattern); + if (matches !== null) { + matches.forEach(function (match) { + //strip out the template cruft + var foundPatternKey = match + .replace("{{> ", "") + .replace(" }}", "") + .replace("{{>", "") + .replace("}}", ""); + + // remove any potential pattern parameters. this and the above are + // rather brutish but I didn't want to do a regex at the time + if (foundPatternKey.indexOf('(') > 0) { + foundPatternKey = foundPatternKey.substring(0, foundPatternKey.indexOf('(')); + } + + //remove any potential stylemodifiers. + foundPatternKey = foundPatternKey.split(':')[0]; + + //get the ancestorPattern + var ancestorPattern = pattern_assembler.get_pattern_by_key(foundPatternKey, patternlab); + + if (ancestorPattern && pattern.lineageIndex.indexOf(ancestorPattern.key) === -1) { + + //add it since it didnt exist + pattern.lineageIndex.push(ancestorPattern.key); + + //create the more complex patternLineage object too + var l = { + "lineagePattern": ancestorPattern.key, + "lineagePath": "../../patterns/" + ancestorPattern.patternLink + }; + if (ancestorPattern.patternState) { + l.lineageState = ancestorPattern.patternState; + } + + pattern.lineage.push(l); + + //also, add the lineageR entry if it doesn't exist + if (ancestorPattern.lineageRIndex.indexOf(pattern.key) === -1) { + ancestorPattern.lineageRIndex.push(pattern.key); + + //create the more complex patternLineage object in reverse + var lr = { + "lineagePattern": pattern.key, + "lineagePath": "../../patterns/" + pattern.patternLink + }; + if (pattern.patternState) { + lr.lineageState = pattern.patternState; + } + + ancestorPattern.lineageR.push(lr); + } + } + }); + } + } + + function setPatternState(direction, pattern, targetPattern) { + // if the request came from the past, apply target pattern state to current pattern lineage + if (direction === 'fromPast') { + for (var i = 0; i < pattern.lineageIndex.length; i++) { + if (pattern.lineageIndex[i] === targetPattern.key) { + pattern.lineage[i].lineageState = targetPattern.patternState; + } + } + } else { + //the request came from the future, apply target pattern state to current pattern reverse lineage + for (var i = 0; i < pattern.lineageRIndex.length; i++) { + if (pattern.lineageRIndex[i] === targetPattern.key) { + pattern.lineageR[i].lineageState = targetPattern.patternState; + } + } + } + } + + + function cascadePatternStates(patternlab) { + + var pattern_assembler = new pa(); + + for (var i = 0; i < patternlab.patterns.length; i++) { + var pattern = patternlab.patterns[i]; + + //for each pattern with a defined state + if (pattern.patternState) { + + if (pattern.lineageIndex && pattern.lineageIndex.length > 0) { + + //find all lineage - patterns being consumed by this one + for (var h = 0; h < pattern.lineageIndex.length; h++) { + var lineagePattern = pattern_assembler.get_pattern_by_key(pattern.lineageIndex[h], patternlab); + setPatternState('fromFuture', lineagePattern, pattern); + } + } + + if (pattern.lineageRIndex && pattern.lineageRIndex.length > 0) { + + //find all reverse lineage - that is, patterns consuming this one + for (var j = 0; j < pattern.lineageRIndex.length; j++) { + + var lineageRPattern = pattern_assembler.get_pattern_by_key(pattern.lineageRIndex[j], patternlab); + + //only set patternState if pattern.patternState "is less than" the lineageRPattern.patternstate + //this makes patternlab apply the lowest common ancestor denominator + if (patternlab.config.patternStateCascade.indexOf(pattern.patternState) + < patternlab.config.patternStateCascade.indexOf(lineageRPattern.patternState)) { + + if (patternlab.config.debug) { + console.log('Found a lower common denominator pattern state: ' + pattern.patternState + ' on ' + pattern.key + '. Setting reverse lineage pattern ' + lineageRPattern.key + ' from ' + lineageRPattern.patternState); + } + + lineageRPattern.patternState = pattern.patternState; + + //take this opportunity to overwrite the lineageRPattern's lineage state too + setPatternState('fromPast', lineageRPattern, pattern); + } else { + setPatternState('fromPast', pattern, lineageRPattern); + } + } + } + } + } + } + + return { + find_lineage: function (pattern, patternlab) { + findlineage(pattern, patternlab); + }, + cascade_pattern_states : function (patternlab) { + cascadePatternStates(patternlab); + } + }; + +}; + +module.exports = lineage_hunter; diff --git a/core/lib/list_item_hunter.js b/core/lib/list_item_hunter.js new file mode 100644 index 000000000..2b08ef2f1 --- /dev/null +++ b/core/lib/list_item_hunter.js @@ -0,0 +1,115 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var list_item_hunter = function () { + + var extend = require('util')._extend, + pa = require('./pattern_assembler'), + smh = require('./style_modifier_hunter'), + pattern_assembler = new pa(), + style_modifier_hunter = new smh(), + items = [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty']; + + function processListItemPartials(pattern, patternlab) { + //find any listitem blocks + var matches = pattern_assembler.find_list_items(pattern, patternlab); + if (matches !== null) { + matches.forEach(function (liMatch) { + + if (patternlab.config.debug) { + console.log('found listItem of size ' + liMatch + ' inside ' + pattern.key); + } + + //find the boundaries of the block + var loopNumberString = liMatch.split('.')[1].split('}')[0].trim(); + var end = liMatch.replace('#', '/'); + var patternBlock = pattern.template.substring(pattern.template.indexOf(liMatch) + liMatch.length, pattern.template.indexOf(end)).trim(); + + //build arrays that repeat the block, however large we need to + var repeatedBlockTemplate = []; + var repeatedBlockHtml = ''; + var i; // for loops + + for (i = 0; i < items.indexOf(loopNumberString); i++) { + repeatedBlockTemplate.push(patternBlock); + } + + //check for a local listitems.json file + var listData = JSON.parse(JSON.stringify(patternlab.listitems)); + listData = pattern_assembler.merge_data(listData, pattern.listitems); + + //iterate over each copied block, rendering its contents along with pattenlab.listitems[i] + for (i = 0; i < repeatedBlockTemplate.length; i++) { + var thisBlockTemplate = repeatedBlockTemplate[i]; + var thisBlockHTML = ""; + + //combine listItem data with pattern data with global data + var itemData = listData['' + items.indexOf(loopNumberString)]; //this is a property like "2" + var globalData = JSON.parse(JSON.stringify(patternlab.data)); + var localData = JSON.parse(JSON.stringify(pattern.jsonFileData)); + + var allData = pattern_assembler.merge_data(globalData, localData); + allData = pattern_assembler.merge_data(allData, itemData !== undefined ? itemData[i] : {}); //itemData could be undefined if the listblock contains no partial, just markup + allData.link = extend({}, patternlab.data.link); + + //check for partials within the repeated block + var foundPartials = pattern_assembler.find_pattern_partials({ 'template' : thisBlockTemplate }); + + if (foundPartials && foundPartials.length > 0) { + for (var j = 0; j < foundPartials.length; j++) { + //get the partial + var partialName = foundPartials[j].match(/([\w\-\.\/~]+)/g)[0]; + var partialPattern = pattern_assembler.get_pattern_by_key(partialName, patternlab); + + //create a copy of the partial so as to not pollute it after the get_pattern_by_key call. + var cleanPartialPattern = JSON.parse(JSON.stringify(partialPattern)); + + //if partial has style modifier data, replace the styleModifier value + if (foundPartials[j].indexOf(':') > -1) { + style_modifier_hunter.consume_style_modifier(cleanPartialPattern, foundPartials[j], patternlab); + } + + //replace its reference within the block with the extended template + thisBlockTemplate = thisBlockTemplate.replace(foundPartials[j], cleanPartialPattern.extendedTemplate); + } + + //render with data + thisBlockHTML = pattern_assembler.renderPattern(thisBlockTemplate, allData, patternlab.partials); + } else { + //just render with mergedData + thisBlockHTML = pattern_assembler.renderPattern(thisBlockTemplate, allData, patternlab.partials); + } + + //add the rendered HTML to our string + repeatedBlockHtml = repeatedBlockHtml + thisBlockHTML; + } + + //replace the block with our generated HTML + var repeatingBlock = pattern.extendedTemplate.substring(pattern.extendedTemplate.indexOf(liMatch), pattern.extendedTemplate.indexOf(end) + end.length); + pattern.extendedTemplate = pattern.extendedTemplate.replace(repeatingBlock, repeatedBlockHtml); + + //update the extendedTemplate in the partials object in case this pattern is consumed later + patternlab.partials[pattern.key] = pattern.extendedTemplate; + + }); + } + } + + return { + process_list_item_partials: function (pattern, patternlab) { + processListItemPartials(pattern, patternlab); + } + }; + +}; + +module.exports = list_item_hunter; diff --git a/core/lib/media_hunter.js b/core/lib/media_hunter.js new file mode 100644 index 000000000..62a02ad5a --- /dev/null +++ b/core/lib/media_hunter.js @@ -0,0 +1,51 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var diveSync = require('diveSync'), + path = require('path'), + fs = require('fs-extra'); + +var media_hunter = function () { + + function findMediaQueries(dir, patternlab) { + patternlab.mediaQueries = []; + + diveSync(dir, function (err, file) { + if (path.extname(file) === '.css') { + var contents = fs.readFileSync(file, 'utf8'); + var safeContents = contents.replace("\r", " ").replace("\n", " "); + var matches = safeContents.match(/\((min|max)-width:([ ]+)?(([0-9]{1,5})(\.[0-9]{1,20}|)(px|em))/g); + for (var i = 0; i < matches.length; i++) { + var breakpoint = matches[i].substring(matches[i].indexOf(':') + 1).trimLeft(); + if (patternlab.mediaQueries.indexOf(breakpoint) === -1) { + patternlab.mediaQueries.push(breakpoint); + } + } + } + }); + patternlab.mediaQueries.sort(function (a, b) { + a.match(/(?:\d*\.)?\d+/g); + b.match(/(?:\d*\.)?\d+/g); + return parseInt(a, 10) > parseInt(b, 10); + }); + } + + return { + find_media_queries: function (dir, patternlab) { + findMediaQueries(dir, patternlab); + } + }; + +}; + +module.exports = media_hunter; + diff --git a/core/lib/object_factory.js b/core/lib/object_factory.js new file mode 100644 index 000000000..28a4af057 --- /dev/null +++ b/core/lib/object_factory.js @@ -0,0 +1,70 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var oPattern = function (abspath, subdir, filename, data) { + this.fileName = filename.substring(0, filename.indexOf('.')); + this.abspath = abspath; + this.subdir = subdir; + this.name = subdir.replace(/[\/\\]/g, '-') + '-' + this.fileName; //this is the unique name with the subDir + this.jsonFileData = data || {}; + this.patternName = this.fileName.replace(/^\d*\-/, ''); + this.patternDisplayName = this.patternName.split('-').reduce(function (val, working) { + return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1); + }, '').trim(); //this is the display name for the ui. strip numeric + hyphen prefixes + this.patternLink = this.name + '/' + this.name + '.html'; + this.patternGroup = this.name.substring(this.name.indexOf('-') + 1, this.name.indexOf('-', 4) + 1 - this.name.indexOf('-') + 1); + this.patternSubGroup = subdir.substring(subdir.indexOf('/') + 4); + this.flatPatternPath = subdir.replace(/[\/\\]/g, '-'); + this.key = this.patternGroup + '-' + this.patternName; + this.template = ''; + this.patternPartial = ''; + this.lineage = []; + this.lineageIndex = []; + this.lineageR = []; + this.lineageRIndex = []; +}; + +var oBucket = function (name) { + this.bucketNameLC = name; + this.bucketNameUC = name.split('-').reduce(function (val, working) { + return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1); + }, '').trim(); + this.navItems = []; + this.navItemsIndex = []; + this.patternItems = []; + this.patternItemsIndex = []; +}; + +var oNavItem = function (name) { + this.sectionNameLC = name; + this.sectionNameUC = name.split('-').reduce(function (val, working) { + return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1); + }, '').trim(); + this.navSubItems = []; + this.navSubItemsIndex = []; +}; + +var oNavSubItem = function (name) { + this.patternPath = ''; + this.patternPartial = ''; + this.patternName = name.split(' ').reduce(function (val, working) { + return val.charAt(0).toUpperCase() + val.slice(1) + ' ' + working.charAt(0).toUpperCase() + working.slice(1); + }, '').trim(); +}; + +module.exports = { + oPattern: oPattern, + oBucket: oBucket, + oNavItem: oNavItem, + oNavSubItem: oNavSubItem +}; + diff --git a/core/lib/parameter_hunter.js b/core/lib/parameter_hunter.js new file mode 100644 index 000000000..9567f2a46 --- /dev/null +++ b/core/lib/parameter_hunter.js @@ -0,0 +1,189 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var parameter_hunter = function () { + + var extend = require('util')._extend, + pa = require('./pattern_assembler'), + smh = require('./style_modifier_hunter'), + style_modifier_hunter = new smh(), + pattern_assembler = new pa(); + + function paramToJson(pString) { + var paramStringWellFormed = ''; + var paramStringTmp; + var colonPos; + var delimitPos; + var quotePos; + var paramString = pString; + + do { + + //if param key is wrapped in single quotes, replace with double quotes. + paramString = paramString.replace(/(^\s*[\{|\,]\s*)'([^']+)'(\s*\:)/, '$1"$2"$3'); + + //if params key is not wrapped in any quotes, wrap in double quotes. + paramString = paramString.replace(/(^\s*[\{|\,]\s*)([^\s"'\:]+)(\s*\:)/, '$1"$2"$3'); + + //move param key to paramStringWellFormed var. + colonPos = paramString.indexOf(':'); + + //except to prevent infinite loops. + if (colonPos === -1) { + colonPos = paramString.length - 1; + } + else { + colonPos += 1; + } + paramStringWellFormed += paramString.substring(0, colonPos); + paramString = paramString.substring(colonPos, paramString.length).trim(); + + //if param value is wrapped in single quotes, replace with double quotes. + if (paramString[0] === '\'') { + quotePos = paramString.search(/[^\\]'/); + + //except for unclosed quotes to prevent infinite loops. + if (quotePos === -1) { + quotePos = paramString.length - 1; + } + else { + quotePos += 2; + } + + //prepare param value for move to paramStringWellFormed var. + paramStringTmp = paramString.substring(0, quotePos); + + //unescape any escaped single quotes. + paramStringTmp = paramStringTmp.replace(/\\'/g, '\''); + + //escape any double quotes. + paramStringTmp = paramStringTmp.replace(/"/g, '\\"'); + + //replace the delimiting single quotes with double quotes. + paramStringTmp = paramStringTmp.replace(/^'/, '"'); + paramStringTmp = paramStringTmp.replace(/'$/, '"'); + + //move param key to paramStringWellFormed var. + paramStringWellFormed += paramStringTmp; + paramString = paramString.substring(quotePos, paramString.length).trim(); + } + + //if param value is wrapped in double quotes, just move to paramStringWellFormed var. + else if (paramString[0] === '"') { + quotePos = paramString.search(/[^\\]"/); + + //except for unclosed quotes to prevent infinite loops. + if (quotePos === -1) { + quotePos = paramString.length - 1; + } + else { + quotePos += 2; + } + + //move param key to paramStringWellFormed var. + paramStringWellFormed += paramString.substring(0, quotePos); + paramString = paramString.substring(quotePos, paramString.length).trim(); + } + + //if param value is not wrapped in quotes, move everthing up to the delimiting comma to paramStringWellFormed var. + else { + delimitPos = paramString.indexOf(','); + + //except to prevent infinite loops. + if (delimitPos === -1) { + delimitPos = paramString.length - 1; + } + else { + delimitPos += 1; + } + paramStringWellFormed += paramString.substring(0, delimitPos); + paramString = paramString.substring(delimitPos, paramString.length).trim(); + } + + //break at the end. + if (paramString.length === 1) { + paramStringWellFormed += paramString.trim(); + paramString = ''; + break; + } + + } while (paramString); + + return paramStringWellFormed; + } + + function findparameters(pattern, patternlab) { + + if (pattern.parameteredPartials && pattern.parameteredPartials.length > 0) { + + //compile this partial immeadiately, essentially consuming it. + pattern.parameteredPartials.forEach(function (pMatch) { + //find the partial's name and retrieve it + var partialName = pMatch.match(/([\w\-\.\/~]+)/g)[0]; + var partialPattern = pattern_assembler.get_pattern_by_key(partialName, patternlab); + + //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #190 + partialPattern.extendedTemplate = partialPattern.template; + + if (patternlab.config.debug) { + console.log('found patternParameters for ' + partialName); + } + + //strip out the additional data, convert string to JSON. + var leftParen = pMatch.indexOf('('); + var rightParen = pMatch.indexOf(')'); + var paramString = '{' + pMatch.substring(leftParen + 1, rightParen) + '}'; + var paramStringWellFormed = paramToJson(paramString); + + var paramData = {}; + var globalData = {}; + var localData = {}; + + try { + paramData = JSON.parse(paramStringWellFormed); + globalData = JSON.parse(JSON.stringify(patternlab.data)); + localData = JSON.parse(JSON.stringify(pattern.jsonFileData || {})); + } catch (e) { + console.log(e); + } + + var allData = pattern_assembler.merge_data(globalData, localData); + allData = pattern_assembler.merge_data(allData, paramData); + + //if partial has style modifier data, replace the styleModifier value + if (pattern.stylePartials && pattern.stylePartials.length > 0) { + style_modifier_hunter.consume_style_modifier(partialPattern, pMatch, patternlab); + } + + //extend pattern data links into link for pattern link shortcuts to work. we do this locally and globally + allData.link = extend({}, patternlab.data.link); + + var renderedPartial = pattern_assembler.renderPattern(partialPattern.extendedTemplate, allData, patternlab.partials); + + //remove the parameter from the partial and replace it with the rendered partial + paramData + pattern.extendedTemplate = pattern.extendedTemplate.replace(pMatch, renderedPartial); + + //update the extendedTemplate in the partials object in case this pattern is consumed later + patternlab.partials[pattern.key] = pattern.extendedTemplate; + }); + } + } + + return { + find_parameters: function (pattern, patternlab) { + findparameters(pattern, patternlab); + } + }; + +}; + +module.exports = parameter_hunter; diff --git a/core/lib/pattern_assembler.js b/core/lib/pattern_assembler.js new file mode 100644 index 000000000..869b1986c --- /dev/null +++ b/core/lib/pattern_assembler.js @@ -0,0 +1,414 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var pattern_assembler = function () { + + // returns any patterns that match {{> value:mod }} or {{> value:mod(foo:"bar") }} within the pattern + function findPartialsWithStyleModifiers(pattern) { + var matches = pattern.template.match(/{{>([ ])?([\w\-\.\/~]+)(?!\()(\:[A-Za-z0-9-_|]+)+(?:(| )\(.*)?([ ])?}}/g); + return matches; + } + + // returns any patterns that match {{> value(foo:"bar") }} or {{> value:mod(foo:"bar") }} within the pattern + function findPartialsWithPatternParameters(pattern) { + var matches = pattern.template.match(/{{>([ ])?([\w\-\.\/~]+)(?:\:[A-Za-z0-9-_|]+)?(?:(| )\(.*)+([ ])?}}/g); + return matches; + } + + //find and return any {{> template-name* }} within pattern + function findPartials(pattern) { + var matches = pattern.template.match(/{{>([ ])?([\w\-\.\/~]+)(?:\:[A-Za-z0-9-_|]+)?(?:(| )\(.*)?([ ])?}}/g); + return matches; + } + + function findListItems(pattern) { + var matches = pattern.template.match(/({{#( )?)(list(I|i)tems.)(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)( )?}}/g); + return matches; + } + + function setState(pattern, patternlab) { + if (patternlab.config.patternStates && patternlab.config.patternStates[pattern.key]) { + pattern.patternState = patternlab.config.patternStates[pattern.key]; + } else { + pattern.patternState = ""; + } + } + + function addPattern(pattern, patternlab) { + //add the link to the global object + patternlab.data.link[pattern.patternGroup + '-' + pattern.patternName] = '/patterns/' + pattern.patternLink; + + //only push to array if the array doesn't contain this pattern + var isNew = true, i; + for (i = 0; i < patternlab.patterns.length; i++) { + //so we need the identifier to be unique, which patterns[i].abspath is + if (pattern.abspath === patternlab.patterns[i].abspath) { + //if abspath already exists, overwrite that element + patternlab.patterns[i] = pattern; + patternlab.partials[pattern.key] = pattern.extendedTemplate || pattern.template; + isNew = false; + break; + } + } + + //if the pattern is new, just push to the array + if (isNew) { + patternlab.patterns.push(pattern); + patternlab.partials[pattern.key] = pattern.extendedTemplate || pattern.template; + } + } + + function renderPattern(template, data, partials) { + var mustache = require('mustache'); + + if (partials) { + return mustache.render(template, data, partials); + } else { + return mustache.render(template, data); + } + } + + //http://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript + function shuffle(o) { + for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); //eslint-disable-line curly + return o; + } + + function buildListItems(container) { + //combine all list items into one structure + var list = []; + for (var item in container.listitems) { + if (container.listitems.hasOwnProperty(item)) { + list.push(container.listitems[item]); + } + } + container.listItemArray = shuffle(list); + + for (var i = 1; i <= container.listItemArray.length; i++) { + var tempItems = []; + if (i === 1) { + tempItems.push(container.listItemArray[0]); + container.listitems['' + i] = tempItems; + } else { + for (var c = 1; c <= i; c++) { + tempItems.push(container.listItemArray[c - 1]); + container.listitems['' + i] = tempItems; + } + } + } + } + + function getpatternbykey(key, patternlab) { + var i; // for the for loops + + //look for exact key matches + for (i = 0; i < patternlab.patterns.length; i++) { + if (patternlab.patterns[i].key === key) { + return patternlab.patterns[i]; + } + } + + //else look by verbose syntax + for (i = 0; i < patternlab.patterns.length; i++) { + switch (key) { + case patternlab.patterns[i].subdir + '/' + patternlab.patterns[i].fileName: + case patternlab.patterns[i].subdir + '/' + patternlab.patterns[i].fileName + '.mustache': + return patternlab.patterns[i]; + } + } + + //return the fuzzy match if all else fails + for (i = 0; i < patternlab.patterns.length; i++) { + var keyParts = key.split('-'), + keyType = keyParts[0], + keyName = keyParts.slice(1).join('-'); + + if (patternlab.patterns[i].key.split('-')[0] === keyType && patternlab.patterns[i].key.indexOf(keyName) > -1) { + return patternlab.patterns[i]; + } + } + throw 'Could not find pattern with key ' + key; + } + + function processPatternIterative(file, patternlab) { + var fs = require('fs-extra'), + of = require('./object_factory'), + path = require('path'); + + //extract some information + var subdir = path.dirname(path.relative(patternlab.config.paths.source.patterns, file)).replace('\\', '/'); + var filename = path.basename(file); + var ext = path.extname(filename); + + //ignore dotfiles, underscored files, and non-variant .json files + if (filename.charAt(0) === '.' || (ext === '.json' && filename.indexOf('~') === -1)) { + return; + } + + //make a new Pattern Object + var currentPattern = new of.oPattern(file, subdir, filename); + + //if file is named in the syntax for variants, no need to process further + //processPatternRecursive() will run find_pseudopatterns() and look at each pattern for a variant + if (ext === '.json' && filename.indexOf('~') > -1) { + return; + } + + //can ignore all non-mustache files at this point + if (ext !== '.mustache') { + return; + } + + //see if this file has a state + setState(currentPattern, patternlab); + + //look for a json file for this template + try { + var jsonFilename = path.resolve(patternlab.config.paths.source.patterns, currentPattern.subdir, currentPattern.fileName + ".json"); + currentPattern.jsonFileData = fs.readJSONSync(jsonFilename); + if (patternlab.config.debug) { + console.log('found pattern-specific data.json for ' + currentPattern.key); + } + } + catch (error) { + // do nothing + } + + //look for a listitems.json file for this template + try { + var listJsonFileName = path.resolve(patternlab.config.paths.source.patterns, currentPattern.subdir, currentPattern.fileName + ".listitems.json"); + currentPattern.listitems = fs.readJSONSync(listJsonFileName); + buildListItems(currentPattern); + if (patternlab.config.debug) { + console.log('found pattern-specific listitems.json for ' + currentPattern.key); + } + } + catch (err) { + // do nothing + } + + //add the raw template to memory + currentPattern.template = fs.readFileSync(file, 'utf8'); + + //find any stylemodifiers that may be in the current pattern + currentPattern.stylePartials = findPartialsWithStyleModifiers(currentPattern); + + //find any pattern parameters that may be in the current pattern + currentPattern.parameteredPartials = findPartialsWithPatternParameters(currentPattern); + + //add currentPattern to patternlab.patterns array + addPattern(currentPattern, patternlab); + } + + function processPatternRecursive(file, patternlab) { + var lh = require('./lineage_hunter'), + ph = require('./parameter_hunter'), + pph = require('./pseudopattern_hunter'), + lih = require('./list_item_hunter'), + smh = require('./style_modifier_hunter'); + + var parameter_hunter = new ph(), + lineage_hunter = new lh(), + list_item_hunter = new lih(), + style_modifier_hunter = new smh(), + pseudopattern_hunter = new pph(); + + //find current pattern in patternlab object using var file as a key + var currentPattern, + i; + + for (i = 0; i < patternlab.patterns.length; i++) { + if (patternlab.patterns[i].abspath === file) { + currentPattern = patternlab.patterns[i]; + break; + } + } + + //return if processing an ignored file + if (typeof currentPattern === 'undefined') { + return; + } + + currentPattern.extendedTemplate = currentPattern.template; + + //find how many partials there may be for the given pattern + var foundPatternPartials = findPartials(currentPattern); + + if (foundPatternPartials !== null && foundPatternPartials.length > 0) { + + if (patternlab.config.debug) { + console.log('found partials for ' + currentPattern.key); + } + + //find any listItem blocks + list_item_hunter.process_list_item_partials(currentPattern, patternlab); + + //determine if the template contains any pattern parameters. if so they must be immediately consumed + parameter_hunter.find_parameters(currentPattern, patternlab); + + //do something with the regular old partials + for (i = 0; i < foundPatternPartials.length; i++) { + var partialKey = foundPatternPartials[i].replace(/{{>([ ])?([\w\-\.\/~]+)(:[A-z0-9-_|]+)?(?:\:[A-Za-z0-9-_]+)?(?:(| )\(.*)?([ ])?}}/g, '$2'); + + var partialPath; + + //identify which pattern this partial corresponds tou + for (var j = 0; j < patternlab.patterns.length; j++) { + if (patternlab.patterns[j].key === partialKey || + patternlab.patterns[j].abspath.indexOf(partialKey) > -1) { + partialPath = patternlab.patterns[j].abspath; + } + } + + //recurse through nested partials to fill out this extended template. + processPatternRecursive(partialPath, patternlab); + + //complete assembly of extended template + var partialPattern = getpatternbykey(partialKey, patternlab); + + //if partial has style modifier data, replace the styleModifier value + if (currentPattern.stylePartials && currentPattern.stylePartials.length > 0) { + style_modifier_hunter.consume_style_modifier(partialPattern, foundPatternPartials[i], patternlab); + } + + currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], partialPattern.extendedTemplate); + + //update the extendedTemplate in the partials object in case this pattern is consumed later + patternlab.partials[currentPattern.key] = currentPattern.extendedTemplate; + } + + } else { + //find any listItem blocks that within the pattern, even if there are no partials + list_item_hunter.process_list_item_partials(currentPattern, patternlab); + } + + //find pattern lineage + lineage_hunter.find_lineage(currentPattern, patternlab); + + //add to patternlab object so we can look these up later. + addPattern(currentPattern, patternlab); + + //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json + pseudopattern_hunter.find_pseudopatterns(currentPattern, patternlab); + } + + function mergeData(obj1, obj2) { + if (typeof obj2 === 'undefined') { + obj2 = {}; //eslint-disable-line no-param-reassign + } + + for (var p in obj1) { //eslint-disable-line guard-for-in + try { + // Only recurse if obj1[p] is an object. + if (obj1[p].constructor === Object) { + // Requires 2 objects as params; create obj2[p] if undefined. + if (typeof obj2[p] === 'undefined') { + obj2[p] = {}; + } + obj2[p] = mergeData(obj1[p], obj2[p]); + + // Pop when recursion meets a non-object. If obj1[p] is a non-object, + // only copy to undefined obj2[p]. This way, obj2 maintains priority. + } else if (typeof obj2[p] === 'undefined') { + obj2[p] = obj1[p]; + } + } catch (e) { + // Property in destination object not set; create it and set its value. + if (typeof obj2[p] === 'undefined') { + obj2[p] = obj1[p]; + } + } + } + return obj2; + } + + function parseDataLinksHelper(patternlab, obj, key) { + var linkRE, dataObjAsString, linkMatches, expandedLink; + + linkRE = /link\.[A-z0-9-_]+/g; + dataObjAsString = JSON.stringify(obj); + linkMatches = dataObjAsString.match(linkRE); + + if (linkMatches) { + for (var i = 0; i < linkMatches.length; i++) { + expandedLink = patternlab.data.link[linkMatches[i].split('.')[1]]; + if (expandedLink) { + if (patternlab.config.debug) { + console.log('expanded data link from ' + linkMatches[i] + ' to ' + expandedLink + ' inside ' + key); + } + dataObjAsString = dataObjAsString.replace(linkMatches[i], expandedLink); + } + } + } + return JSON.parse(dataObjAsString); + } + + //look for pattern links included in data files. + //these will be in the form of link.* WITHOUT {{}}, which would still be there from direct pattern inclusion + function parseDataLinks(patternlab) { + //look for link.* such as link.pages-blog as a value + + patternlab.data = parseDataLinksHelper(patternlab, patternlab.data, 'data.json'); + + //loop through all patterns + for (var i = 0; i < patternlab.patterns.length; i++) { + patternlab.patterns[i].jsonFileData = parseDataLinksHelper(patternlab, patternlab.patterns[i].jsonFileData, patternlab.patterns[i].key); + } + } + + return { + find_pattern_partials: function (pattern) { + return findPartials(pattern); + }, + find_pattern_partials_with_style_modifiers: function (pattern) { + return findPartialsWithStyleModifiers(pattern); + }, + find_pattern_partials_with_parameters: function (pattern) { + return findPartialsWithPatternParameters(pattern); + }, + find_list_items: function (pattern) { + return findListItems(pattern); + }, + setPatternState: function (pattern, patternlab) { + setState(pattern, patternlab); + }, + addPattern: function (pattern, patternlab) { + addPattern(pattern, patternlab); + }, + renderPattern: function (template, data, partials) { + return renderPattern(template, data, partials); + }, + process_pattern_iterative: function (file, patternlab) { + processPatternIterative(file, patternlab); + }, + process_pattern_recursive: function (file, patternlab, additionalData) { + processPatternRecursive(file, patternlab, additionalData); + }, + get_pattern_by_key: function (key, patternlab) { + return getpatternbykey(key, patternlab); + }, + merge_data: function (existingData, newData) { + return mergeData(existingData, newData); + }, + combine_listItems: function (patternlab) { + buildListItems(patternlab); + }, + parse_data_links: function (patternlab) { + parseDataLinks(patternlab); + } + }; + +}; + +module.exports = pattern_assembler; + + diff --git a/core/lib/pattern_exporter.js b/core/lib/pattern_exporter.js new file mode 100644 index 000000000..ff53cac6c --- /dev/null +++ b/core/lib/pattern_exporter.js @@ -0,0 +1,40 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var fs = require('fs-extra'); + +var pattern_exporter = function () { + + function exportPatterns(patternlab) { + //read the config export options + var exportKeys = patternlab.config.patternExportKeys; + + //find the chosen patterns to export + for (var i = 0; i < exportKeys.length; i++) { + for (var j = 0; j < patternlab.patterns.length; j++) { + if (exportKeys[i] === patternlab.patterns[j].key) { + //write matches to the desired location + fs.outputFileSync(patternlab.config.patternExportDirectory + patternlab.patterns[j].key + '.html', patternlab.patterns[j].patternPartial); + } + } + } + } + + return { + export_patterns: function (patternlab) { + exportPatterns(patternlab); + } + }; + +}; + +module.exports = pattern_exporter; diff --git a/builder/patternlab.js b/core/lib/patternlab.js similarity index 62% rename from builder/patternlab.js rename to core/lib/patternlab.js index aae791f23..cc910a5bc 100644 --- a/builder/patternlab.js +++ b/core/lib/patternlab.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v1.1.3 - 2016 + * patternlab-node - v1.2.0 - 2016 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. @@ -12,20 +12,18 @@ var patternlab_engine = function (config) { 'use strict'; var path = require('path'), - fs = require('fs-extra'), - extend = require('util')._extend, - diveSync = require('diveSync'), - mustache = require('mustache'), - glob = require('glob'), - of = require('./object_factory'), - pa = require('./pattern_assembler'), - mh = require('./media_hunter'), - pe = require('./pattern_exporter'), - he = require('html-entities').AllHtmlEntities, - patternlab = {}; + fs = require('fs-extra'), + diveSync = require('diveSync'), + of = require('./object_factory'), + pa = require('./pattern_assembler'), + mh = require('./media_hunter'), + pe = require('./pattern_exporter'), + lh = require('./lineage_hunter'), + he = require('html-entities').AllHtmlEntities, + patternlab = {}; patternlab.package = fs.readJSONSync('./package.json'); - patternlab.config = config || fs.readJSONSync(path.resolve(__dirname, '../config.json')); + patternlab.config = config || fs.readJSONSync(path.resolve(__dirname, '../../patternlab-config.json')); var paths = patternlab.config.paths; @@ -34,7 +32,7 @@ var patternlab_engine = function (config) { console.log(patternlab.package.version); } - function help(){ + function help() { console.log('Patternlab Node Help'); console.log('==============================='); console.log('Command Line Arguments'); @@ -50,95 +48,172 @@ var patternlab_engine = function (config) { } function printDebug() { - //debug file can be written by setting flag on config.json - if(patternlab.config.debug){ + //debug file can be written by setting flag on patternlab-config.json + if (patternlab.config.debug) { console.log('writing patternlab debug file to ./patternlab.json'); fs.outputFileSync('./patternlab.json', JSON.stringify(patternlab, null, 3)); } } - function buildPatterns(deletePatternDir){ + function setCacheBust() { + if (patternlab.config.cacheBust) { + if (patternlab.config.debug) { + console.log('setting cacheBuster value for frontend assets.'); + } + patternlab.cacheBuster = new Date().getTime(); + } else { + patternlab.cacheBuster = 0; + } + } + + function buildPatterns(deletePatternDir) { patternlab.data = fs.readJSONSync(path.resolve(paths.source.data, 'data.json')); patternlab.listitems = fs.readJSONSync(path.resolve(paths.source.data, 'listitems.json')); - patternlab.header = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'pattern-header-footer/header.html'), 'utf8'); - patternlab.footer = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'pattern-header-footer/footer.html'), 'utf8'); + patternlab.header = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/pattern-header-footer/header.html'), 'utf8'); + patternlab.footerPattern = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/pattern-header-footer/footer-pattern.html'), 'utf8'); + patternlab.footer = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/pattern-header-footer/footer.html'), 'utf8'); patternlab.patterns = []; patternlab.partials = {}; patternlab.data.link = {}; + setCacheBust(); + var pattern_assembler = new pa(), - entity_encoder = new he(), - pattern_exporter = new pe(), - patterns_dir = paths.source.patterns; + entity_encoder = new he(), + pattern_exporter = new pe(), + lineage_hunter = new lh(), + patterns_dir = paths.source.patterns; pattern_assembler.combine_listItems(patternlab); //diveSync once to perform iterative populating of patternlab object - diveSync(patterns_dir, { - filter: function(path, dir) { - if(dir){ - var remainingPath = path.replace(patterns_dir, ''); - var isValidPath = remainingPath.indexOf('/_') === -1; - return isValidPath; - } + diveSync( + patterns_dir, + { + filter: function (filePath, dir) { + if (dir) { + var remainingPath = filePath.replace(patterns_dir, ''); + var isValidPath = remainingPath.indexOf('/_') === -1; + return isValidPath; + } return true; } }, - function(err, file){ + function (err, file) { //log any errors - if(err){ + if (err) { console.log(err); return; } pattern_assembler.process_pattern_iterative(path.resolve(file), patternlab); - }); + } + ); //diveSync again to recursively include partials, filling out the //extendedTemplate property of the patternlab.patterns elements - diveSync(patterns_dir, { - filter: function(path, dir) { - if(dir){ - var remainingPath = path.replace(patterns_dir, ''); - var isValidPath = remainingPath.indexOf('/_') === -1; - return isValidPath; - } + diveSync( + patterns_dir, + { + filter: function (filePath, dir) { + if (dir) { + var remainingPath = filePath.replace(patterns_dir, ''); + var isValidPath = remainingPath.indexOf('/_') === -1; + return isValidPath; + } return true; } }, - function(err, file){ + function (err, file) { //log any errors - if(err){ + if (err) { console.log(err); return; } pattern_assembler.process_pattern_recursive(path.resolve(file), patternlab); }); + //set user defined head and foot if they exist + try { + patternlab.userHead = pattern_assembler.get_pattern_by_key('atoms-head', patternlab); + } + catch (ex) { + if (patternlab.config.debug) { + console.log(ex); + console.log('Could not find optional user-defined header, atoms-head pattern. It was likely deleted.'); + } + } + try { + patternlab.userFoot = pattern_assembler.get_pattern_by_key('atoms-foot', patternlab); + } + catch (ex) { + if (patternlab.config.debug) { + console.log(ex); + console.log('Could not find optional user-defined footer, atoms-foot pattern. It was likely deleted.'); + } + } //now that all the main patterns are known, look for any links that might be within data and expand them //we need to do this before expanding patterns & partials into extendedTemplates, otherwise we could lose the data -> partial reference pattern_assembler.parse_data_links(patternlab); + //cascade any patternStates + lineage_hunter.cascade_pattern_states(patternlab); + //delete the contents of config.patterns.public before writing - if(deletePatternDir){ + if (deletePatternDir) { fs.emptyDirSync(paths.public.patterns); } + //set pattern-specific header if necessary + var head; + if (patternlab.userHead) { + head = patternlab.userHead.extendedTemplate.replace('{% pattern-lab-head %}', patternlab.header); + } else { + head = patternlab.header; + } + //render all patterns last, so lineageR works - patternlab.patterns.forEach(function(pattern, index, patterns){ + patternlab.patterns.forEach(function (pattern) { + + pattern.header = head; + + //json stringify lineage and lineageR + var lineageArray = []; + for (var i = 0; i < pattern.lineage.length; i++) { + lineageArray.push(JSON.stringify(pattern.lineage[i])); + } + pattern.lineage = lineageArray; + + var lineageRArray = []; + for (var i = 0; i < pattern.lineageR.length; i++) { + lineageRArray.push(JSON.stringify(pattern.lineageR[i])); + } + pattern.lineageR = lineageRArray; //render the pattern, but first consolidate any data we may have - var allData = JSON.parse(JSON.stringify(patternlab.data)); + var allData = JSON.parse(JSON.stringify(patternlab.data)); allData = pattern_assembler.merge_data(allData, pattern.jsonFileData); + //also add the cachebuster value. slight chance this could collide with a user that has defined cacheBuster as a value + allData.cacheBuster = patternlab.cacheBuster; + pattern.cacheBuster = patternlab.cacheBuster; + + //render the pattern-specific header + var headHtml = pattern_assembler.renderPattern(pattern.header, allData); + //render the extendedTemplate with all data pattern.patternPartial = pattern_assembler.renderPattern(pattern.extendedTemplate, allData); - //add footer info before writing - var patternFooter = pattern_assembler.renderPattern(patternlab.footer, pattern); + //set the pattern-specific footer if necessary + if (patternlab.userFoot) { + var userFooter = patternlab.userFoot.extendedTemplate.replace('{% pattern-lab-foot %}', patternlab.footerPattern + patternlab.footer); + pattern.footer = pattern_assembler.renderPattern(userFooter, pattern); + } else { + pattern.footer = pattern_assembler.renderPattern(patternlab.footerPattern, pattern); + } //write the compiled template to the public patterns directory - fs.outputFileSync(paths.public.patterns + pattern.patternLink, patternlab.header + pattern.patternPartial + patternFooter); + fs.outputFileSync(paths.public.patterns + pattern.patternLink, headHtml + pattern.patternPartial + pattern.footer); //write the mustache file too fs.outputFileSync(paths.public.patterns + pattern.patternLink.replace('.html', '.mustache'), entity_encoder.encode(pattern.template)); @@ -149,27 +224,36 @@ var patternlab_engine = function (config) { //export patterns if necessary pattern_exporter.export_patterns(patternlab); + } + function addToPatternPaths(bucketName, pattern) { + //this is messy, could use a refactor. + patternlab.patternPaths[bucketName][pattern.patternName] = pattern.subdir.replace(/\\/g, '/') + "/" + pattern.fileName; } - function buildFrontEnd(){ + //todo: refactor this as a method on the pattern object itself once we merge dev with pattern-engines branch + function isPatternExcluded(pattern) { + // returns whether or not the first character of the pattern filename is an underscore, or excluded + return pattern.fileName.charAt(0) === '_'; + } + + function buildFrontEnd() { var pattern_assembler = new pa(), - media_hunter = new mh(), - styleGuideExcludes = patternlab.config.styleGuideExcludes, - styleguidePatterns = []; + media_hunter = new mh(), + styleGuideExcludes = patternlab.config.styleGuideExcludes, + styleguidePatterns = [], + i; // for loops + patternlab.buckets = []; patternlab.bucketIndex = []; patternlab.patternPaths = {}; patternlab.viewAllPaths = {}; //sort all patterns explicitly. - patternlab.patterns = patternlab.patterns.sort(function(a,b){ - if (a.name > b.name) { - return 1; - } - if (a.name < b.name) { - return -1; - } + patternlab.patterns = patternlab.patterns.sort(function (a, b) { + if (a.name > b.name) { return 1; } + if (a.name < b.name) { return -1; } + // a must be equal to b return 0; }); @@ -179,41 +263,50 @@ var patternlab_engine = function (config) { // check if patterns are excluded, if not add them to styleguidePatterns if (styleGuideExcludes && styleGuideExcludes.length) { - for (i = 0; i < patternlab.patterns.length; i++) { + for (i = 0; i < patternlab.patterns.length; i++) { - // skip underscore-prefixed files - if(isPatternExcluded(patternlab.patterns[i])){ - if(patternlab.config.debug){ - console.log('Omitting ' + patternlab.patterns[i].key + " from styleguide pattern exclusion."); - } - continue; + // skip underscore-prefixed files + if (isPatternExcluded(patternlab.patterns[i])) { + if (patternlab.config.debug) { + console.log('Omitting ' + patternlab.patterns[i].key + " from styleguide pattern exclusion."); } + continue; + } - var key = patternlab.patterns[i].key; - var typeKey = key.substring(0, key.indexOf('-')); - var isExcluded = (styleGuideExcludes.indexOf(typeKey) > -1); - if (!isExcluded) { - styleguidePatterns.push(patternlab.patterns[i]); - } + var key = patternlab.patterns[i].key; + var typeKey = key.substring(0, key.indexOf('-')); + var isExcluded = (styleGuideExcludes.indexOf(typeKey) > -1); + if (!isExcluded) { + styleguidePatterns.push(patternlab.patterns[i]); } + } } else { styleguidePatterns = patternlab.patterns; } + //also add the cachebuster value. slight chance this could collide with a user that has defined cacheBuster as a value + patternlab.data.cacheBuster = patternlab.cacheBuster; + + //get the main page head and foot + var mainPageHead = patternlab.userHead.extendedTemplate.replace('{% pattern-lab-head %}', patternlab.header); + var mainPageHeadHtml = pattern_assembler.renderPattern(mainPageHead, patternlab.data); + var mainPageFoot = patternlab.userFoot.extendedTemplate.replace('{% pattern-lab-foot %}', patternlab.footer); + var mainPageFootHtml = pattern_assembler.renderPattern(mainPageFoot, patternlab.data); + //build the styleguide - var styleguideTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'styleguide.mustache'), 'utf8'), - styleguideHtml = pattern_assembler.renderPattern(styleguideTemplate, {partials: styleguidePatterns}); - fs.outputFileSync(path.resolve(paths.public.styleguide, 'html/styleguide.html'), styleguideHtml); + var styleguideTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/styleguide.mustache'), 'utf8'), + styleguideHtml = pattern_assembler.renderPattern(styleguideTemplate, {partials: styleguidePatterns, cacheBuster: patternlab.cacheBuster}); + + fs.outputFileSync(path.resolve(paths.public.styleguide, 'html/styleguide.html'), mainPageHeadHtml + styleguideHtml + mainPageFootHtml); //build the viewall pages var prevSubdir = '', - prevGroup = '', - i; + prevGroup = ''; for (i = 0; i < patternlab.patterns.length; i++) { // skip underscore-prefixed files - if(isPatternExcluded(patternlab.patterns[i])){ - if(patternlab.config.debug){ + if (isPatternExcluded(patternlab.patterns[i])) { + if (patternlab.config.debug) { console.log('Omitting ' + patternlab.patterns[i].key + " from view all rendering."); } continue; @@ -223,18 +316,18 @@ var patternlab_engine = function (config) { //create the view all for the section // check if the current section is different from the previous one - if (pattern.patternGroup != prevGroup){ + if (pattern.patternGroup !== prevGroup) { prevGroup = pattern.patternGroup; var viewAllPatterns = [], - patternPartial = "viewall-" + pattern.patternGroup, - j; + patternPartial = "viewall-" + pattern.patternGroup, + j; for (j = 0; j < patternlab.patterns.length; j++) { if (patternlab.patterns[j].patternGroup === pattern.patternGroup) { //again, skip any sibling patterns to the current one that may have underscores - if(isPatternExcluded(patternlab.patterns[j])){ - if(patternlab.config.debug){ + if (isPatternExcluded(patternlab.patterns[j])) { + if (patternlab.config.debug) { console.log('Omitting ' + patternlab.patterns[j].key + " from view all sibling rendering."); } continue; @@ -244,56 +337,55 @@ var patternlab_engine = function (config) { } } - var viewAllTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'viewall.mustache'), 'utf8'); - var viewAllHtml = pattern_assembler.renderPattern(viewAllTemplate, {partials: viewAllPatterns, patternPartial: patternPartial}); - fs.outputFileSync(paths.public.patterns + pattern.subdir.slice(0, pattern.subdir.indexOf(pattern.patternGroup) + pattern.patternGroup.length) + '/index.html', viewAllHtml); + var viewAllTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/viewall.mustache'), 'utf8'); + var viewAllHtml = pattern_assembler.renderPattern(viewAllTemplate, {partials: viewAllPatterns, patternPartial: patternPartial, cacheBuster: patternlab.cacheBuster }); + fs.outputFileSync(paths.public.patterns + pattern.subdir.slice(0, pattern.subdir.indexOf(pattern.patternGroup) + pattern.patternGroup.length) + '/index.html', mainPageHead + viewAllHtml + mainPageFoot); } - //create the view all for the subsection + // create the view all for the subsection // check if the current sub section is different from the previous one if (pattern.subdir !== prevSubdir) { prevSubdir = pattern.subdir; var viewAllPatterns = [], - patternPartial = "viewall-" + pattern.patternGroup + "-" + pattern.patternSubGroup, - j; + patternPartial = "viewall-" + pattern.patternGroup + "-" + pattern.patternSubGroup, + j; for (j = 0; j < patternlab.patterns.length; j++) { if (patternlab.patterns[j].subdir === pattern.subdir) { //again, skip any sibling patterns to the current one that may have underscores - if(isPatternExcluded(patternlab.patterns[j])){ - if(patternlab.config.debug){ + if (isPatternExcluded(patternlab.patterns[j])) { + if (patternlab.config.debug) { console.log('Omitting ' + patternlab.patterns[j].key + " from view all sibling rendering."); } continue; } - viewAllPatterns.push(patternlab.patterns[j]); } } - var viewAllTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'viewall.mustache'), 'utf8'); - var viewAllHtml = pattern_assembler.renderPattern(viewAllTemplate, {partials: viewAllPatterns, patternPartial: patternPartial}); - fs.outputFileSync(paths.public.patterns + pattern.flatPatternPath + '/index.html', viewAllHtml); + var viewAllTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/viewall.mustache'), 'utf8'); + var viewAllHtml = pattern_assembler.renderPattern(viewAllTemplate, {partials: viewAllPatterns, patternPartial: patternPartial, cacheBuster: patternlab.cacheBuster}); + fs.outputFileSync(paths.public.patterns + pattern.flatPatternPath + '/index.html', mainPageHeadHtml + viewAllHtml + mainPageFootHtml); } } //build the patternlab website - var patternlabSiteTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'index.mustache'), 'utf8'); + var patternlabSiteTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/index.mustache'), 'utf8'); //loop through all patterns.to build the navigation //todo: refactor this someday - for(var i = 0; i < patternlab.patterns.length; i++){ + for (i = 0; i < patternlab.patterns.length; i++) { var pattern = patternlab.patterns[i]; var bucketName = pattern.name.replace(/\\/g, '-').split('-')[1]; //check if the bucket already exists var bucketIndex = patternlab.bucketIndex.indexOf(bucketName); - if(bucketIndex === -1){ + if (bucketIndex === -1) { // skip underscore-prefixed files. don't create a bucket on account of an underscored pattern - if(isPatternExcluded(pattern)){ + if (isPatternExcluded(pattern)) { continue; } @@ -313,7 +405,7 @@ var patternlab_engine = function (config) { //test whether the pattern struture is flat or not - usually due to a template or page var flatPatternItem = false; - if(navItemName === bucketName){ + if (navItemName === bucketName) { flatPatternItem = true; } @@ -326,20 +418,18 @@ var patternlab_engine = function (config) { navSubItem.patternPartial = bucketName + "-" + pattern.patternName; //add the hyphenated name //add the patternState if it exists - if(pattern.patternState){ + if (pattern.patternState) { navSubItem.patternState = pattern.patternState; } //if it is flat - we should not add the pattern to patternPaths - if(flatPatternItem){ - + if (flatPatternItem) { bucket.patternItems.push(navSubItem); //add to patternPaths addToPatternPaths(bucketName, pattern); - } else{ - + } else { bucket.navItems.push(navItem); bucket.navItemsIndex.push(navItemName); navItem.navSubItems.push(navSubItem); @@ -354,8 +444,7 @@ var patternlab_engine = function (config) { navViewAllItem.patternPartial = "viewall-" + pattern.patternGroup; bucket.patternItems.push(navViewAllItem); - patternlab.viewAllPaths[bucketName]['viewall'] = pattern.subdir.slice(0, pattern.subdir.indexOf(pattern.patternGroup) + pattern.patternGroup.length); - + patternlab.viewAllPaths[bucketName].viewall = pattern.subdir.slice(0, pattern.subdir.indexOf(pattern.patternGroup) + pattern.patternGroup.length); } //add the bucket. @@ -364,7 +453,7 @@ var patternlab_engine = function (config) { //done - } else{ + } else { //find the bucket var bucket = patternlab.buckets[bucketIndex]; @@ -383,21 +472,21 @@ var patternlab_engine = function (config) { navSubItem.patternPartial = bucketName + "-" + pattern.patternName; //add the hyphenated name //add the patternState if it exists - if(pattern.patternState){ + if (pattern.patternState) { navSubItem.patternState = pattern.patternState; } //test whether the pattern struture is flat or not - usually due to a template or page var flatPatternItem = false; - if(navItemName === bucketName){ + if (navItemName === bucketName) { flatPatternItem = true; } //if it is flat - we should not add the pattern to patternPaths - if(flatPatternItem){ + if (flatPatternItem) { // skip underscore-prefixed files - if(isPatternExcluded(pattern)){ + if (isPatternExcluded(pattern)) { continue; } @@ -407,14 +496,13 @@ var patternlab_engine = function (config) { //add to patternPaths addToPatternPaths(bucketName, pattern); - } else{ + } else { // only do this if pattern is included - if(!isPatternExcluded(pattern)){ + if (!isPatternExcluded(pattern)) { //check to see if navItem exists var navItemIndex = bucket.navItemsIndex.indexOf(navItemName); - if(navItemIndex === -1){ - + if (navItemIndex === -1) { var navItem = new of.oNavItem(navItemName); //add the navItem and navSubItem @@ -423,7 +511,7 @@ var patternlab_engine = function (config) { bucket.navItems.push(navItem); bucket.navItemsIndex.push(navItemName); - } else{ + } else { //add the navSubItem var navItem = bucket.navItems[navItemIndex]; navItem.navSubItems.push(navSubItem); @@ -456,24 +544,27 @@ var patternlab_engine = function (config) { //the patternlab site requires a lot of partials to be rendered. //patternNav - var patternNavTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials/patternNav.mustache'), 'utf8'); + var patternNavTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/partials/patternNav.mustache'), 'utf8'); var patternNavPartialHtml = pattern_assembler.renderPattern(patternNavTemplate, patternlab); //ishControls - var ishControlsTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials/ishControls.mustache'), 'utf8'); + var ishControlsTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/partials/ishControls.mustache'), 'utf8'); patternlab.config.mqs = patternlab.mediaQueries; var ishControlsPartialHtml = pattern_assembler.renderPattern(ishControlsTemplate, patternlab.config); //patternPaths - var patternPathsTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials/patternPaths.mustache'), 'utf8'); + var patternPathsTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/partials/patternPaths.mustache'), 'utf8'); var patternPathsPartialHtml = pattern_assembler.renderPattern(patternPathsTemplate, {'patternPaths': JSON.stringify(patternlab.patternPaths)}); //viewAllPaths - var viewAllPathsTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials/viewAllPaths.mustache'), 'utf8'); + var viewAllPathsTemplate = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'templates/partials/viewAllPaths.mustache'), 'utf8'); var viewAllPathsPartialHtml = pattern_assembler.renderPattern(viewAllPathsTemplate, {'viewallpaths': JSON.stringify(patternlab.viewAllPaths)}); //render the patternlab template, with all partials - var patternlabSiteHtml = pattern_assembler.renderPattern(patternlabSiteTemplate, {}, { + var patternlabSiteHtml = pattern_assembler.renderPattern(patternlabSiteTemplate, { + defaultPattern: patternlab.config.defaultPattern || 'all', + cacheBuster: patternlab.cacheBuster + }, { 'ishControls': ishControlsPartialHtml, 'patternNav': patternNavPartialHtml, 'patternPaths': patternPathsPartialHtml, @@ -482,30 +573,19 @@ var patternlab_engine = function (config) { fs.outputFileSync(path.resolve(paths.public.root, 'index.html'), patternlabSiteHtml); } - function addToPatternPaths(bucketName, pattern){ - //this is messy, could use a refactor. - patternlab.patternPaths[bucketName][pattern.patternName] = pattern.subdir.replace(/\\/g, '/') + "/" + pattern.fileName; - } - - //todo: refactor this as a method on the pattern object itself once we merge dev with pattern-engines branch - function isPatternExcluded(pattern){ - // returns whether or not the first character of the pattern filename is an underscore, or excluded - return pattern.fileName.charAt(0) === '_'; - } - return { - version: function(){ + version: function () { return getVersion(); }, - build: function(deletePatternDir){ + build: function (deletePatternDir) { buildPatterns(deletePatternDir); buildFrontEnd(); printDebug(); }, - help: function(){ + help: function () { help(); }, - build_patterns_only: function(deletePatternDir){ + build_patterns_only: function (deletePatternDir) { buildPatterns(deletePatternDir); printDebug(); } diff --git a/core/lib/patternlab_grunt.js b/core/lib/patternlab_grunt.js new file mode 100644 index 000000000..d124a7e0e --- /dev/null +++ b/core/lib/patternlab_grunt.js @@ -0,0 +1,38 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +var patternlab_engine = require('./patternlab.js'); + +module.exports = function (grunt) { + grunt.registerTask('patternlab', 'create design systems with atomic design', function (arg) { + var patternlab = patternlab_engine(); + + if (arguments.length === 0) { + patternlab.build(true); + } + + if (arg && arg === 'v') { + patternlab.version(); + } + + if (arg && arg === "only_patterns") { + patternlab.build_patterns_only(true); + } + + if (arg && arg === "help") { + patternlab.help(); + } + + if (arg && (arg !== "v" && arg !== "only_patterns" && arg !== "help")) { + patternlab.help(); + } + }); + +}; diff --git a/builder/patternlab_gulp.js b/core/lib/patternlab_gulp.js similarity index 67% rename from builder/patternlab_gulp.js rename to core/lib/patternlab_gulp.js index e036b1bb5..be02179cf 100644 --- a/builder/patternlab_gulp.js +++ b/core/lib/patternlab_gulp.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v1.1.3 - 2016 + * patternlab-node - v1.2.0 - 2016 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. @@ -10,27 +10,26 @@ var patternlab_engine = require('./patternlab.js'); -module.exports = function(gulp) { +module.exports = function (gulp) { - gulp.task('patternlab', ['clean'], function(cb){ + gulp.task('patternlab', ['clean'], function (cb) { var patternlab = patternlab_engine(); patternlab.build(false); cb(); }); - gulp.task('patternlab:version', function(){ + gulp.task('patternlab:version', function () { var patternlab = patternlab_engine(); patternlab.version(); }); - gulp.task('patternlab:only_patterns', ['clean'], function(){ + gulp.task('patternlab:only_patterns', ['clean'], function () { var patternlab = patternlab_engine(); patternlab.build_patterns_only(false); }); - gulp.task('patternlab:help', function(){ + gulp.task('patternlab:help', function () { var patternlab = patternlab_engine(); patternlab.help(); }); - }; diff --git a/core/lib/pseudopattern_hunter.js b/core/lib/pseudopattern_hunter.js new file mode 100644 index 000000000..053e520c3 --- /dev/null +++ b/core/lib/pseudopattern_hunter.js @@ -0,0 +1,76 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var pseudopattern_hunter = function () { + + function findpseudopatterns(currentPattern, patternlab) { + var glob = require('glob'), + fs = require('fs-extra'), + pa = require('./pattern_assembler'), + lh = require('./lineage_hunter'), + of = require('./object_factory'), + path = require('path'); + + var pattern_assembler = new pa(); + var lineage_hunter = new lh(); + var paths = patternlab.config.paths; + + //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json + var needle = currentPattern.subdir + '/' + currentPattern.fileName + '~*.json'; + var pseudoPatterns = glob.sync(needle, { + cwd: paths.source.patterns, + debug: false, + nodir: true + }); + + if (pseudoPatterns.length > 0) { + for (var i = 0; i < pseudoPatterns.length; i++) { + if (patternlab.config.debug) { + console.log('found pseudoPattern variant of ' + currentPattern.key); + } + + //we want to do everything we normally would here, except instead read the pseudoPattern data + var variantFileData = fs.readJSONSync(path.resolve(paths.source.patterns, pseudoPatterns[i])); + + //extend any existing data with variant data + variantFileData = pattern_assembler.merge_data(currentPattern.jsonFileData, variantFileData); + + var variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0]; + var variantFilePath = path.resolve(paths.source.patterns, currentPattern.subdir, currentPattern.fileName + '~' + variantName + '.json'); + var variantFileName = currentPattern.fileName + '-' + variantName + '.'; + var patternVariant = new of.oPattern(variantFilePath, currentPattern.subdir, variantFileName, variantFileData); + + //see if this file has a state + pattern_assembler.setPatternState(patternVariant, patternlab); + + //use the same template as the non-variant + patternVariant.template = currentPattern.template; + patternVariant.extendedTemplate = currentPattern.extendedTemplate; + + //find pattern lineage + lineage_hunter.find_lineage(patternVariant, patternlab); + + //add to patternlab object so we can look these up later. + pattern_assembler.addPattern(patternVariant, patternlab); + } + } + } + + return { + find_pseudopatterns: function (pattern, patternlab) { + findpseudopatterns(pattern, patternlab); + } + }; + +}; + +module.exports = pseudopattern_hunter; diff --git a/core/lib/style_modifier_hunter.js b/core/lib/style_modifier_hunter.js new file mode 100644 index 000000000..b6f870074 --- /dev/null +++ b/core/lib/style_modifier_hunter.js @@ -0,0 +1,43 @@ +/* + * patternlab-node - v1.2.0 - 2016 + * + * Brian Muenzenmeyer, and the web community. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * + */ + +"use strict"; + +var style_modifier_hunter = function () { + + function consumestylemodifier(pattern, partial, patternlab) { + //extract the classname from the stylemodifier which comes in the format of :className + var styleModifier = partial.match(/:([\w\-_|])+/g) ? partial.match(/:([\w\-_|])+/g)[0].slice(1) : null; + + if (styleModifier) { + //replace the special character pipe | used to separate multiple classes with a space + styleModifier = styleModifier.replace(/\|/g, ' '); + + if (patternlab.config.debug) { + console.log('found partial styleModifier within pattern ' + pattern.key); + } + + //replace the stylemodifier placeholder with the class name + pattern.extendedTemplate = pattern.extendedTemplate.replace(/{{[ ]?styleModifier[ ]?}}/i, styleModifier); + + //update the extendedTemplate in the partials object in case this pattern is consumed later + patternlab.partials[pattern.key] = pattern.extendedTemplate; + } + } + + return { + consume_style_modifier: function (pattern, partial, patternlab) { + consumestylemodifier(pattern, partial, patternlab); + } + }; + +}; + +module.exports = style_modifier_hunter; diff --git a/core/styleguide/js/styleguide.js b/core/styleguide/js/styleguide.js index 44adb6561..ce83f10d5 100644 --- a/core/styleguide/js/styleguide.js +++ b/core/styleguide/js/styleguide.js @@ -469,16 +469,18 @@ } // load the iframe source - var patternName = "all"; + var patternName = window.defaultPattern || "all"; var patternPath = ""; - var iFramePath = window.location.protocol+"//"+window.location.host+window.location.pathname.replace("index.html","")+"styleguide/html/styleguide.html"; + var iFramePath = window.location.protocol + "//" + window.location.host + window.location.pathname.replace("index.html","") + "styleguide/html/styleguide.html"; + + // if we were passed some url parameters, deal with it if ((oGetVars.p != undefined) || (oGetVars.pattern != undefined)) { patternName = (oGetVars.p != undefined) ? oGetVars.p : oGetVars.pattern; - patternPath = urlHandler.getFileName(patternName); - iFramePath = (patternPath != "") ? window.location.protocol+"//"+window.location.host+window.location.pathname.replace("index.html","")+patternPath : iFramePath; } if (patternName != "all") { + patternPath = urlHandler.getFileName(patternName); + iFramePath = (patternPath != "") ? window.location.protocol + "//" + window.location.host + window.location.pathname.replace("index.html","") + patternPath : iFramePath; document.getElementById("title").innerHTML = "Pattern Lab - "+patternName; history.replaceState({ "pattern": patternName }, null, null); } diff --git a/source/_patternlab-files/README b/core/templates/README.txt similarity index 100% rename from source/_patternlab-files/README rename to core/templates/README.txt diff --git a/source/_patternlab-files/index.mustache b/core/templates/index.mustache similarity index 68% rename from source/_patternlab-files/index.mustache rename to core/templates/index.mustache index 1688121dd..0c50d9459 100644 --- a/source/_patternlab-files/index.mustache +++ b/core/templates/index.mustache @@ -3,13 +3,22 @@