diff --git a/.gitignore b/.gitignore index ac97c0ae0..7200b256a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ public/fonts/* public/js/* public/images/* public/patterns/* -config.ini latest-change.txt patternlab.json .sass-cache/* diff --git a/.travis.yml b/.travis.yml index 274dcc885..0adf4ab2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,10 @@ language: node_js node_js: + - 4.1 + - 4.0 + - 0.12 - 0.11 - - 0.10 before_install: - phantomjs --version diff --git a/CHANGELOG b/CHANGELOG index 9a3abb751..1c47bf53e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,15 @@ THIS CHANGELOG IS AN ATTEMPT TO DOCUMENT CHANGES TO THIS PROJECT. +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 diff --git a/Gruntfile.js b/Gruntfile.js index c8f9e603f..06cf9e29c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,6 +28,10 @@ module.exports = function(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' diff --git a/builder/lineage_hunter.js b/builder/lineage_hunter.js index 7c9f5038f..a6244f41a 100644 --- a/builder/lineage_hunter.js +++ b/builder/lineage_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/list_item_hunter.js b/builder/list_item_hunter.js index cc357f48d..50b3aa88a 100644 --- a/builder/list_item_hunter.js +++ b/builder/list_item_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/media_hunter.js b/builder/media_hunter.js index 247a96042..6170b1bf5 100644 --- a/builder/media_hunter.js +++ b/builder/media_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/object_factory.js b/builder/object_factory.js index 38582ab65..d70f69996 100644 --- a/builder/object_factory.js +++ b/builder/object_factory.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. @@ -11,12 +11,16 @@ (function () { "use strict"; - var oPattern = function(subdir, filename, data){ + 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.substring(this.fileName.indexOf('-') + 1); //this is the display name for the ui + 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); @@ -32,7 +36,9 @@ var oBucket = function(name){ this.bucketNameLC = name; - this.bucketNameUC = name.charAt(0).toUpperCase() + name.slice(1); + 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 = []; @@ -41,7 +47,9 @@ var oNavItem = function(name){ this.sectionNameLC = name; - this.sectionNameUC = name.charAt(0).toUpperCase() + name.slice(1); + 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 = []; }; @@ -49,7 +57,9 @@ var oNavSubItem = function(name){ this.patternPath = ''; this.patternPartial = ''; - this.patternName = name.charAt(0).toUpperCase() + name.slice(1); + 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 = { diff --git a/builder/parameter_hunter.js b/builder/parameter_hunter.js index e4faf7450..388bf7249 100644 --- a/builder/parameter_hunter.js +++ b/builder/parameter_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/pattern_assembler.js b/builder/pattern_assembler.js index 65e80eb5b..feba11874 100644 --- a/builder/pattern_assembler.js +++ b/builder/pattern_assembler.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. @@ -44,7 +44,22 @@ function addPattern(pattern, patternlab){ patternlab.data.link[pattern.patternGroup + '-' + pattern.patternName] = '/patterns/' + pattern.patternLink; - patternlab.patterns.push(pattern); + + //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; + isNew = false; + break; + } + } + //if the pattern is new, just push to the array + if(isNew){ + patternlab.patterns.push(pattern); + } } function renderPattern(template, data, partials) { @@ -58,23 +73,38 @@ } } - function processPatternFile(file, patternlab){ + function processPatternIterative(file, patternlab){ var fs = require('fs-extra'), of = require('./object_factory'), path = require('path'); //extract some information - var abspath = file.substring(2); var subdir = path.dirname(path.relative(patternlab.config.patterns.source, file)).replace('\\', '/'); var filename = path.basename(file); + var ext = path.extname(filename); - //ignore _underscored patterns, json (for now), and dotfiles - if(filename.charAt(0) === '_' || path.extname(filename) === '.json' || filename.charAt(0) === '.'){ + //ignore dotfiles 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(subdir, filename); + var currentPattern = new of.oPattern(file, subdir, filename); + + //if file is named in the syntax for variants + if(ext === '.json' && filename.indexOf('~') > -1){ + //add current pattern to patternlab object with minimal data + //processPatternRecursive() will run find_pseudopatterns() to fill out + //the object in the next diveSync + addPattern(currentPattern, patternlab); + //no need to process further + return; + } + + //can ignore all non-mustache files at this point + if(ext !== '.mustache'){ + return; + } //see if this file has a state setState(currentPattern, patternlab); @@ -83,7 +113,9 @@ try { var jsonFilename = patternlab.config.patterns.source + currentPattern.subdir + '/' + currentPattern.fileName + ".json"; currentPattern.jsonFileData = fs.readJSONSync(jsonFilename.substring(2)); - console.log('found pattern-specific data.json for ' + currentPattern.key); + if(patternlab.config.debug){ + console.log('found pattern-specific data.json for ' + currentPattern.key); + } } catch(e) { } @@ -92,19 +124,21 @@ try { var listJsonFileName = patternlab.config.patterns.source + currentPattern.subdir + '/' + currentPattern.fileName + ".listitems.json"; currentPattern.patternSpecificListJson = fs.readJSONSync(listJsonFileName.substring(2)); - console.log('found pattern-specific listitems.json for ' + currentPattern.key); + 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(abspath, 'utf8'); + currentPattern.template = fs.readFileSync(file, 'utf8'); - //our helper function that does a lot of heavy lifting - processPattern(currentPattern, patternlab); + //add currentPattern to patternlab.patterns array + addPattern(currentPattern, patternlab); } - function processPattern(currentPattern, patternlab, additionalData){ + function processPatternRecursive(file, patternlab, additionalData){ var fs = require('fs-extra'), mustache = require('mustache'), @@ -119,6 +153,21 @@ list_item_hunter = new lih(), 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]; + } + } + + //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 @@ -137,8 +186,23 @@ parameter_hunter.find_parameters(currentPattern, patternlab); //do something with the regular old partials - for(var i = 0; i < foundPatternPartials.length; i++){ + for(i = 0; i < foundPatternPartials.length; i++){ var partialKey = foundPatternPartials[i].replace(/{{>([ ])?([\w\-\.\/~]+)(?:\:[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); currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], partialPattern.extendedTemplate); } @@ -148,11 +212,11 @@ //find pattern lineage lineage_hunter.find_lineage(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); - //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){ @@ -167,24 +231,39 @@ throw 'Could not find pattern with key ' + key; } - - var self = this; - function mergeData(obj1, obj2) { - for (var p in obj2) { + /** + * Recursively merge properties of two objects. + * + * @param {Object} obj1 If obj1 has properties obj2 doesn't, add to obj2. + * @param {Object} obj2 This object's properties have priority over obj1. + * @returns {Object} obj2 + */ + function mergeData(obj1, obj2){ + if(typeof obj2 === 'undefined'){ + obj2 = {}; + } + for(var p in obj1){ try { - // Property in destination object set; update its value. - if ( obj2[p].constructor == Object ) { - obj1[p] = self.merge_data(obj1[p], obj2[p]); - - } else { - obj1[p] = obj2[p]; + // 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. - obj1[p] = obj2[p]; + if(typeof obj2[p] === 'undefined'){ + obj2[p] = obj1[p]; + } } } - return obj1; + return obj2; } function buildListItems(patternlab){ @@ -233,11 +312,11 @@ renderPattern: function(template, data, partials){ return renderPattern(template, data, partials); }, - process_pattern_file: function(file, patternlab){ - processPatternFile(file, patternlab); + process_pattern_iterative: function(file, patternlab){ + processPatternIterative(file, patternlab); }, - process_pattern: function(pattern, patternlab, additionalData){ - processPattern(pattern, patternlab, additionalData); + process_pattern_recursive: function(file, patternlab, additionalData){ + processPatternRecursive(file, patternlab, additionalData); }, get_pattern_by_key: function(key, patternlab){ return getpatternbykey(key, patternlab); diff --git a/builder/pattern_exporter.js b/builder/pattern_exporter.js index 71c19b616..1be55f96c 100644 --- a/builder/pattern_exporter.js +++ b/builder/pattern_exporter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/patternlab.js b/builder/patternlab.js index 80f8d75f4..eef0ddbb3 100644 --- a/builder/patternlab.js +++ b/builder/patternlab.js @@ -1,10 +1,10 @@ -/* - * patternlab-node - v0.12.0 - 2015 - * +/* + * patternlab-node - v0.13.0 - 2015 + * * Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. + * Licensed under the MIT license. + * + * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. * */ @@ -70,6 +70,7 @@ var patternlab_engine = function () { pattern_assembler.combine_listItems(patternlab); + //diveSync once to perform iterative populating of patternlab object diveSync(patterns_dir, { filter: function(path, dir) { if(dir){ @@ -87,7 +88,29 @@ var patternlab_engine = function () { return; } - pattern_assembler.process_pattern_file(file, patternlab); + pattern_assembler.process_pattern_iterative(file.substring(2), 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; + } + return true; + } + }, + function(err, file){ + //log any errors + if(err){ + console.log(err); + return; + } + + pattern_assembler.process_pattern_recursive(file.substring(2), patternlab); }); //delete the contents of config.patterns.public before writing @@ -142,6 +165,11 @@ var patternlab_engine = function () { i; for (i = 0; i < patternlab.patterns.length; i++) { + // skip underscore-prefixed files + if (path.basename(patternlab.patterns[i].abspath).charAt(0) === '_') { + continue; + } + var pattern = patternlab.patterns[i]; // check if the current sub section is different from the previous one @@ -170,6 +198,11 @@ var patternlab_engine = function () { //loop through all patterns.to build the navigation //todo: refactor this someday for(var i = 0; i < patternlab.patterns.length; i++){ + // skip underscore-prefixed files + if (path.basename(patternlab.patterns[i].abspath).charAt(0) === '_') { + continue; + } + var pattern = patternlab.patterns[i]; var bucketName = pattern.name.replace(/\\/g, '-').split('-')[1]; @@ -184,7 +217,8 @@ var patternlab_engine = function () { patternlab.viewAllPaths[bucketName] = {}; //get the navItem - var navItemName = pattern.subdir.split('-').pop(); + var navItemName = pattern.subdir.split('/').pop(); + navItemName = navItemName.replace(/(\d).(-)/g, ''); //get the navSubItem var navSubItemName = pattern.patternName.replace(/-/g, ' '); @@ -239,7 +273,10 @@ var patternlab_engine = function () { var bucket = patternlab.buckets[bucketIndex]; //get the navItem - var navItemName = pattern.subdir.split('-').pop(); + //if there is one or more slashes in the subdir, get everything after + //the last slash. if no slash, get the whole subdir string and strip + //any numeric + hyphen prefix + var navItemName = pattern.subdir.split('/').pop().replace(/^\d*\-/, ''); //get the navSubItem var navSubItemName = pattern.patternName.replace(/-/g, ' '); diff --git a/builder/patternlab_grunt.js b/builder/patternlab_grunt.js index 744421a1b..6d093ae83 100644 --- a/builder/patternlab_grunt.js +++ b/builder/patternlab_grunt.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. diff --git a/builder/patternlab_gulp.js b/builder/patternlab_gulp.js index 5cbeb2440..11fc334dc 100644 --- a/builder/patternlab_gulp.js +++ b/builder/patternlab_gulp.js @@ -1,3 +1,13 @@ +/* + * patternlab-node - v0.13.0 - 2015 + * + * 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(gulp) { diff --git a/builder/pseudopattern_hunter.js b/builder/pseudopattern_hunter.js index 9f0a33a4e..a07f5e4df 100644 --- a/builder/pseudopattern_hunter.js +++ b/builder/pseudopattern_hunter.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v0.12.0 - 2015 + * patternlab-node - v0.13.0 - 2015 * * Brian Muenzenmeyer, and the web community. * Licensed under the MIT license. @@ -45,10 +45,12 @@ var variantFileData = fs.readJSONSync('source/_patterns/' + pseudoPatterns[i]); //extend any existing data with variant data - variantFileData = pattern_assembler.merge_data(variantFileData, currentPattern.jsonFileData); + variantFileData = pattern_assembler.merge_data(currentPattern.jsonFileData, variantFileData); var variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0]; - var patternVariant = new of.oPattern(currentPattern.subdir, currentPattern.fileName + '-' + variantName + '.mustache', variantFileData); + var variantFilePath = '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); diff --git a/gulpfile.js b/gulpfile.js index 8007b8410..a3b426ecc 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -37,6 +37,7 @@ gulp.task('banner', function(){ './builder/lineage_hunter.js', './builder/media_hunter.js', './builder/patternlab_grunt.js', + './builder/patternlab_gulp.js', './builder/parameter_hunter.js', './builder/pattern_exporter.js', './builder/pattern_assembler.js', diff --git a/package.gulp.json b/package.gulp.json index 36406357a..3bc12da2c 100644 --- a/package.gulp.json +++ b/package.gulp.json @@ -1,7 +1,7 @@ { "name": "patternlab-node", "description": "Pattern Lab is a collection of tools to help you create atomic design systems. This is the node command line interface (CLI).", - "version": "0.12.0", + "version": "0.13.0", "devDependencies": { "browser-sync": "^2.8.2", "del": "^1.2.1", diff --git a/package.json b/package.json index 1d97710a9..b018cf595 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "patternlab-node", "description": "Pattern Lab is a collection of tools to help you create atomic design systems. This is the node command line interface (CLI).", - "version": "0.12.0", + "version": "0.13.0", "devDependencies": { "bs-html-injector": "^2.0.4", "diveSync": "^0.2.1", diff --git a/source/Thumbs.db b/source/Thumbs.db deleted file mode 100644 index c8dedbbe7..000000000 Binary files a/source/Thumbs.db and /dev/null differ diff --git a/source/_patternlab-files/styleguide.mustache b/source/_patternlab-files/styleguide.mustache index e4a9e5479..fe39a3816 100644 --- a/source/_patternlab-files/styleguide.mustache +++ b/source/_patternlab-files/styleguide.mustache @@ -9,21 +9,21 @@
- +