UNPKG

patternlab-node

Version:

Pattern Lab is a collection of tools to help you create atomic design systems. This is the node command line interface (CLI).

317 lines (258 loc) 13 kB
"use strict"; var parameter_hunter = function () { var extend = require('util')._extend, pa = require('./pattern_assembler'), smh = require('./style_modifier_hunter'), plutils = require('./utilities'), style_modifier_hunter = new smh(), jsonCopy = require('./json_copy'), pattern_assembler = new pa(); /** * This function is really to accommodate the lax JSON-like syntax allowed by * Pattern Lab PHP for parameter submissions to partials. Unfortunately, no * easily searchable library was discovered for this. What we had to do was * write a custom script to crawl through the parameter string, and wrap the * keys and values in double-quotes as necessary. * The steps on a high-level are as follows: * * Further escape all escaped quotes and colons. Use the string * representation of their unicodes for this. This has the added bonus * of being interpreted correctly by JSON.parse() without further * modification. This will be useful later in the function. * * Once escaped quotes are out of the way, we know the remaining quotes * are either key/value wrappers or wrapped within those wrappers. We know * that remaining commas and colons are either delimiters, or wrapped * within quotes to not be recognized as such. * * A do-while loop crawls paramString to write keys to a keys array and * values to a values array. * * Start by parsing the first key. Determine the type of wrapping quote, * if any. * * By knowing the open wrapper, we know that the next quote of that kind * (if the key is wrapped in quotes), HAS to be the close wrapper. * Similarly, if the key is unwrapped, we know the next colon HAS to be * the delimiter between key and value. * * Save the key to the keys array. * * Next, search for a value. It will either be the next block wrapped in * quotes, or a string of alphanumerics, decimal points, or minus signs. * * Save the value to the values array. * * The do-while loop truncates the paramString value while parsing. Its * condition for completion is when the paramString is whittled down to an * empty string. * * After the keys and values arrays are built, a for loop iterates through * them to build the final paramStringWellFormed string. * * No quote substitution had been done prior to this loop. In this loop, * all keys are ensured to be wrapped in double-quotes. String values are * also ensured to be wrapped in double-quotes. * * Unescape escaped unicodes except for double-quotes. Everything beside * double-quotes will be wrapped in double-quotes without need for escape. * * Return paramStringWellFormed. * * @param {string} pString * @param {object} patternlab * @returns {string} paramStringWellFormed */ function paramToJson(pString, patternlab) { var colonPos = -1; var keys = []; var paramString = pString; // to not reassign param var paramStringWellFormed; var quotePos = -1; var regex; var values = []; var wrapper; // attempt to parse the data in case it is already well formed JSON try { paramStringWellFormed = JSON.stringify(JSON.parse(pString)); return paramStringWellFormed; } catch (err) { //todo this might be a good candidate for a different log level, should we implement that someday if (patternlab.config.debug) { console.log(`Not valid JSON found for passed pattern parameter ${pString} will attempt to parse manually...`); } } //replace all escaped double-quotes with escaped unicode paramString = paramString.replace(/\\"/g, '\\u0022'); //replace all escaped single-quotes with escaped unicode paramString = paramString.replace(/\\'/g, '\\u0027'); //replace all escaped colons with escaped unicode paramString = paramString.replace(/\\:/g, '\\u0058'); //with escaped chars out of the way, crawl through paramString looking for //keys and values do { //check if searching for a key if (paramString[0] === '{' || paramString[0] === ',') { paramString = paramString.substring(1, paramString.length).trim(); //search for end quote if wrapped in quotes. else search for colon. //everything up to that position will be saved in the keys array. switch (paramString[0]) { //need to search for end quote pos in case the quotes wrap a colon case '"': case '\'': wrapper = paramString[0]; quotePos = paramString.indexOf(wrapper, 1); break; default: colonPos = paramString.indexOf(':'); } if (quotePos > -1) { keys.push(paramString.substring(0, quotePos + 1).trim()); //truncate the beginning from paramString and look for a value paramString = paramString.substring(quotePos + 1, paramString.length).trim(); //unset quotePos quotePos = -1; } else if (colonPos > -1) { keys.push(paramString.substring(0, colonPos).trim()); //truncate the beginning from paramString and look for a value paramString = paramString.substring(colonPos, paramString.length); //unset colonPos colonPos = -1; //if there are no more colons, and we're looking for a key, there is //probably a problem. stop any further processing. } else { paramString = ''; break; } } //now, search for a value if (paramString[0] === ':') { paramString = paramString.substring(1, paramString.length).trim(); //the only reason we're using regexes here, instead of indexOf(), is //because we don't know if the next delimiter is going to be a comma or //a closing curly brace. since it's not much of a performance hit to //use regexes as sparingly as here, and it's much more concise and //readable, we'll use a regex for match() and replace() instead of //performing conditional logic with indexOf(). switch (paramString[0]) { //since a quote of same type as its wrappers would be escaped, and we //escaped those even further with their unicodes, it is safe to look //for wrapper pairs and conclude that their contents are values case '"': regex = /^"(.|\s)*?"/; break; case '\'': regex = /^'(.|\s)*?'/; break; //if there is no value wrapper, regex for alphanumerics, decimal //points, and minus signs for exponential notation. default: regex = /^[\w\-\.]*/; } values.push(paramString.match(regex)[0].trim()); //truncate the beginning from paramString and continue either //looking for a key, or returning paramString = paramString.replace(regex, '').trim(); //exit do while if the final char is '}' if (paramString === '}') { paramString = ''; break; } //if there are no more colons, and we're looking for a value, there is //probably a problem. stop any further processing. } else { paramString = ''; break; } } while (paramString); //build paramStringWellFormed string for JSON parsing paramStringWellFormed = '{'; for (var i = 0; i < keys.length; i++) { //keys //replace single-quote wrappers with double-quotes if (keys[i][0] === '\'' && keys[i][keys[i].length - 1] === '\'') { paramStringWellFormed += '"'; //any enclosed double-quotes must be escaped paramStringWellFormed += keys[i].substring(1, keys[i].length - 1).replace(/"/g, '\\"'); paramStringWellFormed += '"'; } else { //open wrap with double-quotes if no wrapper if (keys[i][0] !== '"' && keys[i][0] !== '\'') { paramStringWellFormed += '"'; //this is to clean up vestiges from Pattern Lab PHP's escaping scheme. //F.Y.I. Pattern Lab PHP would allow special characters like question //marks in parameter keys so long as the key was unwrapped and the //special character escaped with a backslash. In Node, we need to wrap //those keys and unescape those characters. keys[i] = keys[i].replace(/\\/g, ''); } paramStringWellFormed += keys[i]; //close wrap with double-quotes if no wrapper if (keys[i][keys[i].length - 1] !== '"' && keys[i][keys[i].length - 1] !== '\'') { paramStringWellFormed += '"'; } } //colon delimiter. paramStringWellFormed += ':'; //values //replace single-quote wrappers with double-quotes if (values[i][0] === '\'' && values[i][values[i].length - 1] === '\'') { paramStringWellFormed += '"'; //any enclosed double-quotes must be escaped paramStringWellFormed += values[i].substring(1, values[i].length - 1).replace(/"/g, '\\"'); paramStringWellFormed += '"'; //for everything else, just add the value however it's wrapped } else { paramStringWellFormed += values[i]; } //comma delimiter if (i < keys.length - 1) { paramStringWellFormed += ','; } } paramStringWellFormed += '}'; //unescape escaped unicode except for double-quotes paramStringWellFormed = paramStringWellFormed.replace(/\\u0027/g, '\''); paramStringWellFormed = paramStringWellFormed.replace(/\\u0058/g, ':'); return paramStringWellFormed; } function findparameters(pattern, patternlab) { if (pattern.parameteredPartials && pattern.parameteredPartials.length > 0) { //compile this partial immeadiately, essentially consuming it. pattern.parameteredPartials.forEach(function (pMatch) { //find the partial's name and retrieve it var partialName = pMatch.match(/([\w\-\.\/~]+)/g)[0]; var partialPattern = pattern_assembler.getPartial(partialName, patternlab); //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #190 partialPattern.extendedTemplate = partialPattern.template; if (patternlab.config.debug) { console.log('found patternParameters for ' + partialName); } //strip out the additional data, convert string to JSON. var leftParen = pMatch.indexOf('('); var rightParen = pMatch.lastIndexOf(')'); var paramString = '{' + pMatch.substring(leftParen + 1, rightParen) + '}'; var paramStringWellFormed = paramToJson(paramString, patternlab); var paramData = {}; var globalData = {}; var localData = {}; try { paramData = JSON.parse(paramStringWellFormed); globalData = jsonCopy(patternlab.data, 'config.paths.source.data global data'); localData = jsonCopy(pattern.jsonFileData || {}, `pattern ${pattern.patternPartial} data`); } catch (err) { console.log('There was an error parsing JSON for ' + pattern.relPath); console.log(err); } var allData = plutils.mergeData(globalData, localData); allData = plutils.mergeData(allData, paramData); //if the partial has pattern parameters itself, we need to handle those findparameters(partialPattern, patternlab); //if partial has style modifier data, replace the styleModifier value if (pattern.stylePartials && pattern.stylePartials.length > 0) { style_modifier_hunter.consume_style_modifier(partialPattern, pMatch, patternlab); } //extend pattern data links into link for pattern link shortcuts to work. we do this locally and globally allData.link = extend({}, patternlab.data.link); var renderedPartial = pattern_assembler.renderPattern(partialPattern.extendedTemplate, allData, patternlab.partials); //remove the parameter from the partial and replace it with the rendered partial + paramData pattern.extendedTemplate = pattern.extendedTemplate.replace(pMatch, renderedPartial); //update the extendedTemplate in the partials object in case this pattern is consumed later patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate; }); } } return { find_parameters: function (pattern, patternlab) { findparameters(pattern, patternlab); } }; }; module.exports = parameter_hunter;