diff --git a/core/lib/list_item_hunter.js b/core/lib/list_item_hunter.js index c3131876d..518a9868c 100644 --- a/core/lib/list_item_hunter.js +++ b/core/lib/list_item_hunter.js @@ -12,119 +12,97 @@ var list_item_hunter = function () { - var extend = require('util')._extend, - JSON5 = require('json5'), + var JSON5 = require('json5'), 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 getListItemIterationKeys() { + return items; + } + function processListItemPartials(pattern, patternlab) { + //find any listitem blocks - var matches = pattern_assembler.find_list_items(pattern, patternlab); - if (matches !== null) { - matches.forEach(function (liMatch) { + var liMatches = pattern_assembler.find_list_items(pattern.extendedTemplate, patternlab); + if (liMatches !== null) { + + if (pattern.listitemsRaw) { + + //copy, don't reference + try { + pattern.listitems = JSON5.parse(JSON5.stringify(pattern.listitemsRaw)); + } catch (err) { + console.log('There was an error parsing JSON for ' + pattern.abspath); + console.log(err); + } + + //if no local listitems.json file, use global listitems data + } else { + + //copy, don't reference + try { + pattern.listitems = JSON5.parse(JSON5.stringify(patternlab.listitems)); + pattern.listitemsRaw = JSON5.parse(JSON5.stringify(patternlab.listitems)); + } catch (err) { + console.log('There was an error parsing JSON for ' + pattern.abspath); + console.log(err); + } + } + + //this shuffles listitemsRaw, and builds it into an object of array of + //objects and saves that in listitems + pattern_assembler.combine_listItems(pattern); + + for (var i = 0; i < liMatches.length; i++) { if (patternlab.config.debug) { - console.log('found listItem of size ' + liMatch + ' inside ' + pattern.key); + console.log('found listItem of size ' + liMatches[i] + ' 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(); + var loopNumberString = liMatches[i].split('.')[1].split('}')[0].trim(); + var end = liMatches[i].replace('#', '/'); + var patternBlock = pattern.extendedTemplate.substring(pattern.extendedTemplate.indexOf(liMatches[i]) + liMatches[i].length, pattern.extendedTemplate.indexOf(end)).trim(); //build arrays that repeat the block, however large we need to var repeatedBlockTemplate = []; var repeatedBlockHtml = ''; - var i; // for loops + var j; // for loops - for (i = 0; i < items.indexOf(loopNumberString); i++) { + for (j = 0; j < items.indexOf(loopNumberString); j++) { repeatedBlockTemplate.push(patternBlock); } - //check for a local listitems.json file - var listData; - try { - listData = JSON5.parse(JSON5.stringify(patternlab.listitems)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.abspath); - console.log(err); - } - 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]; + //iterate over each copied block, rendering its contents along with pattenlab.listitems[j] + for (j = 0; j < repeatedBlockTemplate.length; j++) { var thisBlockHTML = ""; + var itemData = pattern.listitems['' + items.indexOf(loopNumberString)]; //this is a property like "2" + var patternData = pattern.jsonFileData; + var allData = pattern_assembler.merge_data(patternData, itemData !== undefined ? itemData[j] : {}); //itemData could be undefined if the listblock contains no partial, just markup - //combine listItem data with pattern data with global data - var itemData = listData['' + items.indexOf(loopNumberString)]; //this is a property like "2" - var globalData; - var localData; - try { - globalData = JSON5.parse(JSON5.stringify(patternlab.data)); - localData = JSON5.parse(JSON5.stringify(pattern.jsonFileData)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.abspath); - console.log(err); - } - - 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; - try { - cleanPartialPattern = JSON5.parse(JSON5.stringify(partialPattern)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.abspath); - console.log(err); - } - - //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); - } + + thisBlockHTML = pattern_assembler.renderPattern(patternBlock, allData); //add the rendered HTML to our string repeatedBlockHtml = repeatedBlockHtml + thisBlockHTML; + + //unset the allData reference to free up memory + allData = null; } //replace the block with our generated HTML - var repeatingBlock = pattern.extendedTemplate.substring(pattern.extendedTemplate.indexOf(liMatch), pattern.extendedTemplate.indexOf(end) + end.length); + var repeatingBlock = pattern.extendedTemplate.substring(pattern.extendedTemplate.indexOf(liMatches[i]), 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 { + get_list_item_iteration_keys: function () { + return getListItemIterationKeys(); + }, process_list_item_partials: function (pattern, patternlab) { processListItemPartials(pattern, patternlab); } diff --git a/core/lib/parameter_hunter.js b/core/lib/parameter_hunter.js index 45f111a67..1d004f0a7 100644 --- a/core/lib/parameter_hunter.js +++ b/core/lib/parameter_hunter.js @@ -12,8 +12,7 @@ var parameter_hunter = function () { - var extend = require('util')._extend, - JSON5 = require('json5'), + var JSON5 = require('json5'), pa = require('./pattern_assembler'), smh = require('./style_modifier_hunter'), pattern_assembler = new pa(), @@ -159,7 +158,7 @@ var parameter_hunter = function () { values.push(paramString.match(regex)[0].trim()); //truncate the beginning from paramString and continue either - //looking for a key, or returning + //looking for a key, or returning paramString = paramString.replace(regex, '').trim(); //exit do while if the final char is '}' @@ -241,67 +240,97 @@ var parameter_hunter = function () { return paramStringWellFormed; } - function findparameters(pattern, patternlab) { + function findparameters(pattern, patternlab, parameteredPartials) { + var uniquePartials = []; - if (pattern.parameteredPartials && pattern.parameteredPartials.length > 0) { + for (var i = 0; i < parameteredPartials.length; i++) { - //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); + //limit iteration to one time per partial. eliminate duplicates. + if (uniquePartials.indexOf(parameteredPartials[i]) === -1) { + uniquePartials.push(parameteredPartials[i]); + } else { + continue; + } - //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #190 - partialPattern.extendedTemplate = partialPattern.template; + //find the partial's name and retrieve it + var partialName = parameteredPartials[i].match(/([\w\-\.\/~]+)/g)[0]; + var partialPattern = pattern_assembler.get_pattern_by_key(partialName, patternlab); - if (patternlab.config.debug) { - console.log('found patternParameters for ' + partialName); - } + if (!partialPattern) { + throw ('Could not find pattern with key ' + partialName); + } - //strip out the additional data, convert string to JSON. - var leftParen = pMatch.indexOf('('); - var rightParen = pMatch.lastIndexOf(')'); - var paramString = '{' + pMatch.substring(leftParen + 1, rightParen) + '}'; - var paramStringWellFormed = paramToJson(paramString); - - var paramData = {}; - var globalData = {}; - var localData = {}; - - try { - paramData = JSON5.parse(paramStringWellFormed); - globalData = JSON5.parse(JSON5.stringify(patternlab.data)); - localData = JSON5.parse(JSON5.stringify(pattern.jsonFileData || {})); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.abspath); - console.log(err); - } + //if we retrieved a pattern we should make sure that its tmpTemplate is reset. looks to fix #190 + partialPattern.tmpTemplate = partialPattern.escapedTemplate; - var allData = pattern_assembler.merge_data(globalData, localData); - allData = pattern_assembler.merge_data(allData, paramData); + if (patternlab.config.debug) { + console.log('found patternParameters for ' + partialName); + } - //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); + //if the current tag has styleModifier data, replace the styleModifier value in the partial + //do this before rendering parametered tags + if (pattern_assembler.find_pattern_partials_with_style_modifiers(parameteredPartials[i])) { + style_modifier_hunter.consume_style_modifier(partialPattern, parameteredPartials[i], patternlab); + } + + //strip out the additional data, convert string to JSON + var leftParen = parameteredPartials[i].indexOf('('); + var rightParen = parameteredPartials[i].lastIndexOf(')'); + var paramString = '{' + parameteredPartials[i].substring(leftParen + 1, rightParen) + '}'; + var paramStringWellFormed = paramToJson(paramString); + + var paramData = {}; + try { + paramData = JSON5.parse(paramStringWellFormed); + } catch (e) { + console.log(e); + } + + var regex; + var escapedKey; + for (var j in paramData) { + if (paramData.hasOwnProperty(j) && (typeof paramData[j] === 'boolean' || typeof paramData[j] === 'number' || typeof paramData[j] === 'string')) { + //escape regex special characters as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Using_special_characters + escapedKey = j.replace(/[.*+?^${}()|[\]\\\/]/g, '\\$&'); + + //apply replacement based on allowable characters from lines 78 and 79 of mustache.js + //of the Mustache for JS project. + regex = new RegExp('\\{\\{([\\{#\\^\\/&]?\\s*' + escapedKey + '\\s*\\}?)\\}\\}', 'g'); + + //since ERB is already used for escaping in partialPattern.escapedTemplate, + //using <%% %%> as escaping tags. + partialPattern.tmpTemplate = partialPattern.tmpTemplate.replace(regex, '<%%$1%%>'); + regex = new RegExp('<%([\\{#\\^\\/&]?\\s*' + escapedKey + '\\s*\\}?)%>', 'g'); + partialPattern.tmpTemplate = partialPattern.tmpTemplate.replace(regex, '<%%$1%%>'); + + //when using alternate delimiters, triple-Mustache syntax won't work. + //<%{ must be replaced with <%# and }%> with %>. + partialPattern.tmpTemplate = partialPattern.tmpTemplate.replace(/<%%\{/g, '<%%&'); + partialPattern.tmpTemplate = partialPattern.tmpTemplate.replace(/\}%%>/g, '%%>'); } + } - //extend pattern data links into link for pattern link shortcuts to work. we do this locally and globally - allData.link = extend({}, patternlab.data.link); + //then set the new delimiter at the beginning of the extended template + partialPattern.tmpTemplate = '{{=<%% %%>=}}' + partialPattern.tmpTemplate; - var renderedPartial = pattern_assembler.renderPattern(partialPattern.extendedTemplate, allData, patternlab.partials); + //the reason for rendering at this point is to eliminate the unwanted + //recursion paths that would remain if irrelevant conditional tags persisted. + partialPattern.tmpTemplate = pattern_assembler.renderPattern(partialPattern.tmpTemplate, paramData); + partialPattern.tmpTemplate = pattern_assembler.winnow_unused_tags(partialPattern.tmpTemplate, pattern); - //remove the parameter from the partial and replace it with the rendered partial + paramData - pattern.extendedTemplate = pattern.extendedTemplate.replace(pMatch, renderedPartial); + //replace parameteredPartials with their rendered values. + var pMatch = parameteredPartials[i].replace(/[.*+?^${}()|[\]\\\/]/g, '\\$&'); + regex = new RegExp(pMatch, 'g'); + pattern.extendedTemplate = pattern.extendedTemplate.replace(regex, partialPattern.tmpTemplate); - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[pattern.key] = pattern.extendedTemplate; - }); + //free tmpTemplate from memory. + partialPattern.tmpTemplate = ''; } } return { - find_parameters: function (pattern, patternlab) { - findparameters(pattern, patternlab); + find_parameters: function (pattern, patternlab, parameteredPartials, tmpTemplate) { + findparameters(pattern, patternlab, parameteredPartials, tmpTemplate); } }; diff --git a/core/lib/pattern_assembler.js b/core/lib/pattern_assembler.js index d9136af33..e15a01d46 100644 --- a/core/lib/pattern_assembler.js +++ b/core/lib/pattern_assembler.js @@ -12,26 +12,47 @@ var pattern_assembler = function () { + // find regex matches within both pattern strings and pattern objects. + function patternMatcher(pattern, regex) { + var matches; + + if (typeof pattern === 'string') { + matches = pattern.match(regex); + } else if (typeof pattern === 'object' && typeof pattern.template === 'string') { + matches = pattern.template.match(regex); + } + + return matches; + } + // 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); + var regex = /{{>\s*([\w\-\.\/~]+)(?!\()(\:[\w\-\|]+)(\s*\((.|\s)*?\))?\s*}}/g; + var matches = patternMatcher(pattern, regex); + 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); + var regex = /{{>\s*([\w\-\.\/~]+)(\:[\w\-\|]+)?(\s*\((.|\s)*?\))\s*}}/g; + var matches = patternMatcher(pattern, regex); + return matches; } //find and return any {{> template-name* }} within pattern function findPartials(pattern) { - var matches = pattern.template.match(/{{>([ ])?([\w\-\.\/~]+)(?:\:[A-Za-z0-9-_|]+)?(?:(| )\(.*)?([ ])?}}/g); + var regex = /{{>\s*([\w\-\.\/~]+)(\:[\w\-|]+)?(\s*\((.|\s)*?\))?\s*}}/g; + var matches = patternMatcher(pattern, regex); + 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); + var regex = /{{#\s*(list(I|i)tems\.)(one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty)\s*}}/g; + var matches = patternMatcher(pattern, regex); + return matches; } @@ -54,7 +75,6 @@ var pattern_assembler = function () { 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; } @@ -63,17 +83,17 @@ var pattern_assembler = function () { //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'); + var hogan = require('hogan.js'); + var compiled = hogan.compile(template); if (partials) { - return mustache.render(template, data, partials); + return compiled.render(data, partials); } else { - return mustache.render(template, data); + return compiled.render(data); } } @@ -107,19 +127,45 @@ var pattern_assembler = function () { } } - 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]; + /** + * Recursively get all the property keys from the JSON data for a pattern. + * + * @param {object} data + * @param {array} uniqueKeys The array of unique keys to be added to and returned. + * @returns {array} keys A flat, one-dimensional array. + */ + function getDataKeys(data, uniqueKeys) { + for (var key in data) { + if (data.hasOwnProperty(key)) { + if (data.constructor !== Array) { + if (uniqueKeys.indexOf(key) === -1) { + uniqueKeys.push(key); + } else { + continue; + } + } + if (typeof data[key] === 'object') { + getDataKeys(data[key], uniqueKeys); + } } } - //else look by verbose syntax + return uniqueKeys; + } + + function getpatternbykey(key, patternlab) { + var i; // for the for loops + for (i = 0; i < patternlab.patterns.length; i++) { switch (key) { + + //look for exact key matches + case patternlab.patterns[i].key: + + //look for abspath matches + case patternlab.patterns[i].abspath: + + //else look by verbose syntax case patternlab.patterns[i].subdir + '/' + patternlab.patterns[i].fileName: case patternlab.patterns[i].subdir + '/' + patternlab.patterns[i].fileName + '.mustache': return patternlab.patterns[i]; @@ -136,11 +182,224 @@ var pattern_assembler = function () { return patternlab.patterns[i]; } } - throw 'Could not find pattern with key ' + key; + + return null; + } + + /** + * Merge the properties of two JSON objects, with the 2nd taking priority over + * the 1st when the same property key exists for both. + * + * @param {object} obj1 + * @param {object} obj2 If obj2 is null or undefined, be sure to make an + * assignment to the output of this function. In other cases, obj2 get + * mutated, and no assignment is necessary. + * @returns {object} obj2 The mutated obj2 object. + */ + function mergeData(obj1, obj2) { + obj2 = 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 JSON5 = require('json5'); + var linkRE, dataObjAsString, linkMatches, expandedLink; + + linkRE = /link\.[A-z0-9-_]+/g; + dataObjAsString = JSON5.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); + } + } + } + + var dataObj; + try { + dataObj = JSON5.parse(dataObjAsString); + } catch (err) { + console.log('There was an error parsing JSON for ' + key); + console.log(err); + } + + return dataObj; + } + + //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); + } + } + + function outputPatternToFS(pattern, patternlab) { + var fs = require('fs-extra'); + var he = require('html-entities').AllHtmlEntities; + var entity_encoder = new he(); + var paths = patternlab.config.paths; + + pattern.jsonFileData = parseDataLinksHelper(patternlab, pattern.jsonFileData, pattern.key); + pattern.jsonFileData.baseurl = patternlab.config.baseurl; + pattern.jsonFileData.patternGroup = pattern.patternGroup; + pattern.jsonFileData.patternName = pattern.patternName; + pattern.jsonFileData.patternState = pattern.patternState; + + //json stringify lineage and lineageR + var i; + var lineageArray = []; + for (i = 0; i < pattern.lineage.length; i++) { + lineageArray.push(JSON.stringify(pattern.lineage[i])); + } + pattern.jsonFileData.lineage = lineageArray; + + var lineageRArray = []; + for (i = 0; i < pattern.lineageR.length; i++) { + lineageRArray.push(JSON.stringify(pattern.lineageR[i])); + } + pattern.jsonFileData.lineageR = lineageRArray; + + //render the header + var userHeader = renderPattern(patternlab.userHead.extendedTemplate, pattern.jsonFileData); + + //render the extendedTemplate with all data + pattern.patternPartial = renderPattern(pattern.extendedTemplate, pattern.jsonFileData); + + //render the footer + var userFooter = renderPattern(patternlab.userFoot.extendedTemplate, pattern.jsonFileData); + + //write the compiled template to the public patterns directory + fs.outputFileSync(paths.public.patterns + pattern.patternLink, userHeader + pattern.patternPartial + userFooter); + + //write the mustache file too + fs.outputFileSync(paths.public.patterns + pattern.patternLink.replace('.html', '.mustache'), entity_encoder.encode(pattern.template)); + + //write the encoded version too + fs.outputFileSync(paths.public.patterns + pattern.patternLink.replace('.html', '.escaped.html'), entity_encoder.encode(pattern.patternPartial)); + + //since we're done with these pattern properties, free them from memory + pattern.extendedTemplate = ''; + pattern.template = ''; + pattern.tmpTemplate = ''; + pattern.dataKeys = null; + pattern.listitems = null; + pattern.listitemsRaw = null; + + if (pattern.jsonFileData !== patternlab.data) { + pattern.jsonFileData = null; + } + } + + /** + * Render the template excluding partials. The reason for this is to eliminate + * the unwanted recursion paths that would remain if irrelevant conditional + * tags persisted. Targeting non-partial tags that are not keyed in the JSON + * data for this pattern. Those will be deleted after this runs. + * + * @param {string|null} template The template to winnow and render or null if + * setting up the pattern.escapedTemplate property. + * @param {object} data The data to render with. + * @param {object} dataKeys The data to render with. + * @returns {string} templateWinnowed + */ + function winnowUnusedTags(template, pattern, patternlab) { + var dataKeys; + var escapedKeys; + var i; + var regex; + var templateEscaped; + var templateWinnowed; + + if (template === null) { + dataKeys = patternlab.dataKeys; + templateEscaped = pattern.template; + } else { + dataKeys = pattern.dataKeys; + templateEscaped = template; + } + + //escape all tags that match keys in the JSON data. + //it can be 10% faster to process large dataKeys arrays in one read with a + //large regex than to read many times and process with small regexes. + if (dataKeys.length) { + escapedKeys = '('; + for (i = 0; i < dataKeys.length; i++) { + escapedKeys += dataKeys[i].replace(/[.*+?^${}()|[\]\\\/]/g, '\\$&'); + if (i < dataKeys.length - 1) { + escapedKeys += '|'; + } + } + escapedKeys += ')'; + regex = new RegExp('\\{\\{([\\{#\\^\\/&]?(\\s*|[^\\}]*\\.)' + escapedKeys + '\\s*\\}?)\\}\\}', 'g'); + templateEscaped = templateEscaped.replace(regex, '<%$1%>'); + } + + //escape partial tags by switching them to ERB syntax. + //the regex is mostly copied from function findPartials(). however, we need + //to specify a capture group for the replace() method and not capture other + //parenthetical groups for the nominal performance gain. + regex = /{{>(\s*(?:[\w\-\.\/~]+)(?:\:[\w\-|]+)?(?:\s*\((?:.|\s)*?\))?\s*)}}/g; + templateEscaped = templateEscaped.replace(regex, '<%>$1%>'); + + //removing empty lines for some reason reduces rendering time considerably. + templateEscaped = templateEscaped.replace(/^\s*$\n/gm, ''); + + if (template === null) { + pattern.escapedTemplate = templateEscaped; + + } else { + + //render to winnow unused tags. + templateWinnowed = renderPattern(templateEscaped, pattern.jsonFileData); + } + + //after that's done, switch only partial tags back to standard Mustache tags and return. + if (templateWinnowed) { + templateWinnowed = templateWinnowed.replace(/<%>((?:.|\s)*?)%>/g, '{{>$1}}'); + } + + return templateWinnowed; } function processPatternIterative(file, patternlab) { var fs = require('fs-extra'), + lh = require('./lineage_hunter'), + lineage_hunter = new lh(), of = require('./object_factory'), path = require('path'); @@ -157,9 +416,19 @@ var pattern_assembler = function () { //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 + //see if this file has a state + setState(currentPattern, patternlab); + + //if file is named in the syntax for variants, add the variant data to memory. + //processPatternRecursive() will run find_pseudopatterns() to render with the variant data. if (ext === '.json' && filename.indexOf('~') > -1) { + try { + currentPattern.patternName = currentPattern.patternName.replace('~', '-'); + currentPattern.jsonFileData = fs.readJSONSync(file); + addPattern(currentPattern, patternlab); + } catch (err) { + console.log(err); + } return; } @@ -168,51 +437,69 @@ var pattern_assembler = function () { return; } - //see if this file has a state - setState(currentPattern, patternlab); + //add the raw template to memory + currentPattern.template = fs.readFileSync(file, 'utf8'); - //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 - } + //since we'll need to recognize ERB syntax tags, escape anything in the + //template that looks like ERB tags. the Pattern Lab docs do not forbid them, + //so it shouldn't be on users to avoid them, however extreme an edge case it + //may be. we'll unescape them later. + currentPattern.template = currentPattern.template.replace(/<%/g, '\\u003C\\u0025'); + currentPattern.template = currentPattern.template.replace(/%>/g, '\\u0025\\u003E'); - //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 - } + //define tmpTemplate and listitems to avoid undefined type errors + //trying to keep memory footprint small, so set it empty at first + currentPattern.tmpTemplate = ''; - //add the raw template to memory - currentPattern.template = fs.readFileSync(file, 'utf8'); + //needs to be empty object so buildListItems can work + currentPattern.listitems = {}; - //find any stylemodifiers that may be in the current pattern - currentPattern.stylePartials = findPartialsWithStyleModifiers(currentPattern); + //needs to be null so list_item_hunter.process_list_item_partials can work + currentPattern.listitemsRaw = null; - //find any pattern parameters that may be in the current pattern - currentPattern.parameteredPartials = findPartialsWithPatternParameters(currentPattern); + //needs to be initialized as {} so winnowUnusedTags can work + currentPattern.jsonFileData = {}; + + //this writes currentPattern.escapedTemplate which needs to be initialized + winnowUnusedTags(null, currentPattern, patternlab); + + //do the same with extendedTemplate + //skip this on underscore prefixed files, as they don't use extendedTemplate + //and since it will need to skip in processPatternRecursive + if (filename.charAt(0) !== '_') { + currentPattern.extendedTemplate = ''; + } + + //find pattern lineage + //TODO: consider repurposing lineage hunter. it currently only works at the + //iterative level, and isn't called upon any further. however, it could be + //repurposed to target and render only those files affected by a template edit. + //this could bring an enormous performance improvement on large projects. + lineage_hunter.find_lineage(currentPattern, patternlab); //add currentPattern to patternlab.patterns array addPattern(currentPattern, patternlab); } - function processPatternRecursive(file, patternlab) { - var lh = require('./lineage_hunter'), - ph = require('./parameter_hunter'), + /** + * Recurse through patternlab object as necessitated by partial inclusions. + * Build out the final output for writing to the public/patterns directory. + * + * @param {string} file The abspath of pattern being processed. + * @param {object} patternlab The patternlab object. + * @param {number|undefined} recursionLevel Top level === 0. Increments by 1 after that. + * @param {object|undefined} currentPatternAsParam Only submitted on recursionLevel > 0. + * @param {boolean|undefined} test When unit testing, pass in true to not output to file system. + */ + function processPatternRecursive(file, patternlab, recursionLevel, currentPatternAsParam, test) { + var fs = require('fs-extra'), + glob = require('glob'), + JSON5 = require('json5'), + path = require('path'); + + var ph = require('./parameter_hunter'), pph = require('./pseudopattern_hunter'), + lh = require('./lineage_hunter'), lih = require('./list_item_hunter'), smh = require('./style_modifier_hunter'); @@ -222,156 +509,246 @@ var pattern_assembler = function () { style_modifier_hunter = new smh(), pseudopattern_hunter = new pph(); - //find current pattern in patternlab object using var file as a key - var currentPattern, - i; + var currentPattern; + var i; // for the for loops + var paths = patternlab.config.paths; - for (i = 0; i < patternlab.patterns.length; i++) { - if (patternlab.patterns[i].abspath === file) { - currentPattern = patternlab.patterns[i]; - break; + if (!recursionLevel) { + + //find current pattern in patternlab object using var file as a key + currentPattern = getpatternbykey(file, patternlab); + + //return if processing an ignored file + if (!currentPattern || typeof currentPattern.extendedTemplate === 'undefined') { + return; } - } - //return if processing an ignored file - if (typeof currentPattern === 'undefined') { - return; - } + //output .json pseudoPattern variants to the file system and return. + //diveSync should process these variants after their originals, given that + //tildes come after periods in ASCII order. as such, their extendedTemplates + //should be filled out and renderable. + if (path.extname(file) === '.json') { + if (!test) { - currentPattern.extendedTemplate = currentPattern.template; + //pseudoPatterns skipped lineage hunt earlier so do it now + lineage_hunter.find_lineage(currentPattern, patternlab); + outputPatternToFS(currentPattern, patternlab); + } + return; + } - //find how many partials there may be for the given pattern - var foundPatternPartials = findPartials(currentPattern); + //continue with regular mustache templates + //look for a json file for this template + var jsonFilename; + var localJsonString; - if (foundPatternPartials !== null && foundPatternPartials.length > 0) { + //localData will get overwritten by mergeData, so keep it scoped to this function. + var localData = null; - if (patternlab.config.debug) { - console.log('found partials for ' + currentPattern.key); - } + try { - //find any listItem blocks - list_item_hunter.process_list_item_partials(currentPattern, patternlab); + //check if there is json data local to this pattern + jsonFilename = file.substr(0, file.lastIndexOf('.')) + '.json'; - //determine if the template contains any pattern parameters. if so they must be immediately consumed - parameter_hunter.find_parameters(currentPattern, patternlab); + //if so, load it into memory as a string (to create 2 non-referencing objects). + localJsonString = fs.readFileSync(jsonFilename); - //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'); + if (patternlab.config.debug) { + console.log('found pattern-specific data.json for ' + currentPattern.key); + } + } catch (err) { - var partialPath; + //do nothing + } - //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; + //set currentPattern.jsonFileData + if (localJsonString) { + try { + localData = JSON5.parse(localJsonString); + currentPattern.jsonFileData = mergeData(patternlab.data, JSON5.parse(localJsonString)); + } catch (err) { + console.log('There was an error parsing JSON for ' + file); + console.log(err); + } + + //if this pattern doesn't have a local .json file, save + //CPU steps by just creating a reference to the patternlab.data object. + } else { + currentPattern.jsonFileData = patternlab.data; + } + + var needle = currentPattern.subdir + '/' + currentPattern.fileName + '~*.json'; + var pseudoPatternFiles = glob.sync(needle, { + cwd: paths.source.patterns, + debug: false, + nodir: true + }); + var pseudoPatternsArray = []; + var pseudoPattern; + + if (pseudoPatternFiles.length) { + + for (i = 0; i < pseudoPatternFiles.length; i++) { + pseudoPattern = getpatternbykey(path.resolve(paths.source.patterns, pseudoPatternFiles[i]), patternlab); + if (pseudoPattern) { + pseudoPatternsArray.push(pseudoPattern); + + //update the localData object. + //pseudoPattern.jsonFileData set in processPatternIterative. + mergeData(pseudoPattern.jsonFileData, localData); } } + } - //recurse through nested partials to fill out this extended template. - processPatternRecursive(partialPath, patternlab); + var hasLocalListItems = false; + var listJsonFileName; + try { + var listJsonFileName = path.resolve(patternlab.config.paths.source.patterns, currentPattern.subdir, currentPattern.fileName + '.listitems.json'); + hasLocalListItems = fs.statSync(listJsonFileName); + } catch (err) { - //complete assembly of extended template - var partialPattern = getpatternbykey(partialKey, patternlab); + //do nothing + } + if (hasLocalListItems) { + try { + currentPattern.listitems = mergeData(patternlab.listitems, fs.readJSONSync(listJsonFileName)); + currentPattern.listitemsRaw = mergeData(patternlab.listitems, fs.readJSONSync(listJsonFileName)); + buildListItems(currentPattern); - //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); + if (patternlab.config.debug) { + console.log('found pattern-specific listitems.json for ' + currentPattern.key); + } + } catch (err) { + + //to troubleshoot malformed json + console.log(err); } + } - currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], partialPattern.extendedTemplate); + currentPattern.extendedTemplate = currentPattern.template; - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[currentPattern.key] = currentPattern.extendedTemplate; + //add localData keys to currentPattern.dataKeys + currentPattern.dataKeys = getDataKeys(localData, []); + + //add merged listitem keys to currentPattern.dataKeys + if (currentPattern.listitems) { + currentPattern.dataKeys = currentPattern.dataKeys.concat(getDataKeys(currentPattern.listitems, currentPattern.dataKeys)); } + //set currentPattern for recursionLevel > 0 } else { - //find any listItem blocks that within the pattern, even if there are no partials - list_item_hunter.process_list_item_partials(currentPattern, patternlab); + currentPattern = currentPatternAsParam; } - //find pattern lineage - lineage_hunter.find_lineage(currentPattern, patternlab); + //find parametered partials + var parameteredPartials = findPartialsWithPatternParameters(currentPattern.extendedTemplate); - //add to patternlab object so we can look these up later. - addPattern(currentPattern, patternlab); + //if the template contains any pattern parameters, recurse through them + if (parameteredPartials && parameteredPartials.length) { + if (patternlab.config.debug) { + console.log('found parametered partials for ' + currentPattern.key); + } - //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); - } + if (!recursionLevel && currentPattern.extendedTemplate === currentPattern.template) { + currentPattern.extendedTemplate = winnowUnusedTags(currentPattern.escapedTemplate, currentPattern); + } - function mergeData(obj1, obj2) { - if (typeof obj2 === 'undefined') { - obj2 = {}; //eslint-disable-line no-param-reassign + //recursively render currentPattern.extendedTemplate via parameter_hunter.find_parameters() + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); + + //recurse, going a level deeper, with each render eliminating nested parameteredPartials + //when there are no more nested parameteredPartials, we'll pop back up + processPatternRecursive(currentPattern.abspath, patternlab, recursionLevel ? recursionLevel + 1 : 1, currentPattern); } - 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]); + //find non-parametered partials. + var foundPatternPartials = findPartials(currentPattern.extendedTemplate); + var uniquePartials = []; - // 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]; - } + //recurse through non-parametered partials + if (foundPatternPartials && foundPatternPartials.length) { + if (patternlab.config.debug) { + console.log('found partials for ' + currentPattern.key); } - } - return obj2; - } - function parseDataLinksHelper(patternlab, obj, key) { - var JSON5 = require('json5'); - var linkRE, dataObjAsString, linkMatches, expandedLink; + //copy winnowed template to extendedTemplate + if (!recursionLevel && currentPattern.extendedTemplate === currentPattern.template) { + currentPattern.extendedTemplate = winnowUnusedTags(currentPattern.escapedTemplate, currentPattern); + } - linkRE = /link\.[A-z0-9-_]+/g; - dataObjAsString = JSON5.stringify(obj); - linkMatches = dataObjAsString.match(linkRE); + for (i = 0; i < foundPatternPartials.length; i++) { - 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); + //limit iteration to one time per partial. eliminate duplicates. + if (uniquePartials.indexOf(foundPatternPartials[i]) === -1) { + uniquePartials.push(foundPatternPartials[i]); + } else { + continue; + } + + var partialKey = foundPatternPartials[i].replace(/{{>([ ])?([\w\-\.\/~]+)(:[A-z0-9-_|]+)?(?:\:[A-Za-z0-9-_]+)?(?:(| )\(.*)?([ ])?}}/g, '$2'); + + //identify which pattern this partial corresponds to + var partialPattern = getpatternbykey(partialKey, patternlab); + + if (!partialPattern) { + throw 'Could not find pattern with key ' + partialKey; + } else { + partialPattern.tmpTemplate = partialPattern.escapedTemplate; + + //if the current tag has styleModifier data, replace the styleModifier value in the partial + if (findPartialsWithStyleModifiers(foundPatternPartials[i])) { + style_modifier_hunter.consume_style_modifier(partialPattern, foundPatternPartials[i], patternlab); } - dataObjAsString = dataObjAsString.replace(linkMatches[i], expandedLink); + + var winnowedPartial = winnowUnusedTags(partialPattern.tmpTemplate, currentPattern); + + partialPattern.tmpTemplate = winnowedPartial; + + //replace each partial tag with the partial's template. + //escape regex special characters as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Using_special_characters + var escapedPartial = foundPatternPartials[i].replace(/[.*+?^${}()|[\]\\\/]/g, '\\$&'); + var regex = new RegExp(escapedPartial, 'g'); + currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(regex, partialPattern.tmpTemplate); + + //free tmpTemplate from memory. + partialPattern.tmpTemplate = ''; } } - } - var dataObj; - try { - dataObj = JSON5.parse(dataObjAsString); - } catch (err) { - console.log('There was an error parsing JSON for ' + key); - console.log(err); + //recurse, going a level deeper, with each render eliminating nested partials + //when there are no more nested partials, we'll pop back up + processPatternRecursive(currentPattern.abspath, patternlab, recursionLevel ? recursionLevel + 1 : 1, currentPattern); } - return dataObj; - } + //do only when popped back to the top level of recursion + if (!recursionLevel) { - //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 + //switch Mustache escaped as ERB back to Mustache + if (currentPattern.extendedTemplate !== currentPattern.template) { + currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(/<%((?:.|\s)*?)%>/g, '{{$1}}'); + } - patternlab.data = parseDataLinksHelper(patternlab, patternlab.data, 'data.json'); + //switch escaped unicodes for ERB back to ERB + currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(/\\u003C\\u0025/g, '<%'); + currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(/\\u0025\\u003E/g, '%>'); - //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); + //find and process any listItem blocks within the pattern + list_item_hunter.process_list_item_partials(currentPattern, patternlab); + + //look through pseudoPatternsArray again, and update their patternlab objects + if (pseudoPatternsArray.length) { + pseudopattern_hunter.find_pseudopatterns(currentPattern, patternlab, pseudoPatternsArray); + } + + //output rendered pattern to the file system + if (!test) { + outputPatternToFS(currentPattern, patternlab); + } + + //unset all tmpTemplates + for (i = 0; i < patternlab.patterns.length; i++) { + patternlab.patterns[i].tmpTemplate = ''; + } } } @@ -397,11 +774,11 @@ var pattern_assembler = function () { renderPattern: function (template, data, partials) { return renderPattern(template, data, partials); }, - process_pattern_iterative: function (file, patternlab) { - processPatternIterative(file, patternlab); + combine_listItems: function (patternlab) { + buildListItems(patternlab); }, - process_pattern_recursive: function (file, patternlab, additionalData) { - processPatternRecursive(file, patternlab, additionalData); + get_data_keys: function (data, uniqueKeys) { + return getDataKeys(data, uniqueKeys); }, get_pattern_by_key: function (key, patternlab) { return getpatternbykey(key, patternlab); @@ -409,16 +786,26 @@ var pattern_assembler = function () { merge_data: function (existingData, newData) { return mergeData(existingData, newData); }, - combine_listItems: function (patternlab) { - buildListItems(patternlab); + parse_data_links_helper: function (patternlab, obj, key) { + return parseDataLinksHelper(patternlab, obj, key); }, parse_data_links: function (patternlab) { parseDataLinks(patternlab); + }, + winnow_unused_tags: function (template, pattern) { + return winnowUnusedTags(template, pattern); + }, + process_pattern_iterative: function (file, patternlab) { + processPatternIterative(file, patternlab); + }, + + //only submit all 5 params within the function itself and in unit tests. + //when calling outside of these circumstances, only submit file and patternlab. + process_pattern_recursive: function (file, patternlab, recursionLevel, currentPatternAsParam, test) { + processPatternRecursive(file, patternlab, recursionLevel, currentPatternAsParam, test); } }; }; module.exports = pattern_assembler; - - diff --git a/core/lib/patternlab.js b/core/lib/patternlab.js index b0192c8dd..facde3c1e 100644 --- a/core/lib/patternlab.js +++ b/core/lib/patternlab.js @@ -17,10 +17,10 @@ var patternlab_engine = function (config) { diveSync = require('diveSync'), of = require('./object_factory'), pa = require('./pattern_assembler'), + lh = require('./lineage_hunter'), + lih = require('./list_item_hunter'), mh = require('./media_hunter'), pe = require('./pattern_exporter'), - lh = require('./lineage_hunter'), - he = require('html-entities').AllHtmlEntities, patternlab = {}; patternlab.package = fs.readJSONSync('./package.json'); @@ -74,32 +74,26 @@ var patternlab_engine = function (config) { 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(); + //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; + var pattern_assembler = new pa(), - entity_encoder = new he(), + list_item_hunter = new lih(), pattern_exporter = new pe(), lineage_hunter = new lh(), patterns_dir = paths.source.patterns; - pattern_assembler.combine_listItems(patternlab); + patternlab.dataKeys = pattern_assembler.get_data_keys(patternlab.data, []); + patternlab.dataKeys = patternlab.dataKeys.concat(list_item_hunter.get_list_item_iteration_keys()); + patternlab.dataKeys = patternlab.dataKeys.concat(pattern_assembler.get_data_keys(patternlab.listitems, [])); //diveSync once to perform iterative populating of patternlab object 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) { //log any errors if (err) { @@ -110,32 +104,13 @@ var patternlab_engine = function (config) { } ); - //diveSync again to recursively include partials, filling out the - //extendedTemplate property of the patternlab.patterns elements - 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) { - //log any errors - if (err) { - console.log(err); - return; - } - pattern_assembler.process_pattern_recursive(path.resolve(file), patternlab); - }); + patternlab.data = pattern_assembler.parse_data_links_helper(patternlab, patternlab.data, 'data.json'); //set user defined head and foot if they exist + var userHeader; try { - patternlab.userHead = pattern_assembler.get_pattern_by_key('atoms-head', patternlab); + userHeader = pattern_assembler.get_pattern_by_key('atoms-head', patternlab); + patternlab.userHead = JSON.parse(JSON.stringify(userHeader)); } catch (ex) { if (patternlab.config.debug) { @@ -143,8 +118,20 @@ var patternlab_engine = function (config) { console.log('Could not find optional user-defined header, atoms-head pattern. It was likely deleted.'); } } + + if (patternlab.userHead) { + patternlab.userHead.extendedTemplate = patternlab.userHead.template.replace('{% pattern-lab-head %}', patternlab.header); + } else { + patternlab.userHead = { + template: patternlab.header, + extendedTemplate: patternlab.header + }; + } + + var userFooter; try { - patternlab.userFoot = pattern_assembler.get_pattern_by_key('atoms-foot', patternlab); + userFooter = pattern_assembler.get_pattern_by_key('atoms-foot', patternlab); + patternlab.userFoot = JSON.parse(JSON.stringify(userFooter)); } catch (ex) { if (patternlab.config.debug) { @@ -153,9 +140,14 @@ var patternlab_engine = function (config) { } } - //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); + if (patternlab.userFoot) { + patternlab.userFoot.extendedTemplate = patternlab.userFoot.template.replace('{% pattern-lab-foot %}', patternlab.footerPattern + patternlab.footer); + } else { + patternlab.userFoot = { + template: patternlab.footerPattern, + extendedTemplate: patternlab.footerPattern + }; + } //cascade any patternStates lineage_hunter.cascade_pattern_states(patternlab); @@ -165,69 +157,19 @@ var patternlab_engine = function (config) { 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) { - - pattern.header = head; - - //json stringify lineage and lineageR - var lineageArray = []; - for (var i = 0; i < pattern.lineage.length; i++) { - lineageArray.push(JSON5.stringify(pattern.lineage[i])); - } - pattern.lineage = lineageArray; - - var lineageRArray = []; - for (var i = 0; i < pattern.lineageR.length; i++) { - lineageRArray.push(JSON5.stringify(pattern.lineageR[i])); - } - pattern.lineageR = lineageRArray; - - //render the pattern, but first consolidate any data we may have - var allData; - try { - allData = JSON5.parse(JSON5.stringify(patternlab.data)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.abspath); - console.log(err); - } - 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); - - //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); + //diveSync again to recursively include partials, filling out the + //extendedTemplate property of the patternlab.patterns elements + diveSync( + patterns_dir, + function (err, file) { + //log any errors + if (err) { + console.log(err); + return; + } + pattern_assembler.process_pattern_recursive(path.resolve(file), patternlab); } - - //write the compiled template to the public patterns directory - 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)); - - //write the encoded version too - fs.outputFileSync(paths.public.patterns + pattern.patternLink.replace('.html', '.escaped.html'), entity_encoder.encode(pattern.patternPartial)); - }); + ); //export patterns if necessary pattern_exporter.export_patterns(patternlab); @@ -268,45 +210,33 @@ var patternlab_engine = function (config) { //find mediaQueries media_hunter.find_media_queries('./source/css', patternlab); - // 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; + } + // check if patterns are excluded, if not add them to styleguidePatterns + var isExcluded = false; + if (styleGuideExcludes && styleGuideExcludes.length) { 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]); - } + isExcluded = (styleGuideExcludes.indexOf(typeKey) > -1); } - } else { - 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; - } + + if (!isExcluded) { styleguidePatterns.push(patternlab.patterns[i]); } } - //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 mainPageHead = patternlab.userHead.template.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 mainPageFoot = patternlab.userFoot.template.replace('{% pattern-lab-foot %}', patternlab.footer); var mainPageFootHtml = pattern_assembler.renderPattern(mainPageFoot, patternlab.data); //build the styleguide @@ -315,11 +245,18 @@ var patternlab_engine = function (config) { fs.outputFileSync(path.resolve(paths.public.styleguide, 'html/styleguide.html'), mainPageHeadHtml + styleguideHtml + mainPageFootHtml); + //unset styleguidePatterns + styleguidePatterns = []; + //build the viewall pages var prevSubdir = '', prevGroup = ''; for (i = 0; i < patternlab.patterns.length; i++) { + + //unset escapedTemplates + patternlab.patterns[i].escapedTemplate = ''; + // skip underscore-prefixed files if (isPatternExcluded(patternlab.patterns[i])) { if (patternlab.config.debug) { @@ -384,6 +321,9 @@ var patternlab_engine = function (config) { 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); } + + //unset viewAllPatterns + viewAllPatterns = []; } //build the patternlab website @@ -394,6 +334,11 @@ var patternlab_engine = function (config) { for (i = 0; i < patternlab.patterns.length; i++) { var pattern = patternlab.patterns[i]; + + //unset all pattern.patternPartials here since they take up a lot of memory + //and we won't be needing them again + pattern.patternPartial = ''; + var bucketName = pattern.name.replace(/\\/g, '-').split('-')[1]; //check if the bucket already exists @@ -606,7 +551,6 @@ var patternlab_engine = function (config) { printDebug(); } }; - }; module.exports = patternlab_engine; diff --git a/core/lib/pseudopattern_hunter.js b/core/lib/pseudopattern_hunter.js index 5254c12fb..08da37cc0 100644 --- a/core/lib/pseudopattern_hunter.js +++ b/core/lib/pseudopattern_hunter.js @@ -12,62 +12,40 @@ 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'); + function findpseudopatterns(currentPattern, patternlab, pseudoPatternsArray) { + var pa = require('./pattern_assembler'), + lh = require('./lineage_hunter'); 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); + for (var i = 0; i < pseudoPatternsArray.length; i++) { + if (patternlab.config.debug) { + console.log('found pseudoPattern variant of ' + currentPattern.key); + } - //see if this file has a state - pattern_assembler.setPatternState(patternVariant, patternlab); + //extend any existing data with variant data. + pattern_assembler.merge_data(currentPattern.jsonFileData, pseudoPatternsArray[i].jsonFileData); - //use the same template as the non-variant - patternVariant.template = currentPattern.template; - patternVariant.extendedTemplate = currentPattern.extendedTemplate; + //see if this file has a state + pattern_assembler.setPatternState(pseudoPatternsArray[i], patternlab); - //find pattern lineage - lineage_hunter.find_lineage(patternVariant, patternlab); + //use the same template as the non-variant + pseudoPatternsArray[i].template = currentPattern.template; + pseudoPatternsArray[i].extendedTemplate = currentPattern.extendedTemplate; - //add to patternlab object so we can look these up later. - pattern_assembler.addPattern(patternVariant, patternlab); - } + //find pattern lineage + //TODO: consider repurposing lineage hunter. it currently only works at the + //iterative level, and isn't called upon any further. however, it could be + //repurposed to target and render only those files affected by a template edit. + //this could bring an enormous performance improvement on large projects. + lineage_hunter.find_lineage(pseudoPatternsArray[i], patternlab); } } return { - find_pseudopatterns: function (pattern, patternlab) { - findpseudopatterns(pattern, patternlab); + find_pseudopatterns: function (pattern, patternlab, pseudoPatternsArray) { + findpseudopatterns(pattern, patternlab, pseudoPatternsArray); } }; diff --git a/core/lib/style_modifier_hunter.js b/core/lib/style_modifier_hunter.js index 99d258337..cf56e1196 100644 --- a/core/lib/style_modifier_hunter.js +++ b/core/lib/style_modifier_hunter.js @@ -25,10 +25,7 @@ var style_modifier_hunter = function () { } //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; + pattern.tmpTemplate = pattern.tmpTemplate.replace(/{{[ ]?styleModifier[ ]?}}/i, styleModifier); } } diff --git a/core/templates/pattern-header-footer/header.html b/core/templates/pattern-header-footer/header.html index ebe81993e..c607380ef 100644 --- a/core/templates/pattern-header-footer/header.html +++ b/core/templates/pattern-header-footer/header.html @@ -1,9 +1,11 @@ + diff --git a/package.gulp.json b/package.gulp.json index fc73824ed..16add5faa 100644 --- a/package.gulp.json +++ b/package.gulp.json @@ -8,9 +8,9 @@ "diveSync": "^0.3.0", "fs-extra": "^0.26.5", "glob": "^7.0.0", + "hogan.js": "^3.0.2", "html-entities": "^1.2.0", - "json5": "^0.5.0", - "mustache": "^2.2.1" + "json5": "^0.5.0" }, "devDependencies": { "browser-sync": "^2.11.1", diff --git a/package.json b/package.json index 628657c39..fb3e88cdb 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,10 @@ "diveSync": "^0.3.0", "fs-extra": "^0.26.2", "glob": "^7.0.0", + "hogan.js": "^3.0.2", "html-entities": "^1.2.0", "json5": "^0.5.0", - "matchdep": "^1.0.0", - "mustache": "^2.2.0" + "matchdep": "^1.0.0" }, "devDependencies": { "bs-html-injector": "^3.0.0", diff --git a/source/_patterns/01-molecules/07-messaging/00-alert.mustache b/source/_patterns/01-molecules/07-messaging/00-alert.mustache index b5e479b31..a28ce7ffe 100644 --- a/source/_patterns/01-molecules/07-messaging/00-alert.mustache +++ b/source/_patterns/01-molecules/07-messaging/00-alert.mustache @@ -1,3 +1,3 @@
{{ excerpt.short }} -
\ No newline at end of file + diff --git a/test/files/_patterns/00-test/01-bar.mustache b/test/files/_patterns/00-test/01-bar.mustache index 5716ca598..08573a42b 100644 --- a/test/files/_patterns/00-test/01-bar.mustache +++ b/test/files/_patterns/00-test/01-bar.mustache @@ -1 +1 @@ -bar +{{message}}bar diff --git a/test/files/_patterns/00-test/02-listitems.mustache b/test/files/_patterns/00-test/02-listitems.mustache new file mode 100644 index 000000000..89ced8929 --- /dev/null +++ b/test/files/_patterns/00-test/02-listitems.mustache @@ -0,0 +1,2 @@ +{{#listItems.one}}{{> 00-test/00-foo }}{{/listItems.one}} +{{#listItems.one}}{{> 00-test/01-bar.mustache }}{{/listItems.one}} diff --git a/test/files/_patterns/00-test/12-parameter-partial.mustache b/test/files/_patterns/00-test/12-parameter-partial.mustache new file mode 100644 index 000000000..e3799746a --- /dev/null +++ b/test/files/_patterns/00-test/12-parameter-partial.mustache @@ -0,0 +1,6 @@ +{{> test-bar }} +{{# flag}} + {{> test-styled-atom:baz(message: 'foo') }} + {{> test-bar }} +{{/ flag}} +{{> test-bar }} diff --git a/test/files/_patterns/00-test/13-another-parameter-partial.mustache b/test/files/_patterns/00-test/13-another-parameter-partial.mustache new file mode 100644 index 000000000..34856e8b1 --- /dev/null +++ b/test/files/_patterns/00-test/13-another-parameter-partial.mustache @@ -0,0 +1 @@ +{{> test-parameter-partial(flag: true) }} \ No newline at end of file diff --git a/test/files/_patterns/00-test/14-template-with-erb-tag.mustache b/test/files/_patterns/00-test/14-template-with-erb-tag.mustache new file mode 100644 index 000000000..a36df1e4f --- /dev/null +++ b/test/files/_patterns/00-test/14-template-with-erb-tag.mustache @@ -0,0 +1,2 @@ +{{> test-parameter-partial(flag: true) }} +<% erb %> diff --git a/test/list_item_hunter_tests.js b/test/list_item_hunter_tests.js index eb3ade6cf..8eb2c953d 100644 --- a/test/list_item_hunter_tests.js +++ b/test/list_item_hunter_tests.js @@ -2,8 +2,9 @@ "use strict"; var lih = require('../core/lib/list_item_hunter'); - var pa = require('../core/lib/pattern_assembler'); var object_factory = require('../core/lib/object_factory'); + var pa = require('../core/lib/pattern_assembler'); + var path = require('path'); exports['list_item_hunter'] = { 'process_list_item_partials finds and outputs basic repeating blocks' : function(test){ @@ -18,26 +19,17 @@ var patternlab = { "listitems": { - "1": [ - { - "title": "Foo" - } - ], - "2": [ - { - "title": "Foo" - }, - { - "title": "Bar" - } - ] + "1": { + "title": "Foo" + }, + "2": { + "title": "Bar" + } }, "data": { - "link": {}, - "partials": [] + "link": {} }, - "config": {"debug": false}, - "partials" : {} + "config": {"debug": false} }; var list_item_hunter = new lih(); @@ -46,7 +38,7 @@ list_item_hunter.process_list_item_partials(currentPattern, patternlab); //assert - test.equals(currentPattern.extendedTemplate, "FooBar" ); + test.ok(currentPattern.extendedTemplate.match(/(FooBar|BarFoo)/)); test.done(); }, @@ -63,26 +55,17 @@ var patternlab = { "listitems": { - "1": [ - { - "title": "Foo" - } - ], - "2": [ - { - "title": "Foo" - }, - { - "title": "Bar" - } - ] + "1": { + "title": "Foo" + }, + "2": { + "title": "Bar" + } }, "data": { - "link": {}, - "partials": [] + "link": {} }, - "config": {"debug": false}, - "partials" : {} + "config": {"debug": false} }; var list_item_hunter = new lih(); @@ -91,130 +74,63 @@ list_item_hunter.process_list_item_partials(currentPattern, patternlab); //assert - test.equals(currentPattern.extendedTemplate, "FooBar" ); + test.ok(currentPattern.extendedTemplate.match(/(FooBar|BarFoo)/)); test.done(); }, 'process_list_item_partials finds partials and outputs repeated renders' : function(test){ - //arrange - //setup current pattern from what we would have during execution - var currentPattern = { - "template": "{{#listItems.two}}{{ title }}{{/listItems.two}}", - "extendedTemplate" : "{{#listItems.two}}{{> test-simple }}{{/listItems.two}}", - "key": "test-patternName", - "jsonFileData" : {} - }; + //will test recursion and verbose partial inclusion syntax + var fs = require('fs-extra'); + var pattern_assembler = new pa(); + var patterns_dir = './test/files/_patterns'; - var patternlab = { - "listitems": { - "1": [ - { - "title": "Foo" - } - ], - "2": [ - { - "title": "Foo" - }, - { - "title": "Bar" - } - ] - }, - "data": { - "link": {}, - "partials": [] - }, - "config": {"debug": false}, - "patterns": [ - { - "template": "{{ title }}", - "extendedTemplate" : "{{ title }}", - "key": "test-simple", - "jsonFileData" : {} + var pl = {}; + pl.config = { + paths: { + source: { + patterns: patterns_dir } - ], - "partials" : {} + } }; - - var list_item_hunter = new lih(); - - //act - list_item_hunter.process_list_item_partials(currentPattern, patternlab); - - //assert - test.equals(currentPattern.extendedTemplate, "FooBar" ); - - test.done(); - }, - - 'process_list_item_partials finds verbose partials and outputs repeated renders' : function(test){ - var pattern1 = { - "template": "{{#listItems.one}}{{> 00-test/00-foo }}{{/listItems.one}}", - "extendedTemplate" : "{{#listItems.one}}{{> 00-test/00-foo }}{{/listItems.one}}", - "key": "test-foo", - "jsonFileData" : {} + pl.data = {}; + pl.data.link = {}; + pl.dataKeys = ['one', 'message']; + pl.config.debug = false; + pl.patterns = []; + pl.config.patterns = { source: patterns_dir}; + pl.listitems = { + "1": { + "message": "Foo" + }, + "2": { + "message": "Bar" + } }; - var pattern2 = { - "template": "{{#listItems.two}}{{> 00-test/01-bar.mustache }}{{/listItems.two}}", - "extendedTemplate" : "{{#listItems.two}}{{> 00-test/01-bar.mustache }}{{/listItems.two}}", - "key": "test-bar", - "jsonFileData" : {} - }; + var fooFile = path.resolve('test/files/_patterns/00-test/00-foo.mustache'); + var barFile = path.resolve('test/files/_patterns/00-test/01-bar.mustache'); + var listitemsFile = path.resolve('test/files/_patterns/00-test/02-listitems.mustache'); - var patternlab = { - "listitems": { - "1": [ - { - "title": "Foo" - } - ], - "2": [ - { - "title": "Foo" - }, - { - "title": "Bar" - } - ] - }, - "data": { - "link": {}, - "partials": [] - }, - "config": {"debug": false}, - "patterns": [ - { - "template": "{{ title }}", - "extendedTemplate" : "{{ title }}", - "subdir": "00-test", - "fileName": "00-foo", - "jsonFileData" : {}, - "key": "test-foo", - }, - { - "template": "{{ title }}", - "extendedTemplate" : "{{ title }}", - "subdir": "00-test", - "fileName": "01-bar", - "jsonFileData" : {}, - "key": "test-bar", - } - ], - "partials" : {} - }; + //the contents of these files: + //00-foo.mustache: + // {{> test-bar }} + //01-bar.mustache: + // {{message}}bar + //02-listitems.mustache + // {{#listItems.one}}{{> 00-test/00-foo }}{{/listItems.one}} + // {{#listItems.one}}{{> 00-test/01-bar.mustache }}{{/listItems.one}} - var list_item_hunter = new lih(); + pattern_assembler.process_pattern_iterative(fooFile, pl); + pattern_assembler.process_pattern_iterative(barFile, pl); + pattern_assembler.process_pattern_iterative(listitemsFile, pl); //act - list_item_hunter.process_list_item_partials(pattern1, patternlab); - list_item_hunter.process_list_item_partials(pattern2, patternlab); + pattern_assembler.process_pattern_recursive(listitemsFile, pl, 0, null, true); + var listitemsPattern = pattern_assembler.get_pattern_by_key(listitemsFile, pl); //assert - test.equals(pattern1.extendedTemplate, "Foo" ); - test.equals(pattern2.extendedTemplate, "FooBar" ); + test.ok(listitemsPattern.extendedTemplate.replace(/\n/g, '').match(/(FoobarFoobar|BarbarBarbar)/)); test.done(); }, @@ -223,41 +139,32 @@ //arrange //setup current pattern from what we would have during execution var currentPattern = { - "template": "{{#listItems.two}}{{ title }}{{/listItems.two}}", - "extendedTemplate" : "{{#listItems.two}}{{> test-simple }}{{/listItems.two}}", - "key": "test-patternName", - "jsonFileData" : {}, - "listitems" : { - "2": [ - { - "title": "One" - }, - { - "title": "Two" - }, - ] + "template": "{{#listItems.two}}{{ title }}{{/listItems.two}}", + "extendedTemplate" : "{{#listItems.two}}{{ title }}{{/listItems.two}}", + "key": "test-patternName1", + "jsonFileData" : {}, + "listitems": {}, + "listitemsRaw": { + "1": { + "title": "One" + }, + "2": { + "title": "Two" } + } }; var patternlab = { - "listitems": { - "1": [ - { - "title": "Foo" - } - ], - "2": [ - { - "title": "Foo" - }, - { - "title": "Bar" - } - ] + "listitems" : { + "1": { + "title": "Foo" + }, + "2": { + "title": "Bar" + } }, "data": { - "link": {}, - "partials": [] + "link": {} }, "config": {"debug": false}, "patterns": [ @@ -267,8 +174,7 @@ "key": "test-simple", "jsonFileData" : {} } - ], - "partials" : {} + ] }; var list_item_hunter = new lih(); @@ -277,7 +183,7 @@ list_item_hunter.process_list_item_partials(currentPattern, patternlab); //assert - test.equals(currentPattern.extendedTemplate, "OneTwo" ); + test.ok(currentPattern.extendedTemplate.match(/OneTwo|TwoOne/)); test.done(); }, @@ -286,41 +192,26 @@ //arrange //setup current pattern from what we would have during execution var currentPattern = { - "template": "{{#listItems.one}}{{ title }}{{/listItems.one}}", - "extendedTemplate" : "{{#listItems.one}}{{> test-simple }}{{/listItems.one}}", - "key": "test-patternName", - "jsonFileData" : {}, - "listitems" : { - "2": [ - { - "title": "One" - }, - { - "title": "Two" - }, - ] + "template": "{{#listItems.one}}{{ title }}{{/listItems.one}}", + "extendedTemplate" : "{{#listItems.one}}{{ title }}{{/listItems.one}}", + "key": "test-patternName", + "jsonFileData" : {}, + "listitems": {}, + "listitemsRaw": { + "1": { + "number": "One" } + } }; var patternlab = { "listitems": { - "1": [ - { - "title": "Foo" - } - ], - "2": [ - { - "title": "Foo" - }, - { - "title": "Bar" - } - ] + "1": { + "title": "Foo" + } }, "data": { - "link": {}, - "partials": [] + "link": {} }, "config": {"debug": false}, "patterns": [ @@ -330,13 +221,14 @@ "key": "test-simple", "jsonFileData" : {} } - ], - "partials" : {} + ] }; var list_item_hunter = new lih(); + var pattern_assembler = new pa(); //act + currentPattern.listitemsRaw = pattern_assembler.merge_data(patternlab.listitems, currentPattern.listitemsRaw); list_item_hunter.process_list_item_partials(currentPattern, patternlab); //assert @@ -349,41 +241,32 @@ //arrange //setup current pattern from what we would have during execution var currentPattern = { - "template": "{{#listItems.one}}{{ title }}{{/listItems.one}}", - "extendedTemplate" : "{{#listItems.one}}{{> test-simple }}{{/listItems.one}}", - "key": "test-patternName", - "jsonFileData" : {}, - "listitems" : { - "1": [ - { - "title": "One" - } - ], - "2": [ - { - "title": "One" - }, - { - "title": "Two" - }, - ] + "template": "{{#listItems.two}}{{ number }}{{/listItems.two}}", + "extendedTemplate" : "{{#listItems.two}}{{ number }}{{/listItems.two}}", + "key": "test-patternName", + "jsonFileData" : {}, + "listitems": {}, + "listitemsRaw" : { + "1": { + "number": "One" + }, + "2": { + "number": "Two" } + } }; var patternlab = { "listitems": { - "2": [ - { - "title": "Foo" - }, - { - "title": "Bar" - } - ] + "1": { + "title": "Foo" + }, + "2": { + "title": "Bar" + } }, "data": { - "link": {}, - "partials": [] + "link": {} }, "config": {"debug": false}, "patterns": [ @@ -393,17 +276,18 @@ "key": "test-simple", "jsonFileData" : {} } - ], - "partials" : {} + ] }; var list_item_hunter = new lih(); + var pattern_assembler = new pa(); //act + currentPattern.listitemsRaw = pattern_assembler.merge_data(patternlab.listitems, currentPattern.listitemsRaw); list_item_hunter.process_list_item_partials(currentPattern, patternlab); //assert - test.equals(currentPattern.extendedTemplate, "One" ); + test.ok(currentPattern.extendedTemplate.match(/(OneTwo|TwoOne)/)); test.done(); }, @@ -416,48 +300,41 @@ var patterns_dir = './test/files/_patterns'; var pl = {}; - pl.config = {}; + pl.config = { + paths: { + source: { + patterns: patterns_dir + } + } + }; pl.data = {}; pl.data.link = {}; + pl.dataKeys = ['two', 'message']; pl.config.debug = false; pl.patterns = []; - pl.partials = {}; pl.config.patterns = { source: patterns_dir}; pl.listitems = { - "1": [ - { - "message": "Foo" - } - ], - "2": [ - { - "message": "Foo" - }, - { - "message": "Bar" - } - ] + "1": { + "message": "Foo" + }, + "2": { + "message": "Bar" + } }; - var atomPattern = new object_factory.oPattern('test/files/_patterns/00-test/03-styled-atom.mustache', '00-test', '03-styled-atom.mustache'); - atomPattern.template = fs.readFileSync(patterns_dir + '/00-test/03-styled-atom.mustache', 'utf8'); - atomPattern.extendedTemplate = atomPattern.template; - atomPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(atomPattern); - - var bookendPattern = new object_factory.oPattern('test/files/_patterns/00-test/11-bookend-listitem.mustache', '00-test', '11-bookend-listitem.mustache'); - bookendPattern.template = fs.readFileSync(patterns_dir + '/00-test/11-bookend-listitem.mustache', 'utf8'); - bookendPattern.extendedTemplate = bookendPattern.template; - bookendPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(bookendPattern); + var atomFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var bookendFile = path.resolve('test/files/_patterns/00-test/11-bookend-listitem.mustache'); - pl.patterns.push(atomPattern); - pl.patterns.push(bookendPattern); + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(bookendFile, pl); //act - list_item_hunter.process_list_item_partials(bookendPattern, pl); + pattern_assembler.process_pattern_recursive(bookendFile, pl, 0, null, true); + var bookendPattern = pattern_assembler.get_pattern_by_key(bookendFile, pl); //assert. here we expect {{styleModifier}} to be replaced with an empty string or the styleModifier value from the found partial with the :styleModifier - var expectedValue = '
Foo Foo Foo Bar Bar Bar
'; - test.equals(bookendPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); + var expectedValue = /
(Foo|Bar) <\/span> (Foo|Bar) <\/span> (Foo|Bar) <\/span> (Foo|Bar) <\/span> (Foo|Bar) <\/span> (Foo|Bar) <\/span> <\/div>/; + test.ok(bookendPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim().match(expectedValue)); test.done(); } diff --git a/test/parameter_hunter_tests.js b/test/parameter_hunter_tests.js index ce6c75a3c..b5fe46066 100644 --- a/test/parameter_hunter_tests.js +++ b/test/parameter_hunter_tests.js @@ -16,7 +16,9 @@ "flatPatternPath": "02-organisms-02-comments", "key": "organisms-sticky-comment", "template": "{{> molecules-single-comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}", + "escapedTemplate": "{{> molecules-single-comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}", "extendedTemplate": "{{> molecules-single-comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}", + "dataKeys": [], "parameteredPartials": [ "{{> molecules-single-comment(description: 'We are all in the gutter, but some of us are looking at the stars.') }}", "{{> molecules-single-comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}" @@ -38,6 +40,7 @@ "flatPatternPath": "01-molecules-06-components", "key": "molecules-single-comment", "template": "

{{description}}

", + "escapedTemplate": "

{{description}}

", "extendedTemplate": "

{{description}}

" } ], @@ -46,8 +49,7 @@ }, data: { description: 'Not a quote from a smart man' - }, - partials: {} + } } }; @@ -57,7 +59,7 @@ var patternlab = patternlabClosure(); var parameter_hunter = new ph(); - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, currentPattern.parameteredPartials); test.equals(currentPattern.extendedTemplate, '

A life is like a garden. Perfect moments can be had, but not preserved, except in memory.

'); test.done(); @@ -67,13 +69,15 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var pa = require('../core/lib/pattern_assembler'); + var pattern_assembler = new pa(); - patternlab.patterns[0].template = "

{{foo}}

{{description}}

"; - patternlab.patterns[0].extendedTemplate = patternlab.patterns[0].template; + currentPattern.escapedTemplate = '

{{foo}}

' + currentPattern.template; + currentPattern.extendedTemplate = currentPattern.escapedTemplate; patternlab.data.foo = 'Bar'; - patternlab.data.description = 'Baz'; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, currentPattern.parameteredPartials); + currentPattern.extendedTemplate = pattern_assembler.renderPattern(currentPattern.extendedTemplate, patternlab.data); test.equals(currentPattern.extendedTemplate, '

Bar

A life is like a garden. Perfect moments can be had, but not preserved, except in memory.

'); test.done(); @@ -83,13 +87,15 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> 01-molecules/06-components/02-single-comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = "{{> 01-molecules/06-components/02-single-comment(description: 'We are all in the gutter, but some of us are looking at the stars.') }}"; - currentPattern.parameteredPartials[1] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = "{{> 01-molecules/06-components/02-single-comment(description: 'We are all in the gutter, but some of us are looking at the stars.') }}"; + parameteredPartials[1] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

A life is like a garden. Perfect moments can be had, but not preserved, except in memory.

'); test.done(); @@ -99,13 +105,15 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> 01-molecules/06-components/02-single-comment.mustache(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = "{{> 01-molecules/06-components/02-single-comment.mustache(description: 'We are all in the gutter, but some of us are looking at the stars.') }}"; - currentPattern.parameteredPartials[1] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = "{{> 01-molecules/06-components/02-single-comment.mustache(description: 'We are all in the gutter, but some of us are looking at the stars.') }}"; + parameteredPartials[1] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

A life is like a garden. Perfect moments can be had, but not preserved, except in memory.

'); test.done(); @@ -117,12 +125,14 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> molecules-single-comment(description: true) }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true

'); test.done(); @@ -132,12 +142,14 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> molecules-single-comment(description: \"true\") }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true

'); test.done(); @@ -147,12 +159,14 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> molecules-single-comment('description': true) }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true

'); test.done(); @@ -162,12 +176,14 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> molecules-single-comment('description': 'true not,\\'true\\'') }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true not,'true'

'); test.done(); @@ -177,12 +193,14 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> molecules-single-comment('description': \"true not:'true'\") }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true not:'true'

'); test.done(); @@ -192,12 +210,14 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> molecules-single-comment(\"description\": true) }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true

'); test.done(); @@ -207,13 +227,15 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; - currentPattern.template = "{{> molecules-single-comment(\"description\": 'true not{\"true\"') }}"; + currentPattern.template = "{{> molecules-single-comment(\"description\": 'true not{\",true\"') }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); - test.equals(currentPattern.extendedTemplate, '

true not{"true"

'); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); + test.equals(currentPattern.extendedTemplate, '

true not{",true"

'); test.done(); }, @@ -222,13 +244,15 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; - currentPattern.template = "{{> molecules-single-comment(\"description\": \"true not}\\\"true\\\"\") }}"; + currentPattern.template = "{{> molecules-single-comment(\"description\": \"true not}\\\":true\\\"\") }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); - test.equals(currentPattern.extendedTemplate, '

true not}"true"

'); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); + test.equals(currentPattern.extendedTemplate, '

true not}":true"

'); test.done(); }, @@ -237,12 +261,14 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> molecules-single-comment(description: true, 'foo': false, \"bar\": false, 'single': true, 'singlesingle': 'true', 'singledouble': \"true\", \"double\": true, \"doublesingle\": 'true', \"doubledouble\": \"true\") }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + currentPattern.dataKeys = []; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true

'); test.done(); @@ -253,12 +279,13 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; currentPattern.template = "{{> molecules-single-comment(description: 'Hello ) World') }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

Hello ) World

'); test.done(); @@ -269,15 +296,17 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; patternlab.patterns[0].template = "

{{foo}}

{{bar}}

"; + patternlab.patterns[0].escapedTemplate = patternlab.patterns[0].template; patternlab.patterns[0].extendedTemplate = patternlab.patterns[0].template; currentPattern.template = "{{> molecules-single-comment(foo: true, bar: \"Hello World\") }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true

Hello World

'); test.done(); @@ -288,15 +317,17 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; patternlab.patterns[0].template = "

{{ silly'key }}

{{bar}}

{{ another\"silly-key }}

"; + patternlab.patterns[0].escapedTemplate = patternlab.patterns[0].template; patternlab.patterns[0].extendedTemplate = patternlab.patterns[0].template; currentPattern.template = "{{> molecules-single-comment('silly\\\'key': true, bar: \"Hello World\", \"another\\\"silly-key\": 42 ) }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

true

Hello World

42

'); test.done(); @@ -307,6 +338,7 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; patternlab.patterns[0].template = "

{{foo}}

"; patternlab.patterns[0].extendedTemplate = patternlab.patterns[0].template; @@ -314,10 +346,10 @@ currentPattern.abspath = __filename; currentPattern.template = "{{> molecules-single-comment( missing-val: , : missing-key, : , , foo: \"Hello World\") }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + parameteredPartials[0] = currentPattern.template; console.log('\nPattern Lab should catch JSON.parse() errors and output useful debugging information...'); - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

'); test.done(); @@ -328,21 +360,116 @@ var currentPattern = currentPatternClosure(); var patternlab = patternlabClosure(); var parameter_hunter = new ph(); + var parameteredPartials = []; patternlab.patterns[0].template = "

{{{ tag1 }}}

{{{ tag2 }}}

{{{ tag3 }}}

"; + patternlab.patterns[0].escapedTemplate = patternlab.patterns[0].template; patternlab.patterns[0].extendedTemplate = patternlab.patterns[0].template; currentPattern.template = "{{> molecules-single-comment(tag1: 'Single-quoted', tag2: \"Double-quoted\", tag3: 'With attributes') }}"; currentPattern.extendedTemplate = currentPattern.template; - currentPattern.parameteredPartials[0] = currentPattern.template; + parameteredPartials[0] = currentPattern.template; - parameter_hunter.find_parameters(currentPattern, patternlab); + parameter_hunter.find_parameters(currentPattern, patternlab, parameteredPartials); test.equals(currentPattern.extendedTemplate, '

Single-quoted

Double-quoted

With attributes

'); test.done(); - } + }, + 'pattern assembler recursively includes and processes a partial that has parameters itself' : function(test){ + // this test utilizes pattern_assembler for the heavy lifting, but the actual code being tested resides inside parameter_hunter.js + //arrange + var fs = require('fs-extra'); + var path = require('path'); + var object_factory = require('../core/lib/object_factory'); + var pa = require('../core/lib/pattern_assembler'); + var pattern_assembler = new pa(); + var patterns_dir = './test/files/_patterns'; + + var pl = {}; + pl.config = { + paths: { + source: { + patterns: patterns_dir + } + } + }; + pl.data = {}; + pl.data.link = {}; + pl.dataKeys = []; + pl.config.debug = false; + pl.patterns = []; + + var atomFile = path.resolve('test/files/_patterns/00-test/01-bar.mustache'); + var styleFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var innerParameteredFile = path.resolve('test/files/_patterns/00-test/12-parameter-partial.mustache'); + var outerParameteredFile = path.resolve('test/files/_patterns/00-test/13-another-parameter-partial.mustache'); + + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(styleFile, pl); + pattern_assembler.process_pattern_iterative(innerParameteredFile, pl); + pattern_assembler.process_pattern_iterative(outerParameteredFile, pl); + + //act + pattern_assembler.process_pattern_recursive(atomFile, pl, 0, null, true); + pattern_assembler.process_pattern_recursive(styleFile, pl, 0, null, true); + pattern_assembler.process_pattern_recursive(innerParameteredFile, pl, 0, null, true); + pattern_assembler.process_pattern_recursive(outerParameteredFile, pl, 0, null, true); + var outerParameteredPattern = pattern_assembler.get_pattern_by_key(outerParameteredFile, pl); + + //assert. + var expectedValue = 'bar foo bar bar'; + test.equals(outerParameteredPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); + test.done(); + }, + 'pattern assembler escapes closing ERB tags written by end-users in templates' : function(test){ + //in order for pattern assembler to recursively include and process partials that have parameters, + //it must temporarily switch standard Mustache syntax to ERB syntax. this presents a problem if end- + //users write "%>" in user-facing templates. therefore, such instances must escaped and unescaped. + //arrange + var fs = require('fs-extra'); + var path = require('path'); + var object_factory = require('../core/lib/object_factory'); + var pa = require('../core/lib/pattern_assembler'); + var pattern_assembler = new pa(); + var patterns_dir = './test/files/_patterns'; + + var pl = {}; + pl.config = { + paths: { + source: { + patterns: patterns_dir + } + } + }; + pl.data = {}; + pl.data.link = {}; + pl.dataKeys = []; + pl.config.debug = false; + pl.patterns = []; + + var atomFile = path.resolve('test/files/_patterns/00-test/01-bar.mustache'); + var styleFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var innerParameteredFile = path.resolve('test/files/_patterns/00-test/12-parameter-partial.mustache'); + var outerParameteredFile = path.resolve('test/files/_patterns/00-test/14-template-with-erb-tag.mustache'); + + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(styleFile, pl); + pattern_assembler.process_pattern_iterative(innerParameteredFile, pl); + pattern_assembler.process_pattern_iterative(outerParameteredFile, pl); + + //act + pattern_assembler.process_pattern_recursive(atomFile, pl, 0, null, true); + pattern_assembler.process_pattern_recursive(styleFile, pl, 0, null, true); + pattern_assembler.process_pattern_recursive(innerParameteredFile, pl, 0, null, true); + pattern_assembler.process_pattern_recursive(outerParameteredFile, pl, 0, null, true); + var outerParameteredPattern = pattern_assembler.get_pattern_by_key(outerParameteredFile, pl); + + //assert. + var expectedValue = 'bar foo bar bar <% erb %>'; + test.equals(outerParameteredPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); + test.done(); + } }; - }()); diff --git a/test/pattern_assembler_tests.js b/test/pattern_assembler_tests.js index 2b42a95e6..b664edc48 100644 --- a/test/pattern_assembler_tests.js +++ b/test/pattern_assembler_tests.js @@ -2,8 +2,8 @@ "use strict"; var pa = require('../core/lib/pattern_assembler'); - var object_factory = require('../core/lib/object_factory'); - var path = require('path'); + var object_factory = require('../core/lib/object_factory'); + var path = require('path'); exports['pattern_assembler'] = { 'find_pattern_partials finds partials' : function(test){ @@ -201,12 +201,12 @@ patternlab.config.paths.source.patterns = patterns_dir; patternlab.data = fs.readJSONSync(path.resolve(patternlab.config.paths.source.data, 'data.json')); + patternlab.dataKeys = pattern_assembler.get_data_keys(patternlab.data, []); patternlab.listitems = fs.readJSONSync(path.resolve(patternlab.config.paths.source.data, 'listitems.json')); patternlab.header = fs.readFileSync(path.resolve(patternlab.config.paths.source.patternlabFiles, 'templates/pattern-header-footer/header.html'), 'utf8'); patternlab.footer = fs.readFileSync(path.resolve(patternlab.config.paths.source.patternlabFiles, 'templates/pattern-header-footer/footer.html'), 'utf8'); patternlab.patterns = []; patternlab.data.link = {}; - patternlab.partials = {}; //diveSync once to perform iterative populating of patternlab object diveSync(patterns_dir, @@ -251,7 +251,7 @@ return; } - pattern_assembler.process_pattern_recursive(path.resolve(file), patternlab); + pattern_assembler.process_pattern_recursive(path.resolve(file), patternlab, 0, null, true); } ); @@ -259,20 +259,23 @@ var foo = fs.readFileSync(patterns_dir + '/00-test/00-foo.mustache', 'utf8').trim(); var bar = fs.readFileSync(patterns_dir + '/00-test/01-bar.mustache', 'utf8').trim(); var fooExtended; + var fooFile = path.resolve(patterns_dir + '/00-test/00-foo.mustache'); + var barFile = path.resolve(patterns_dir + '/00-test/01-bar.mustache'); - //get extended pattern - for(var i = 0; i < patternlab.patterns.length; i++){ - if(patternlab.patterns[i].fileName === '00-foo'){ - fooExtended = patternlab.patterns[i].extendedTemplate.trim(); - break; - } - } + pattern_assembler.process_pattern_iterative(fooFile, patternlab); + pattern_assembler.process_pattern_iterative(barFile, patternlab); + + //act + pattern_assembler.process_pattern_recursive(fooFile, patternlab, 0, null, true); + pattern_assembler.process_pattern_recursive(barFile, patternlab, 0, null, true); + var fooPattern = pattern_assembler.get_pattern_by_key(fooFile, patternlab); + var barPattern = pattern_assembler.get_pattern_by_key(barFile, patternlab); //check initial values - test.equals(foo, '{{> test-bar }}'); - test.equals(bar, 'bar'); + test.equals(fooPattern.template.trim(), '{{> test-bar }}'); + test.equals(barPattern.template.trim(), '{{message}}bar'); //test that 00-foo.mustache included partial 01-bar.mustache - test.equals(fooExtended, 'bar'); + test.equals(fooPattern.extendedTemplate.trim(), 'bar'); test.done(); }, @@ -292,26 +295,22 @@ }; pl.data = {}; pl.data.link = {}; + pl.dataKeys = []; pl.config.debug = false; pl.patterns = []; - pl.partials = {}; - - var atomPattern = new object_factory.oPattern('test/files/_patterns/00-test/03-styled-atom.mustache', '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); - var groupPattern = new object_factory.oPattern('test/files/_patterns/00-test/04-group.mustache', '00-test', '04-group.mustache'); - groupPattern.template = fs.readFileSync(patterns_dir + '/00-test/04-group.mustache', 'utf8'); - groupPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(groupPattern); + var atomFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var groupFile = path.resolve('test/files/_patterns/00-test/04-group.mustache'); - pattern_assembler.addPattern(atomPattern, pl); - pattern_assembler.addPattern(groupPattern, pl); + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(groupFile, pl); //act - pattern_assembler.process_pattern_recursive('test/files/_patterns/00-test/04-group.mustache', pl, {}); + pattern_assembler.process_pattern_recursive(groupFile, pl, 0, null, true); + var groupPattern = pattern_assembler.get_pattern_by_key(groupFile, pl); //assert - var expectedValue = '
{{message}} {{message}} {{message}} {{message}}
'; + var expectedValue = '
'; test.equals(groupPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); test.done(); }, @@ -331,28 +330,22 @@ }; pl.data = {}; pl.data.link = {}; + pl.dataKeys = []; pl.config.debug = false; pl.patterns = []; - pl.partials = {}; - var atomPattern = new object_factory.oPattern('test/files/_patterns/00-test/03-styled-atom.mustache', '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 atomFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var groupFile = path.resolve('test/files/_patterns/00-test/10-multiple-classes-numeric.mustache'); - var groupPattern = new object_factory.oPattern('test/files/_patterns/00-test/10-multiple-classes-numeric.mustache', '00-test', '10-multiple-classes-numeric.mustache'); - groupPattern.template = fs.readFileSync(patterns_dir + '/00-test/10-multiple-classes-numeric.mustache', 'utf8'); - groupPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(groupPattern); - groupPattern.parameteredPartials = pattern_assembler.find_pattern_partials_with_parameters(groupPattern); - - pattern_assembler.addPattern(atomPattern, pl); - pattern_assembler.addPattern(groupPattern, pl); + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(groupFile, pl); //act - pattern_assembler.process_pattern_recursive('test/files/_patterns/00-test/10-multiple-classes-numeric.mustache', pl, {}); + pattern_assembler.process_pattern_recursive(groupFile, pl, 0, null, true); + var groupPattern = pattern_assembler.get_pattern_by_key(groupFile, pl); //assert - var expectedValue = '
{{message}} {{message}} bar
'; + var expectedValue = '
bar
'; test.equals(groupPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); test.done(); }, @@ -372,30 +365,27 @@ }; pl.data = {}; pl.data.link = {}; + pl.dataKeys = []; pl.config.debug = false; pl.patterns = []; - pl.partials = {}; - - var atomPattern = new object_factory.oPattern('test/files/_patterns/00-test/03-styled-atom.mustache', '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); - var mixedPattern = new object_factory.oPattern('test/files/_patterns/00-test/06-mixed.mustache', '00-test', '06-mixed.mustache'); - mixedPattern.template = fs.readFileSync(patterns_dir + '/00-test/06-mixed.mustache', 'utf8'); - mixedPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(mixedPattern); + var atomFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var mixedFile = path.resolve('test/files/_patterns/00-test/06-mixed.mustache'); - pattern_assembler.addPattern(atomPattern, pl); - pattern_assembler.addPattern(mixedPattern, pl); + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(mixedFile, pl); //act - pattern_assembler.process_pattern_recursive('test/files/_patterns/00-test/06-mixed.mustache', pl, {}); + pattern_assembler.process_pattern_recursive(mixedFile, pl, 0, null, true); + var mixedPattern = pattern_assembler.get_pattern_by_key(mixedFile, pl); - //assert. here we expect {{styleModifier}} to be in the first group, since it was not replaced by anything. rendering with data will then remove this (correctly) - var expectedValue = '
{{message}} {{message}} {{message}} {{message}}
'; + //assert. here we expect {{styleModifier}} and {{message}} to be rendered as empty strings in the first group, + //and {{message}} to be rendered as an empty strings in the second, third, and last groups, since they did not receive any value submissions. + var expectedValue = '
'; test.equals(mixedPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); test.done(); }, - 'processPatternRecursive - correctly ignores bookended partials without a style modifier when the same partial has a style modifier between' : function(test){ + 'processPatternRecursive - correctly ignores bookended partials without a style modifier when the same partial has a style modifier between' : function(test){ //arrange var fs = require('fs-extra'); var pattern_assembler = new pa(); @@ -411,26 +401,23 @@ }; pl.data = {}; pl.data.link = {}; + pl.dataKeys = []; pl.config.debug = false; pl.patterns = []; - pl.partials = {}; - - var atomPattern = new object_factory.oPattern('test/files/_patterns/00-test/03-styled-atom.mustache', '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); - var bookendPattern = new object_factory.oPattern('test/files/_patterns/00-test/09-bookend.mustache', '00-test', '09-bookend.mustache'); - bookendPattern.template = fs.readFileSync(patterns_dir + '/00-test/09-bookend.mustache', 'utf8'); - bookendPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(bookendPattern); + var atomFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var bookendFile = path.resolve('test/files/_patterns/00-test/09-bookend.mustache'); - pattern_assembler.addPattern(atomPattern, pl); - pattern_assembler.addPattern(bookendPattern, pl); + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(bookendFile, pl); //act - pattern_assembler.process_pattern_recursive('test/files/_patterns/00-test/09-bookend.mustache', pl, {}); + pattern_assembler.process_pattern_recursive(bookendFile, pl, 0, null, true); + var bookendPattern = pattern_assembler.get_pattern_by_key(bookendFile, pl); - //assert. here we expect {{styleModifier}} to be in the first and last group, since it was not replaced by anything. rendering with data will then remove this (correctly) - var expectedValue = '
{{message}} {{message}} {{message}} {{message}}
'; + //assert. here we expect {{styleModifier}} and {{message}} to be rendered as empty strings in the first and last group, + //and {{message}} to be rendered as an empty strings in the second and third groups, since they did not receive any value submissions. + var expectedValue = '
'; test.equals(bookendPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); test.done(); }, @@ -450,28 +437,22 @@ }; pl.data = {}; pl.data.link = {}; + pl.dataKeys = []; pl.config.debug = false; pl.patterns = []; - pl.partials = {}; - var atomPattern = new object_factory.oPattern('test/files/_patterns/00-test/03-styled-atom.mustache', '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 atomFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var mixedFile = path.resolve('test/files/_patterns/00-test/07-mixed-params.mustache'); - var mixedPattern = new object_factory.oPattern('test/files/_patterns/00-test/07-mixed-params.mustache', '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); - - pattern_assembler.addPattern(atomPattern, pl); - pattern_assembler.addPattern(mixedPattern, pl); + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(mixedFile, pl); //act - pattern_assembler.process_pattern_recursive('test/files/_patterns/00-test/07-mixed-params.mustache', pl, {}); + pattern_assembler.process_pattern_recursive(mixedFile, pl, 0, null, true); + var mixedPattern = pattern_assembler.get_pattern_by_key(mixedFile, pl); - //assert. here we expect {{styleModifier}} to be in the first span, since it was not replaced by anything. rendering with data will then remove this (correctly) - var expectedValue = '
{{message}} 2 3 4
'; + //assert. here we expect {{styleModifier}} and {{message}} to be rendered as empty strings in the first span, since they did not receive any value submissions. + var expectedValue = '
2 3 4
'; test.equals(mixedPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); test.done(); }, @@ -491,28 +472,22 @@ }; pl.data = {}; pl.data.link = {}; + pl.dataKeys = []; pl.config.debug = false; pl.patterns = []; - pl.partials = {}; - - var atomPattern = new object_factory.oPattern('test/files/_patterns/00-test/03-styled-atom.mustache', '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 bookendPattern = new object_factory.oPattern('test/files/_patterns/00-test/08-bookend-params.mustache', '00-test', '08-bookend-params.mustache'); - bookendPattern.template = fs.readFileSync(patterns_dir + '/00-test/08-bookend-params.mustache', 'utf8'); - bookendPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(bookendPattern); - bookendPattern.parameteredPartials = pattern_assembler.find_pattern_partials_with_parameters(bookendPattern); + var atomFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var bookendFile = path.resolve('test/files/_patterns/00-test/08-bookend-params.mustache'); - pattern_assembler.addPattern(atomPattern, pl); - pattern_assembler.addPattern(bookendPattern, pl); + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(bookendFile, pl); //act - pattern_assembler.process_pattern_recursive('test/files/_patterns/00-test/08-bookend-params.mustache', pl, {}); + pattern_assembler.process_pattern_recursive(bookendFile, pl, 0, null, true); + var bookendPattern = pattern_assembler.get_pattern_by_key(bookendFile, pl); - //assert. here we expect {{styleModifier}} to be in the first and last span, since it was not replaced by anything. rendering with data will then remove this (correctly) - var expectedValue = '
{{message}} 2 3 {{message}}
'; + //assert. here we expect {{styleModifier}} and {{message}} to be rendered as empty strings in the first and last span, since they did not receive any value submissions. + var expectedValue = '
2 3
'; test.equals(bookendPattern.extendedTemplate.replace(/\s\s+/g, ' ').replace(/\n/g, ' ').trim(), expectedValue.trim()); test.done(); }, @@ -529,44 +504,40 @@ patternlab.config.paths.source.patterns = patterns_dir; patternlab.data = fs.readJSONSync(path.resolve(patternlab.config.paths.source.data, 'data.json')); + patternlab.dataKeys = pattern_assembler.get_data_keys(patternlab.data, []); patternlab.listitems = fs.readJSONSync(path.resolve(patternlab.config.paths.source.data, 'listitems.json')); patternlab.header = fs.readFileSync(path.resolve(patternlab.config.paths.source.patternlabFiles, 'templates/pattern-header-footer/header.html'), 'utf8'); patternlab.footer = fs.readFileSync(path.resolve(patternlab.config.paths.source.patternlabFiles, 'templates/pattern-header-footer/footer.html'), 'utf8'); patternlab.patterns = []; patternlab.data.link = {}; - patternlab.partials = {}; //act - 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){ + diveSync( + patterns_dir, + function (err, file) { //log any errors - if(err){ + if (err) { console.log(err); return; } - pattern_assembler.process_pattern_iterative(path.resolve(file), patternlab); } ); //assert var foundVariant = false; + var variantIgnored = false; for(var i = 0; i < patternlab.patterns.length; i++){ if(patternlab.patterns[i].fileName.indexOf('~') > -1){ foundVariant = true; + if(typeof patternlab.patterns[i].extendedTemplate === 'undefined'){ + variantIgnored = true; + } } } - test.equals(foundVariant, false); + + test.equals(foundVariant, true); + test.equals(variantIgnored, true); test.done(); }, 'setState - applies any patternState matching the pattern' : function(test){ @@ -621,31 +592,36 @@ patternlab.config = fs.readJSONSync('./patternlab-config.json'); patternlab.config.paths.source.patterns = patterns_dir; patternlab.data = fs.readJSONSync(path.resolve(patternlab.config.paths.source.data, 'data.json')); + patternlab.dataKeys = pattern_assembler.get_data_keys(patternlab.data, []); patternlab.listitems = fs.readJSONSync(path.resolve(patternlab.config.paths.source.data, 'listitems.json')); patternlab.header = fs.readFileSync(path.resolve(patternlab.config.paths.source.patternlabFiles, 'templates/pattern-header-footer/header.html'), 'utf8'); patternlab.footer = fs.readFileSync(path.resolve(patternlab.config.paths.source.patternlabFiles, 'templates/pattern-header-footer/footer.html'), 'utf8'); patternlab.patterns = []; patternlab.data.link = {}; - patternlab.partials = {}; - diveSync(patterns_dir, - { - filter: function(path, dir){ - if(dir){ - var remainingPath = path.replace(patterns_dir, ''); - var isValidPath = remainingPath.indexOf('/_') === -1; - return isValidPath; - } - return true; + diveSync( + patterns_dir, + function (err, file) { + //log any errors + if (err) { + console.log(err); + return; } - }, - function(err, file){ + 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, + function (err, file) { //log any errors - if(err){ + if (err) { console.log(err); return; } - pattern_assembler.process_pattern_iterative(file, patternlab); + pattern_assembler.process_pattern_recursive(path.resolve(file), patternlab, 0, null, true); } ); @@ -729,7 +705,6 @@ var pattern_assembler = new pa(); var patternlab = {}; patternlab.patterns = []; - patternlab.partials = {}; patternlab.data = {link: {}}; var pattern = new object_factory.oPattern('test/files/_patterns/00-test/01-bar.mustache', '00-test', '01-bar.mustache'); @@ -741,8 +716,8 @@ //assert test.equals(patternlab.patterns.length, 1); - test.equals(patternlab.partials['test-bar'] != undefined, true); - test.equals(patternlab.partials['test-bar'], 'barExtended'); + test.equals(patternlab.patterns[0] != undefined, true); + test.equals(patternlab.patterns[0].extendedTemplate, 'barExtended'); test.done(); }, 'addPattern - adds pattern template to patternlab partial object if extendedtemplate does not exist yet' : function(test){ @@ -750,7 +725,6 @@ var pattern_assembler = new pa(); var patternlab = {}; patternlab.patterns = []; - patternlab.partials = {}; patternlab.data = {link: {}}; var pattern = new object_factory.oPattern('test/files/_patterns/00-test/01-bar.mustache', '00-test', '01-bar.mustache'); @@ -762,8 +736,8 @@ //assert test.equals(patternlab.patterns.length, 1); - test.equals(patternlab.partials['test-bar'] != undefined, true); - test.equals(patternlab.partials['test-bar'], 'bar'); + test.equals(patternlab.patterns[0] != undefined, true); + test.equals(patternlab.patterns[0].template, 'bar'); test.done(); } }; diff --git a/test/pseudopattern_hunter_tests.js b/test/pseudopattern_hunter_tests.js index 3a97b773f..7d803cb2b 100644 --- a/test/pseudopattern_hunter_tests.js +++ b/test/pseudopattern_hunter_tests.js @@ -1,12 +1,13 @@ (function () { - "use strict"; + "use strict"; - var pha = require('../core/lib/pseudopattern_hunter'); + var path = require('path'); + var pha = require('../core/lib/pseudopattern_hunter'); var pa = require('../core/lib/pattern_assembler'); - var object_factory = require('../core/lib/object_factory'); + var object_factory = require('../core/lib/object_factory'); - exports['pseudopattern_hunter'] = { - 'pseudpattern found and added as a pattern' : function(test){ + exports['pseudopattern_hunter'] = { + 'pseudopattern found and added as a pattern' : function(test){ //arrange var fs = require('fs-extra'); var pattern_assembler = new pa(); @@ -23,27 +24,26 @@ }; pl.data = {}; pl.data.link = {}; + pl.dataKeys = []; pl.config.debug = false; pl.patterns = []; - pl.partials = {}; pl.config.patternStates = {}; - var atomPattern = new object_factory.oPattern('test/files/_patterns/00-test/03-styled-atom.mustache', '00-test', '03-styled-atom.mustache'); - atomPattern.template = fs.readFileSync(patterns_dir + '00-test/03-styled-atom.mustache', 'utf8'); - atomPattern.extendedTemplate = atomPattern.template; - atomPattern.stylePartials = pattern_assembler.find_pattern_partials_with_style_modifiers(atomPattern); + var atomFile = path.resolve('test/files/_patterns/00-test/03-styled-atom.mustache'); + var atomVariantFile = path.resolve('test/files/_patterns/00-test/03-styled-atom~alt.json'); - pattern_assembler.addPattern(atomPattern, pl); + pattern_assembler.process_pattern_iterative(atomFile, pl); + pattern_assembler.process_pattern_iterative(atomVariantFile, pl); //act - var patternCountBefore = pl.patterns.length; - pseudopattern_hunter.find_pseudopatterns(atomPattern, pl); + pattern_assembler.process_pattern_recursive(atomFile, pl, 0, null, true); + var atomPattern = pattern_assembler.get_pattern_by_key(atomFile, pl); + var atomVariantPattern = pattern_assembler.get_pattern_by_key(atomVariantFile, pl); //assert - test.equals(patternCountBefore + 1, pl.patterns.length); - test.equals(pl.patterns[1].key, 'test-styled-atom-alt'); + test.equals(pl.patterns[1].key, '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(JSON.stringify(pl.patterns[1].jsonFileData), JSON.stringify({"message":"alternateMessage","link":{"test-styled-atom":"/patterns/00-test-03-styled-atom/00-test-03-styled-atom.html","test-styled-atom-alt":"/patterns/00-test-03-styled-atom~alt/00-test-03-styled-atom~alt.html"}})); test.done(); } diff --git a/test/style_modifier_hunter_tests.js b/test/style_modifier_hunter_tests.js index 7a3562ad0..ff4280a24 100644 --- a/test/style_modifier_hunter_tests.js +++ b/test/style_modifier_hunter_tests.js @@ -4,15 +4,14 @@ var smh = require('../core/lib/style_modifier_hunter'); exports['consume_style_modifier'] = { - 'uses the partial stylemodifer to modify the patterns extendedTemplate' : function(test){ + 'uses the partial stylemodifer to modify the patterns tmpTemplate' : function(test){ //arrange var pl = {}; - pl.partials = {}; pl.config = {}; pl.config.debug = false; var pattern = { - extendedTemplate: '
' + tmpTemplate: '
' }; var style_modifier_hunter = new smh(); @@ -21,18 +20,17 @@ style_modifier_hunter.consume_style_modifier(pattern, '{{> partial:bar}}', pl); //assert - test.equals(pattern.extendedTemplate, '
'); + test.equals(pattern.tmpTemplate, '
'); test.done(); }, 'replaces style modifiers with spaces in the syntax' : function(test){ //arrange var pl = {}; - pl.partials = {}; pl.config = {}; pl.config.debug = false; var pattern = { - extendedTemplate: '
' + tmpTemplate: '
' }; var style_modifier_hunter = new smh(); @@ -41,18 +39,17 @@ style_modifier_hunter.consume_style_modifier(pattern, '{{> partial:bar}}', pl); //assert - test.equals(pattern.extendedTemplate, '
'); + test.equals(pattern.tmpTemplate, '
'); test.done(); }, 'replaces multiple style modifiers' : function(test){ //arrange var pl = {}; - pl.partials = {}; pl.config = {}; pl.config.debug = false; var pattern = { - extendedTemplate: '
' + tmpTemplate: '
' }; var style_modifier_hunter = new smh(); @@ -61,18 +58,17 @@ style_modifier_hunter.consume_style_modifier(pattern, '{{> partial:bar|baz|dum}}', pl); //assert - test.equals(pattern.extendedTemplate, '
'); + test.equals(pattern.tmpTemplate, '
'); test.done(); }, - 'does not alter pattern extendedTemplate if styleModifier not found in partial' : function(test){ + 'does not alter pattern tmpTemplate if styleModifier not found in partial' : function(test){ //arrange var pl = {}; - pl.partials = {}; pl.config = {}; pl.config.debug = false; var pattern = { - extendedTemplate: '
' + tmpTemplate: '
' }; var style_modifier_hunter = new smh(); @@ -81,7 +77,7 @@ style_modifier_hunter.consume_style_modifier(pattern, '{{> partial}}', pl); //assert - test.equals(pattern.extendedTemplate, '
'); + test.equals(pattern.tmpTemplate, '
'); test.done(); } };