diff --git a/README.md b/README.md index 63dfc975b..fa94173db 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ If this looks **REALLY DIFFERENT** from what you expected, check out the [Change ## Upgrading -If you find yourself here and are looking to ugpgrade, check out how to upgrade from version to version of Pattern Lab Node here: [https://github.com/pattern-lab/patternlab-node/wiki/Upgrading](https://github.com/pattern-lab/patternlab-node/wiki/Upgrading) +If you find yourself here and are looking to upgrade, check out how to upgrade from version to version of Pattern Lab Node here: [https://github.com/pattern-lab/patternlab-node/wiki/Upgrading](https://github.com/pattern-lab/patternlab-node/wiki/Upgrading) ## Command Line Interface diff --git a/core/lib/pattern_assembler.js b/core/lib/pattern_assembler.js index 1c305688d..f88861628 100644 --- a/core/lib/pattern_assembler.js +++ b/core/lib/pattern_assembler.js @@ -117,8 +117,6 @@ var pattern_assembler = function () { } // do global registration - - if (pattern.isPattern) { patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate || pattern.template; @@ -202,8 +200,63 @@ var pattern_assembler = function () { } } + /** + * A helper that unravels a pattern looking for partials or listitems to unravel. + * The goal is really to convert pattern.template into pattern.extendedTemplate + * @param pattern - the pattern to decompose + * @param patternlab - global data store + * @param ignoreLineage - whether or not to hunt for lineage for this pattern + */ + function decomposePattern(pattern, patternlab, ignoreLineage) { + + var lineage_hunter = new lh(), + list_item_hunter = new lih(); + + pattern.extendedTemplate = pattern.template; + + //find how many partials there may be for the given pattern + var foundPatternPartials = pattern.findPartials(); + + //find any listItem blocks that within the pattern, even if there are no partials + list_item_hunter.process_list_item_partials(pattern, patternlab); + + // expand any partials present in this pattern; that is, drill down into + // the template and replace their calls in this template with rendered + // results + + if (pattern.engine.expandPartials && (foundPatternPartials !== null && foundPatternPartials.length > 0)) { + // eslint-disable-next-line + expandPartials(foundPatternPartials, list_item_hunter, patternlab, pattern); + + // update the extendedTemplate in the partials object in case this + // pattern is consumed later + patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate; + } + + //find pattern lineage + if (!ignoreLineage) { + lineage_hunter.find_lineage(pattern, patternlab); + } + + //add to patternlab object so we can look these up later. + addPattern(pattern, patternlab); + } + function processPatternIterative(relPath, patternlab) { + var relativeDepth = relPath.match(/\w(?=\\)|\w(?=\/)/g || []).length; + if (relativeDepth > 2) { + console.log(''); + plutils.logOrange('Warning:'); + plutils.logOrange('A pattern file: ' + relPath + ' was found greater than 2 levels deep from ' + patternlab.config.paths.source.patterns + '.'); + plutils.logOrange('It\'s strongly suggested to not deviate from the following structure under _patterns/'); + plutils.logOrange('[patternType]/[patternSubtype]/[patternName].[patternExtension]'); + console.log(''); + plutils.logOrange('While Pattern Lab may still function, assets may 404 and frontend links may break. Consider yourself warned. '); + plutils.logOrange('Read More: http://patternlab.io/docs/pattern-organization.html'); + console.log(''); + } + //check if the found file is a top-level markdown file var fileObject = path.parse(relPath); if (fileObject.ext === '.md') { @@ -323,9 +376,6 @@ var pattern_assembler = function () { function processPatternRecursive(file, patternlab) { - var lineage_hunter = new lh(), - list_item_hunter = new lih(); - //find current pattern in patternlab object using var file as a partial var currentPattern, i; @@ -341,32 +391,8 @@ var pattern_assembler = function () { //we are processing a markdown only pattern if (currentPattern.engine === null) { return; } - currentPattern.extendedTemplate = currentPattern.template; - - //find how many partials there may be for the given pattern - var foundPatternPartials = currentPattern.findPartials(); - - //find any listItem blocks that within the pattern, even if there are no partials - list_item_hunter.process_list_item_partials(currentPattern, patternlab); - - // expand any partials present in this pattern; that is, drill down into - // the template and replace their calls in this template with rendered - // results - - if (currentPattern.engine.expandPartials && (foundPatternPartials !== null && foundPatternPartials.length > 0)) { - // eslint-disable-next-line - expandPartials(foundPatternPartials, list_item_hunter, patternlab, currentPattern); - - // update the extendedTemplate in the partials object in case this - // pattern is consumed later - patternlab.partials[currentPattern.patternPartial] = currentPattern.extendedTemplate; - } - - //find pattern lineage - lineage_hunter.find_lineage(currentPattern, patternlab); - - //add to patternlab object so we can look these up later. - addPattern(currentPattern, patternlab); + //call our helper method to actually unravel the pattern with any partials + decomposePattern(currentPattern, patternlab); } function expandPartials(foundPatternPartials, list_item_hunter, patternlab, currentPattern) { @@ -400,14 +426,17 @@ var pattern_assembler = function () { processPatternRecursive(partialPath, patternlab); //complete assembly of extended template + //create a copy of the partial so as to not pollute it after the getPartial call. var partialPattern = getPartial(partial, patternlab); + var cleanPartialPattern = JSON5.parse(JSON5.stringify(partialPattern)); + cleanPartialPattern.extendedTemplate = cleanPartialPattern.template; //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); + style_modifier_hunter.consume_style_modifier(cleanPartialPattern, foundPatternPartials[i], patternlab); } - currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], partialPattern.extendedTemplate); + currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], cleanPartialPattern.extendedTemplate); } } @@ -501,6 +530,9 @@ var pattern_assembler = function () { addSubtypePattern: function (subtypePattern, patternlab) { addSubtypePattern(subtypePattern, patternlab); }, + decomposePattern: function (pattern, patternlab, ignoreLineage) { + decomposePattern(pattern, patternlab, ignoreLineage); + }, renderPattern: function (template, data, partials) { return renderPattern(template, data, partials); }, diff --git a/core/lib/pattern_engines.js b/core/lib/pattern_engines.js index acfe5f9db..451f8d4fe 100644 --- a/core/lib/pattern_engines.js +++ b/core/lib/pattern_engines.js @@ -1,13 +1,4 @@ -/* - * patternlab-node - v0.10.1 - 2015 - * - * Geoffrey Pursell, Brian Muenzenmeyer, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - +// special shoutout to Geoffrey Pursell for single-handedly making Pattern Lab Node Pattern Engines possible! 'use strict'; var path = require('path'); diff --git a/core/lib/patternlab.js b/core/lib/patternlab.js index 99accaa15..3d591bce2 100644 --- a/core/lib/patternlab.js +++ b/core/lib/patternlab.js @@ -1,5 +1,5 @@ /* - * patternlab-node - v2.4.4 - 2016 + * patternlab-node - v2.5.0 - 2016 * * Brian Muenzenmeyer, Geoff Pursell, and the web community. * Licensed under the MIT license. @@ -17,7 +17,6 @@ var diveSync = require('diveSync'), plutils = require('./utilities'); function buildPatternData(dataFilesPath, fs) { - var dataFilesPath = dataFilesPath; var dataFiles = glob.sync(dataFilesPath + '*.json', {"ignore" : [dataFilesPath + 'listitems.json']}); var mergeObject = {}; dataFiles.forEach(function (filePath) { @@ -84,8 +83,13 @@ var patternlab_engine = function (config) { lh = require('./lineage_hunter'), ui = require('./ui_builder'), sm = require('./starterkit_manager'), + Pattern = require('./object_factory').Pattern, patternlab = {}; + var pattern_assembler = new pa(), + pattern_exporter = new pe(), + lineage_hunter = new lh(); + patternlab.package = fs.readJSONSync(path.resolve(__dirname, '../../package.json')); patternlab.config = config || fs.readJSONSync(path.resolve(__dirname, '../../patternlab-config.json')); @@ -191,6 +195,46 @@ var patternlab_engine = function (config) { starterkit_manager.load_starterkit(starterkitName, clean); } + /** + * Process the user-defined pattern head and prepare it for rendering + */ + function processHeadPattern() { + try { + var headPath = path.resolve(paths.source.meta, '_00-head.mustache'); + var headPattern = new Pattern(headPath, null, patternlab); + headPattern.template = fs.readFileSync(headPath, 'utf8'); + headPattern.isPattern = false; + headPattern.isMetaPattern = true; + pattern_assembler.decomposePattern(headPattern, patternlab, true); + patternlab.userHead = headPattern.extendedTemplate; + } + catch (ex) { + plutils.logRed('\nWARNING: Could not find the user-editable header template, currently configured to be at ' + path.join(config.paths.source.meta, '_00-head.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); + if (patternlab.config.debug) { console.log(ex); } + process.exit(1); + } + } + + /** + * Process the user-defined pattern footer and prepare it for rendering + */ + function processFootPattern() { + try { + var footPath = path.resolve(paths.source.meta, '_01-foot.mustache'); + var footPattern = new Pattern(footPath, null, patternlab); + footPattern.template = fs.readFileSync(footPath, 'utf8'); + footPattern.isPattern = false; + footPattern.isMetaPattern = true; + pattern_assembler.decomposePattern(footPattern, patternlab, true); + patternlab.userFoot = footPattern.extendedTemplate; + } + catch (ex) { + plutils.logRed('\nWARNING: Could not find the user-editable footer template, currently configured to be at ' + path.join(config.paths.source.meta, '_01-foot.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); + if (patternlab.config.debug) { console.log(ex); } + process.exit(1); + } + } + function buildPatterns(deletePatternDir) { try { patternlab.data = buildPatternData(paths.source.data, fs); @@ -222,37 +266,18 @@ var patternlab_engine = function (config) { setCacheBust(); - var pattern_assembler = new pa(), - 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 - processAllPatternsIterative(pattern_assembler, patterns_dir, patternlab); + processAllPatternsIterative(pattern_assembler, paths.source.patterns, patternlab); //diveSync again to recursively include partials, filling out the //extendedTemplate property of the patternlab.patterns elements - processAllPatternsRecursive(pattern_assembler, patterns_dir, patternlab); + processAllPatternsRecursive(pattern_assembler, paths.source.patterns, patternlab); - //set user defined head and foot if they exist - try { - patternlab.userHead = fs.readFileSync(path.resolve(paths.source.meta, '_00-head.mustache'), 'utf8'); - } - catch (ex) { - plutils.logRed('\nWARNING: Could not find the user-editable header template, currently configured to be at ' + path.join(config.paths.source.meta, '_00-head.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); - if (patternlab.config.debug) { console.log(ex); } - process.exit(1); - } - try { - patternlab.userFoot = fs.readFileSync(path.resolve(paths.source.meta, '_01-foot.mustache'), 'utf8'); - } - catch (ex) { - plutils.logRed('\nWARNING: Could not find the user-editable footer template, currently configured to be at ' + path.join(config.paths.source.meta, '_01-foot.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); - if (patternlab.config.debug) { console.log(ex); } - process.exit(1); - } + //take the user defined head and foot and process any data and patterns that apply + processHeadPattern(); + processFootPattern(); //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 @@ -287,8 +312,6 @@ var patternlab_engine = function (config) { return false; } - pattern.header = head; - //todo move this into lineage_hunter pattern.patternLineages = pattern.lineage; pattern.patternLineageExists = pattern.lineage.length > 0; @@ -307,14 +330,13 @@ var patternlab_engine = function (config) { allData = plutils.mergeData(allData, pattern.jsonFileData); allData.cacheBuster = patternlab.cacheBuster; + //re-rendering the headHTML each time allows pattern-specific data to influence the head of the pattern + pattern.header = head; var headHTML = pattern_assembler.renderPattern(pattern.header, allData); //render the extendedTemplate with all data pattern.patternPartialCode = pattern_assembler.renderPattern(pattern, allData); - //todo see if this is still needed - //pattern.patternPartialCodeE = entity_encoder.encode(pattern.patternPartialCode); - // stringify this data for individual pattern rendering and use on the styleguide // see if patternData really needs these other duped values pattern.patternData = JSON.stringify({ @@ -350,9 +372,17 @@ var patternlab_engine = function (config) { cacheBuster: patternlab.cacheBuster }); - var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, { - patternLabFoot : footerPartial - }); + var allFooterData; + try { + allFooterData = JSON5.parse(JSON5.stringify(patternlab.data)); + } catch (err) { + console.log('There was an error parsing JSON for ' + pattern.relPath); + console.log(err); + } + allFooterData = plutils.mergeData(allFooterData, pattern.jsonFileData); + allFooterData.patternLabFoot = footerPartial; + + var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, allFooterData); //write the compiled template to the public patterns directory var patternPage = headHTML + pattern.patternPartialCode + footerHTML; diff --git a/core/lib/pseudopattern_hunter.js b/core/lib/pseudopattern_hunter.js index eb8753be2..2b799f2f3 100644 --- a/core/lib/pseudopattern_hunter.js +++ b/core/lib/pseudopattern_hunter.js @@ -54,7 +54,7 @@ var pseudopattern_hunter = function () { // use the same template engine as the non-variant engine: currentPattern.engine - }); + }, patternlab); //process the companion markdown file if it exists pattern_assembler.parse_pattern_markdown(patternVariant, patternlab); diff --git a/core/lib/ui_builder.js b/core/lib/ui_builder.js index 4fecf68d8..3d41acea5 100644 --- a/core/lib/ui_builder.js +++ b/core/lib/ui_builder.js @@ -1,6 +1,7 @@ "use strict"; var path = require('path'); +var JSON5 = require('json5'); var fs = require('fs-extra'); var ae = require('./annotation_exporter'); var of = require('./object_factory'); @@ -98,7 +99,16 @@ var ui_builder = function () { isOmitted = pattern.relPath.charAt(0) === '_' || pattern.relPath.indexOf('/_') > -1; if (isOmitted) { if (patternlab.config.debug) { - console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns its contained within an underscored directory.'); + console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns because its contained within an underscored directory.'); + } + return true; + } + + //this pattern is a head or foot pattern + isOmitted = pattern.isMetaPattern; + if (isOmitted) { + if (patternlab.config.debug) { + console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns because its a meta pattern.'); } return true; } @@ -396,10 +406,17 @@ var ui_builder = function () { cacheBuster: patternlab.cacheBuster }); + var allFooterData; + try { + allFooterData = JSON5.parse(JSON5.stringify(patternlab.data)); + } catch (err) { + console.log('There was an error parsing JSON for patternlab.data'); + console.log(err); + } + allFooterData.patternLabFoot = footerPartial; + //then add it to the user footer - var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, { - patternLabFoot : footerPartial - }); + var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, allFooterData); return footerHTML; } @@ -483,9 +500,13 @@ var ui_builder = function () { //todo this isn't quite working yet //typePatterns = typePatterns.concat(getPatternItems(patternlab, patternType)); - //render the viewall template + //get the appropriate patternType + var anyPatternOfType = _.find(typePatterns, function (pat) { + return pat.patternType && pat.patternType !== '';}); + + //render the viewall template for the type var viewAllHTML = buildViewAllHTML(patternlab, typePatterns, patternType); - writeFile(paths.public.patterns + p.subdir + '/index.html', mainPageHeadHtml + viewAllHTML + footerHTML); + writeFile(paths.public.patterns + anyPatternOfType.patternType + '/index.html', mainPageHeadHtml + viewAllHTML + footerHTML); //determine if we should omit this patterntype completely from the viewall page var omitPatternType = styleGuideExcludes && styleGuideExcludes.length @@ -505,7 +526,6 @@ var ui_builder = function () { return patterns; } - /** * Write out our pattern information for use by the front end * @param patternlab - global data store @@ -574,19 +594,19 @@ var ui_builder = function () { var headerPartial = pattern_assembler.renderPattern(patternlab.header, { cacheBuster: patternlab.cacheBuster }); - var headerHTML = pattern_assembler.renderPattern(patternlab.userHead, { - patternLabHead : headerPartial, - cacheBuster: patternlab.cacheBuster - }); + + var headFootData = patternlab.data; + headFootData.patternLabHead = headerPartial; + headFootData.cacheBuster = patternlab.cacheBuster; + var headerHTML = pattern_assembler.renderPattern(patternlab.userHead, headFootData); //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer var footerPartial = pattern_assembler.renderPattern(patternlab.footer, { patternData: '{}', cacheBuster: patternlab.cacheBuster }); - var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, { - patternLabFoot : footerPartial - }); + headFootData.patternLabFoot = footerPartial; + var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, headFootData); //build the viewall pages var allPatterns = buildViewAllPages(headerHTML, patternlab, styleguidePatterns); diff --git a/package.json b/package.json index 811086048..f6f795888 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": "2.4.4", + "version": "2.5.0", "main": "./core/lib/patternlab.js", "dependencies": { "diveSync": "^0.3.0", diff --git a/test/pattern_assembler_tests.js b/test/pattern_assembler_tests.js index 08b22b871..9c0e8704e 100644 --- a/test/pattern_assembler_tests.js +++ b/test/pattern_assembler_tests.js @@ -265,7 +265,7 @@ var mixedPattern = new Pattern('00-test/07-mixed-params.mustache'); mixedPattern.template = fs.readFileSync(patterns_dir + '/00-test/07-mixed-params.mustache', 'utf8'); mixedPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(mixedPattern); - mixedPattern.parameteredPartials = pattern_assembler.find_pattern_partials_with_parameters(mixedPattern); + mixedPattern.parameteredPartials = pattern_assembler.find_pattern_partials_with_parameters(mixedPattern); pattern_assembler.addPattern(atomPattern, pl); pattern_assembler.addPattern(mixedPattern, pl); @@ -322,6 +322,53 @@ test.equals(bookendPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); test.done(); }, + 'processPatternRecursive - does not pollute previous patterns when a later one is found with a styleModifier' : function(test){ + //arrange + var fs = require('fs-extra'); + var pattern_assembler = new pa(); + var patterns_dir = './test/files/_patterns'; + + var pl = {}; + pl.config = { + paths: { + source: { + patterns: patterns_dir + } + }, + outputFileSuffixes: { + rendered : '' + } + }; + pl.data = {}; + pl.data.link = {}; + pl.config.debug = false; + pl.patterns = []; + pl.partials = {}; + + var atomPattern = new Pattern('00-test/03-styled-atom.mustache'); + atomPattern.template = fs.readFileSync(patterns_dir + '/00-test/03-styled-atom.mustache', 'utf8'); + atomPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(atomPattern); + atomPattern.parameteredPartials = pattern_assembler.find_pattern_partials_with_parameters(atomPattern); + + var anotherPattern = new Pattern('00-test/12-another-styled-atom.mustache'); + anotherPattern.template = fs.readFileSync(patterns_dir + '/00-test/12-another-styled-atom.mustache', 'utf8'); + anotherPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(anotherPattern); + anotherPattern.parameteredPartials = pattern_assembler.find_pattern_partials_with_parameters(anotherPattern); + + pattern_assembler.addPattern(atomPattern, pl); + pattern_assembler.addPattern(anotherPattern, pl); + + //act + pattern_assembler.process_pattern_recursive('00-test' + path.sep + '12-another-styled-atom.mustache', pl, {}); + + //assert + var expectedCleanValue = ' {{message}} '; + var expectedSetValue = ' {{message}} '; + test.equals(anotherPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedSetValue.trim()); + test.equals(atomPattern.template.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedCleanValue.trim()); + test.equals(atomPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedCleanValue.trim()); + test.done(); + }, 'setState - applies any patternState matching the pattern' : function(test){ //arrange var pa = require('../core/lib/pattern_assembler'); diff --git a/test/pseudopattern_hunter_tests.js b/test/pseudopattern_hunter_tests.js index f428172ce..dd2d4c4c2 100644 --- a/test/pseudopattern_hunter_tests.js +++ b/test/pseudopattern_hunter_tests.js @@ -1,5 +1,6 @@ "use strict"; +var path = require('path'); var pha = require('../core/lib/pseudopattern_hunter'); var pa = require('../core/lib/pattern_assembler'); var Pattern = require('../core/lib/object_factory').Pattern; @@ -44,6 +45,7 @@ exports['pseudopattern_hunter'] = { test.equals(pl.patterns[1].patternPartial, 'test-styled-atom-alt'); test.equals(pl.patterns[1].extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), ' {{message}} '); test.equals(JSON.stringify(pl.patterns[1].jsonFileData), JSON.stringify({"message": "alternateMessage"})); + test.equals(pl.patterns[1].patternLink, '00-test-03-styled-atom-alt' + path.sep + '00-test-03-styled-atom-alt.html'); test.done(); }