-
Notifications
You must be signed in to change notification settings - Fork 407
/
Copy pathpattern_assembler.js
615 lines (513 loc) · 22.9 KB
/
pattern_assembler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
"use strict";
var path = require('path'),
_ = require('lodash'),
fs = require('fs-extra'),
Pattern = require('./object_factory').Pattern,
CompileState = require('./object_factory').CompileState,
pph = require('./pseudopattern_hunter'),
mp = require('./markdown_parser'),
plutils = require('./utilities'),
dataLoader = require('./data_loader')(),
patternEngines = require('./pattern_engines'),
lh = require('./lineage_hunter'),
lih = require('./list_item_hunter'),
smh = require('./style_modifier_hunter'),
ph = require('./parameter_hunter'),
jsonCopy = require('./json_copy'),
ch = require('./changes_hunter');
const markdown_parser = new mp();
const changes_hunter = new ch();
var pattern_assembler = function () {
// HELPER FUNCTIONS
function getPartial(partialName, patternlab) {
//look for exact partial matches
for (var i = 0; i < patternlab.patterns.length; i++) {
if (patternlab.patterns[i].patternPartial === partialName) {
return patternlab.patterns[i];
}
}
//else look by verbose syntax
for (var i = 0; i < patternlab.patterns.length; i++) {
switch (partialName) {
case patternlab.patterns[i].relPath:
case patternlab.patterns[i].verbosePartial:
return patternlab.patterns[i];
}
}
//return the fuzzy match if all else fails
for (var i = 0; i < patternlab.patterns.length; i++) {
var partialParts = partialName.split('-'),
partialType = partialParts[0],
partialNameEnd = partialParts.slice(1).join('-');
if (patternlab.patterns[i].patternPartial.split('-')[0] === partialType && patternlab.patterns[i].patternPartial.indexOf(partialNameEnd) > -1) {
return patternlab.patterns[i];
}
}
plutils.warning('Could not find pattern referenced with partial syntax ' + partialName + '. This can occur when a pattern was renamed, moved, or no longer exists but it still called within a different template somewhere.');
return undefined;
}
function buildListItems(container) {
//combine all list items into one structure
var list = [];
for (var item in container.listitems) {
if (container.listitems.hasOwnProperty(item)) {
list.push(container.listitems[item]);
}
}
container.listItemArray = plutils.shuffle(list);
for (var i = 1; i <= container.listItemArray.length; i++) {
var tempItems = [];
if (i === 1) {
tempItems.push(container.listItemArray[0]);
container.listitems['' + i ] = tempItems;
} else {
for (var c = 1; c <= i; c++) {
tempItems.push(container.listItemArray[c - 1]);
container.listitems['' + i ] = tempItems;
}
}
}
}
/*
* Deprecated in favor of .md 'status' frontmatter inside a pattern. Still used for unit tests at this time.
* Will be removed in future versions
*/
function setState(pattern, patternlab, displayDeprecatedWarning) {
if (patternlab.config.patternStates && patternlab.config.patternStates[pattern.patternPartial]) {
if (displayDeprecatedWarning) {
plutils.error("Deprecation Warning: Using patternlab-config.json patternStates object will be deprecated in favor of the state frontmatter key associated with individual pattern markdown files.");
console.log("This feature will still work in it's current form this release (but still be overridden by the new parsing method), and will be removed in the future.");
}
pattern.patternState = patternlab.config.patternStates[pattern.patternPartial];
}
}
function addPattern(pattern, patternlab) {
//add the link to the global object
patternlab.data.link[pattern.patternPartial] = '/patterns/' + pattern.patternLink;
//only push to array if the array doesn't contain this pattern
var isNew = true;
for (var i = 0; i < patternlab.patterns.length; i++) {
//so we need the identifier to be unique, which patterns[i].relPath is
if (pattern.relPath === patternlab.patterns[i].relPath) {
//if relPath already exists, overwrite that element
patternlab.patterns[i] = pattern;
patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate || pattern.template;
isNew = false;
break;
}
}
// if the pattern is new, we must register it with various data structures!
if (isNew) {
if (patternlab.config.debug) {
console.log('found new pattern ' + pattern.patternPartial);
}
// do global registration
if (pattern.isPattern) {
patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate || pattern.template;
// do plugin-specific registration
pattern.registerPartial();
} else {
patternlab.partials[pattern.patternPartial] = pattern.patternDesc;
}
patternlab.graph.add(pattern);
patternlab.patterns.push(pattern);
}
}
function addSubtypePattern(subtypePattern, patternlab) {
patternlab.subtypePatterns[subtypePattern.patternPartial] = subtypePattern;
}
// Render a pattern on request. Long-term, this should probably go away.
function renderPattern(pattern, data, partials) {
// if we've been passed a full Pattern, it knows what kind of template it
// is, and how to render itself, so we just call its render method
if (pattern instanceof Pattern) {
return pattern.render(data, partials);
} else {
// otherwise, assume it's a plain mustache template string, and we
// therefore just need to create a dummpy pattern to be able to render
// it
var dummyPattern = Pattern.createEmpty({extendedTemplate: pattern});
return patternEngines.mustache.renderPattern(dummyPattern, data, partials);
}
}
function parsePatternMarkdown(currentPattern, patternlab) {
try {
var markdownFileName = path.resolve(patternlab.config.paths.source.patterns, currentPattern.subdir, currentPattern.fileName + ".md");
changes_hunter.checkLastModified(currentPattern, markdownFileName);
var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8');
var markdownObject = markdown_parser.parse(markdownFileContents);
if (!plutils.isObjectEmpty(markdownObject)) {
//set keys and markdown itself
currentPattern.patternDescExists = true;
currentPattern.patternDesc = markdownObject.markdown;
//Add all markdown to the currentPattern, including frontmatter
currentPattern.allMarkdown = markdownObject;
//consider looping through all keys eventually. would need to blacklist some properties and whitelist others
if (markdownObject.state) {
currentPattern.patternState = markdownObject.state;
}
if (markdownObject.order) {
currentPattern.order = markdownObject.order;
}
if (markdownObject.hidden) {
currentPattern.hidden = markdownObject.hidden;
}
if (markdownObject.excludeFromStyleguide) {
currentPattern.excludeFromStyleguide = markdownObject.excludeFromStyleguide;
}
if (markdownObject.tags) {
currentPattern.tags = markdownObject.tags;
}
if (markdownObject.title) {
currentPattern.patternName = markdownObject.title;
}
if (markdownObject.links) {
currentPattern.links = markdownObject.links;
}
} else {
if (patternlab.config.debug) {
console.log('error processing markdown for ' + currentPattern.patternPartial);
}
}
if (patternlab.config.debug) {
console.log('found pattern-specific markdown for ' + currentPattern.patternPartial);
}
}
catch (err) {
// do nothing when file not found
if (err.code !== 'ENOENT') {
console.log('there was an error setting pattern keys after markdown parsing of the companion file for pattern ' + currentPattern.patternPartial);
console.log(err);
}
}
}
/**
* A helper that unravels a pattern looking for partials or listitems to unravel.
* The goal is really to convert pattern.template into pattern.extendedTemplate
* @param pattern - the pattern to decompose
* @param patternlab - global data store
* @param ignoreLineage - whether or not to hunt for lineage for this pattern
*/
function decomposePattern(pattern, patternlab, ignoreLineage) {
var lineage_hunter = new lh(),
list_item_hunter = new lih();
pattern.extendedTemplate = pattern.template;
//find how many partials there may be for the given pattern
var foundPatternPartials = pattern.findPartials();
//find any listItem blocks that within the pattern, even if there are no partials
list_item_hunter.process_list_item_partials(pattern, patternlab);
// expand any partials present in this pattern; that is, drill down into
// the template and replace their calls in this template with rendered
// results
if (pattern.engine.expandPartials && (foundPatternPartials !== null && foundPatternPartials.length > 0)) {
// eslint-disable-next-line
expandPartials(foundPatternPartials, list_item_hunter, patternlab, pattern);
// update the extendedTemplate in the partials object in case this
// pattern is consumed later
patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate;
}
//find pattern lineage
if (!ignoreLineage) {
lineage_hunter.find_lineage(pattern, patternlab);
}
//add to patternlab object so we can look these up later.
addPattern(pattern, patternlab);
}
function processPatternIterative(relPath, patternlab) {
var relativeDepth = (relPath.match(/\w(?=\\)|\w(?=\/)/g) || []).length;
if (relativeDepth > 2) {
console.log('');
plutils.warning('Warning:');
plutils.warning('A pattern file: ' + relPath + ' was found greater than 2 levels deep from ' + patternlab.config.paths.source.patterns + '.');
plutils.warning('It\'s strongly suggested to not deviate from the following structure under _patterns/');
plutils.warning('[patternType]/[patternSubtype]/[patternName].[patternExtension]');
console.log('');
plutils.warning('While Pattern Lab may still function, assets may 404 and frontend links may break. Consider yourself warned. ');
plutils.warning('Read More: http://patternlab.io/docs/pattern-organization.html');
console.log('');
}
//check if the found file is a top-level markdown file
var fileObject = path.parse(relPath);
if (fileObject.ext === '.md') {
try {
var proposedDirectory = path.resolve(patternlab.config.paths.source.patterns, fileObject.dir, fileObject.name);
var proposedDirectoryStats = fs.statSync(proposedDirectory);
if (proposedDirectoryStats.isDirectory()) {
var subTypeMarkdownFileContents = fs.readFileSync(proposedDirectory + '.md', 'utf8');
var subTypeMarkdown = markdown_parser.parse(subTypeMarkdownFileContents);
var subTypePattern = new Pattern(relPath, null, patternlab);
subTypePattern.patternSectionSubtype = true;
subTypePattern.patternLink = subTypePattern.name + '/index.html';
subTypePattern.patternDesc = subTypeMarkdown.markdown;
subTypePattern.flatPatternPath = subTypePattern.flatPatternPath + '-' + subTypePattern.fileName;
subTypePattern.isPattern = false;
subTypePattern.engine = null;
addSubtypePattern(subTypePattern, patternlab);
return subTypePattern;
}
} catch (err) {
// no file exists, meaning it's a pattern markdown file
if (err.code !== 'ENOENT') {
console.log(err);
}
}
}
var pseudopattern_hunter = new pph();
//extract some information
var filename = fileObject.base;
var ext = fileObject.ext;
var patternsPath = patternlab.config.paths.source.patterns;
// skip non-pattern files
if (!patternEngines.isPatternFile(filename, patternlab)) { return null; }
//make a new Pattern Object
var currentPattern = new Pattern(relPath, null, patternlab);
//if file is named in the syntax for variants
if (patternEngines.isPseudoPatternJSON(filename)) {
return currentPattern;
}
//can ignore all non-supported files at this point
if (patternEngines.isFileExtensionSupported(ext) === false) {
return currentPattern;
}
//see if this file has a state
setState(currentPattern, patternlab, true);
//look for a json file for this template
try {
var jsonFilename = path.resolve(patternsPath, currentPattern.subdir, currentPattern.fileName);
let configData = dataLoader.loadDataFromFile(jsonFilename, fs);
if (configData) {
currentPattern.jsonFileData = configData;
if (patternlab.config.debug) {
console.log('processPatternIterative: found pattern-specific config data for ' + currentPattern.patternPartial);
}
}
}
catch (err) {
console.log('There was an error parsing sibling JSON for ' + currentPattern.relPath);
console.log(err);
}
//look for a listitems.json file for this template
try {
var listJsonFileName = path.resolve(patternsPath, currentPattern.subdir, currentPattern.fileName + ".listitems");
let listItemsConfig = dataLoader.loadDataFromFile(listJsonFileName, fs);
if (listItemsConfig) {
currentPattern.listitems = listItemsConfig;
buildListItems(currentPattern);
if (patternlab.config.debug) {
console.log('found pattern-specific listitems config for ' + currentPattern.patternPartial);
}
}
}
catch (err) {
console.log('There was an error parsing sibling listitem JSON for ' + currentPattern.relPath);
console.log(err);
}
//look for a markdown file for this template
parsePatternMarkdown(currentPattern, patternlab);
//add the raw template to memory
var templatePath = path.resolve(patternsPath, currentPattern.relPath);
currentPattern.template = fs.readFileSync(templatePath, 'utf8');
//find any stylemodifiers that may be in the current pattern
currentPattern.stylePartials = currentPattern.findPartialsWithStyleModifiers();
//find any pattern parameters that may be in the current pattern
currentPattern.parameteredPartials = currentPattern.findPartialsWithPatternParameters();
[templatePath, jsonFilename, listJsonFileName].forEach(file => {
changes_hunter.checkLastModified(currentPattern, file);
});
changes_hunter.checkBuildState(currentPattern, patternlab);
//add currentPattern to patternlab.patterns array
addPattern(currentPattern, patternlab);
//look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json
pseudopattern_hunter.find_pseudopatterns(currentPattern, patternlab);
return currentPattern;
}
function processPatternRecursive(file, patternlab) {
//find current pattern in patternlab object using var file as a partial
var currentPattern, i;
for (i = 0; i < patternlab.patterns.length; i++) {
if (patternlab.patterns[i].relPath === file) {
currentPattern = patternlab.patterns[i];
}
}
//return if processing an ignored file
if (typeof currentPattern === 'undefined') { return; }
//we are processing a markdown only pattern
if (currentPattern.engine === null) { return; }
//call our helper method to actually unravel the pattern with any partials
decomposePattern(currentPattern, patternlab);
}
/**
* Finds patterns that were modified and need to be rebuilt. For clean patterns load the already
* rendered markup.
*
* @param lastModified
* @param patternlab
*/
function markModifiedPatterns(lastModified, patternlab) {
/**
* If the given array exists, apply a function to each of its elements
* @param {Array} array
* @param {Function} func
*/
const forEachExisting = (array, func) => {
if (array) {
array.forEach(func);
}
};
const modifiedOrNot = _.groupBy(
patternlab.patterns,
p => changes_hunter.needsRebuild(lastModified, p) ? 'modified' : 'notModified');
// For all unmodified patterns load their rendered template output
forEachExisting(modifiedOrNot.notModified, cleanPattern => {
const xp = path.join(patternlab.config.paths.public.patterns, cleanPattern.getPatternLink(patternlab, 'markupOnly'));
// Pattern with non-existing markupOnly files were already marked for rebuild and thus are not "CLEAN"
cleanPattern.patternPartialCode = fs.readFileSync(xp, 'utf8');
});
// For all patterns that were modified, schedule them for rebuild
forEachExisting(modifiedOrNot.modified, p => p.compileState = CompileState.NEEDS_REBUILD);
return modifiedOrNot;
}
function expandPartials(foundPatternPartials, list_item_hunter, patternlab, currentPattern) {
var style_modifier_hunter = new smh(),
parameter_hunter = new ph();
if (patternlab.config.debug) {
console.log('found partials for ' + currentPattern.patternPartial);
}
// determine if the template contains any pattern parameters. if so they
// must be immediately consumed
parameter_hunter.find_parameters(currentPattern, patternlab);
//do something with the regular old partials
for (var i = 0; i < foundPatternPartials.length; i++) {
var partial = currentPattern.findPartial(foundPatternPartials[i]);
var partialPath;
//identify which pattern this partial corresponds to
for (var j = 0; j < patternlab.patterns.length; j++) {
if (patternlab.patterns[j].patternPartial === partial ||
patternlab.patterns[j].relPath.indexOf(partial) > -1)
{
partialPath = patternlab.patterns[j].relPath;
}
}
//recurse through nested partials to fill out this extended template.
processPatternRecursive(partialPath, patternlab);
//complete assembly of extended template
//create a copy of the partial so as to not pollute it after the getPartial call.
var partialPattern = getPartial(partial, patternlab);
var cleanPartialPattern = jsonCopy(partialPattern, `partial pattern ${partial}`);
//if partial has style modifier data, replace the styleModifier value
if (currentPattern.stylePartials && currentPattern.stylePartials.length > 0) {
style_modifier_hunter.consume_style_modifier(cleanPartialPattern, foundPatternPartials[i], patternlab);
}
currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], cleanPartialPattern.extendedTemplate);
}
}
function parseDataLinksHelper(patternlab, obj, key) {
var linkRE, dataObjAsString, linkMatches;
//check for 'link.patternPartial'
linkRE = /(?:'|")(link\.[A-z0-9-_]+)(?:'|")/g;
//stringify the passed in object
dataObjAsString = JSON.stringify(obj);
if (!dataObjAsString) { return obj; }
//find matches
linkMatches = dataObjAsString.match(linkRE);
if (linkMatches) {
for (var i = 0; i < linkMatches.length; i++) {
var dataLink = linkMatches[i];
if (dataLink && dataLink.split('.').length >= 2) {
//get the partial the link refers to
var linkPatternPartial = dataLink.split('.')[1].replace('"', '').replace("'", "");
var pattern = getPartial(linkPatternPartial, patternlab);
if (pattern !== undefined) {
//get the full built link and replace it
var fullLink = patternlab.data.link[linkPatternPartial];
if (fullLink) {
fullLink = path.normalize(fullLink).replace(/\\/g, '/');
if (patternlab.config.debug) {
console.log('expanded data link from ' + dataLink + ' to ' + fullLink + ' inside ' + key);
}
//also make sure our global replace didn't mess up a protocol
fullLink = fullLink.replace(/:\//g, '://');
dataObjAsString = dataObjAsString.replace('link.' + linkPatternPartial, fullLink);
}
} else {
if (patternlab.config.debug) {
console.log('pattern not found for', dataLink, 'inside', key);
}
}
}
}
}
var dataObj;
try {
dataObj = JSON.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].patternPartial);
}
}
return {
mark_modified_patterns: function (lastModified, patternlab) {
return markModifiedPatterns(lastModified, patternlab);
},
find_pattern_partials: function (pattern) {
return pattern.findPartials();
},
find_pattern_partials_with_style_modifiers: function (pattern) {
return pattern.findPartialsWithStyleModifiers();
},
find_pattern_partials_with_parameters: function (pattern) {
return pattern.findPartialsWithPatternParameters();
},
find_list_items: function (pattern) {
return pattern.findListItems();
},
setPatternState: function (pattern, patternlab, displayDeprecatedWarning) {
setState(pattern, patternlab, displayDeprecatedWarning);
},
addPattern: function (pattern, patternlab) {
addPattern(pattern, patternlab);
},
addSubtypePattern: function (subtypePattern, patternlab) {
addSubtypePattern(subtypePattern, patternlab);
},
decomposePattern: function (pattern, patternlab, ignoreLineage) {
decomposePattern(pattern, patternlab, ignoreLineage);
},
renderPattern: function (template, data, partials) {
return renderPattern(template, data, partials);
},
process_pattern_iterative: function (file, patternlab) {
return processPatternIterative(file, patternlab);
},
process_pattern_recursive: function (file, patternlab, additionalData) {
processPatternRecursive(file, patternlab, additionalData);
},
getPartial: function (partial, patternlab) {
return getPartial(partial, patternlab);
},
combine_listItems: function (patternlab) {
buildListItems(patternlab);
},
parse_data_links: function (patternlab) {
parseDataLinks(patternlab);
},
parse_data_links_specific: function (patternlab, data, label) {
return parseDataLinksHelper(patternlab, data, label);
},
parse_pattern_markdown: function (pattern, patternlab) {
parsePatternMarkdown(pattern, patternlab);
}
};
};
module.exports = pattern_assembler;