Skip to content

Adding better traverse for pattern engines #736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ language: node_js

node_js:
- node
- 8
- 6

before_install:
- phantomjs --version

before_script:
- npm install patternengine-node-mustache
- npm install patternengine-node-underscore
- npm install patternengine-node-handlebars
- npm install @pattern-lab/patternengine-node-mustache
- npm install @pattern-lab/patternengine-node-underscore
- npm install @pattern-lab/patternengine-node-handlebars
- npm install patternengine-node-twig

branches:
only:
- master
- dev
- dev-3.0

notifications:
webhooks:
Expand Down
142 changes: 99 additions & 43 deletions core/lib/pattern_engines.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,103 @@
// special shoutout to Geoffrey Pursell for single-handedly making Pattern Lab Node Pattern Engines possible!
'use strict';
const {existsSync, lstatSync, readdirSync} = require('fs');
const path = require('path');
const diveSync = require('diveSync');
const chalk = require('chalk');
const engineMatcher = /^patternengine-node-(.*)$/;
const enginesDirectories = [
{
displayName: 'the core',
path: path.resolve(__dirname, '..', '..', 'node_modules')
},
{
displayName: 'the edition or test directory',
path: path.join(process.cwd(), 'node_modules')
}
];
const scopeMatch = /^@(.*)$/;
const isDir = fPath => lstatSync(fPath).isDirectory();

const enginesDirectories = [{
displayName: 'the core',
path: path.resolve(__dirname, '..', '..', 'node_modules')
}, {
displayName: 'the edition or test directory',
path: path.join(process.cwd(), 'node_modules')
}];

// given a path: return the engine name if the path points to a valid engine
// module directory, or false if it doesn't
function isEngineModule(filePath) {
const baseName = path.basename(filePath);
const engineMatch = baseName.match(engineMatcher);

if (engineMatch) { return engineMatch[1]; }
return false;
}

function findEngineModulesInDirectory(dir) {
const foundEngines = [];
/**
* @name isScopedPackage
* @desc Checks whether a path in modules belongs to a scoped package
* @param {string} filePath - The pathname to check
* @return {Boolean} - Returns a bool when found, false othersie
*/
function isScopedPackage(filePath) {
const baseName = path.basename(filePath);
return scopeMatch.test(baseName);
}

diveSync(dir, {
recursive: false,
directories: true
}, function (err, filePath) {
if (err) { throw err; }
const foundEngineName = isEngineModule(filePath);
if (foundEngineName) {
foundEngines.push({
name: foundEngineName,
modulePath: filePath
});
}
});
/**
* @name resolveEngines
* @desc Creates an array of all available patternlab engines
* @param {string} dir - The directory to search for engines and scoped engines)
* @return {Array<Engine>} An array of engine objects
*/
function resolveEngines(dir) {

// Guard against non-existent directories.
if (!existsSync(dir)) {
return []; // Silence is golden …
}

/**
* @name walk
* @desc Traverse the given path and gather possible engines
* @param {string} fPath - The file path to traverse
* @param {Array<Engine>} engines - An array of engines from the inner most matches
* @return {Array<Engine>} - The final array of engines
*/
const walk = (fPath, engines) => {

/**
* @name dirList
* @desc A list of all directories in the given path
* @type {Array<string>}
*/
const dirList = readdirSync(fPath).filter(p => isDir(path.join(fPath, p)));

/**
* @name e
* @desc For the current dir get all engines
* @type {Array<Engine>}
*/
const e = engines.concat(dirList
.filter(isEngineModule)
.map(engine => {
return {
name: isEngineModule(engine),
modulePath: path.join(fPath, engine)
}
})
);

/**
* 1. Flatten all engines from inner recursions and current dir
* 2. Filter the dirList for scoped packages
* 3. Map over every scoped package and recurse into it to find scoped engines
*/
return [].concat(
...e,
...dirList
.filter(isScopedPackage) // 2
.map(scope => walk(path.join(fPath, scope), e)) // 3
);
};

return walk(dir, []);
}

function findEngineModulesInDirectory(dir) {
const foundEngines = resolveEngines(dir)
return foundEngines;
}

Expand All @@ -61,18 +117,18 @@ function findEngineModulesInDirectory(dir) {
// methods and properites below should therefore be on its prototype.

const PatternEngines = Object.create({

loadAllEngines: function (patternLabConfig) {
var self = this;

// Try to load engines! We scan for engines at each path specified above. This
// function is kind of a big deal.
enginesDirectories.forEach(function (engineDirectory) {
const enginesInThisDir = findEngineModulesInDirectory(engineDirectory.path);
if (patternLabConfig.debug) {
console.log(chalk.bold(`Loading engines from ${engineDirectory.displayName}...\n`));
}

// find all engine-named things in this directory and try to load them,
// unless it's already been loaded.
enginesInThisDir.forEach(function (engineDiscovery) {
Expand All @@ -81,7 +137,7 @@ const PatternEngines = Object.create({
if (patternLabConfig.debug) {
chalk.green(successMessage);
}

try {
// Give it a try! load 'er up. But not if we already have,
// of course. Also pass the pattern lab config object into
Expand All @@ -108,7 +164,7 @@ const PatternEngines = Object.create({
});
console.log('');
});

// Complain if for some reason we haven't loaded any engines.
if (Object.keys(self).length === 0) {
throw new Error('No engines loaded! Something is seriously wrong.');
Expand All @@ -117,7 +173,7 @@ const PatternEngines = Object.create({
console.log(chalk.bold('Done loading engines.\n'));
}
},

getEngineNameForPattern: function (pattern) {
// avoid circular dependency by putting this in here. TODO: is this slow?
const of = require('./object_factory');
Expand All @@ -126,7 +182,7 @@ const PatternEngines = Object.create({
const engineNames = Object.keys(this);
for (let i = 0; i < engineNames.length; i++) {
const engine = this[engineNames[i]];

if (Array.isArray(engine.engineFileExtension)) {
if (engine.engineFileExtension.includes(pattern.fileExtension)) {
return engine.engineName;
Expand All @@ -139,12 +195,12 @@ const PatternEngines = Object.create({
}
}
}

// otherwise, assume it's a plain mustache template string and act
// accordingly
return 'mustache';
},

getEngineForPattern: function (pattern) {
if (pattern.isPseudoPattern) {
return this.getEngineForPattern(pattern.basePattern);
Expand All @@ -153,7 +209,7 @@ const PatternEngines = Object.create({
return this[engineName];
}
},

// combine all found engines into a single array of supported extensions
getSupportedFileExtensions: function () {
const engineNames = Object.keys(PatternEngines);
Expand All @@ -162,19 +218,19 @@ const PatternEngines = Object.create({
});
return [].concat.apply([], allEnginesExtensions);
},

isFileExtensionSupported: function (fileExtension) {
const supportedExtensions = PatternEngines.getSupportedFileExtensions();
return (supportedExtensions.lastIndexOf(fileExtension) !== -1);
},

// given a filename, return a boolean: whether or not the filename indicates
// that the file is pseudopattern JSON
isPseudoPatternJSON: function (filename) {
const extension = path.extname(filename);
return (extension === '.json' && filename.indexOf('~') > -1);
},

// takes a filename string, not a full path; a basename (plus extension)
// ignore _underscored patterns, dotfiles, and anything not recognized by a
// loaded pattern engine. Pseudo-pattern .json files ARE considered to be
Expand All @@ -183,14 +239,14 @@ const PatternEngines = Object.create({
// skip hidden patterns/files without a second thought
const extension = path.extname(filename);
if (filename.charAt(0) === '.' ||
(extension === '.json' && !PatternEngines.isPseudoPatternJSON(filename))) {
(extension === '.json' && !PatternEngines.isPseudoPatternJSON(filename))) {
return false;
}

// not a hidden pattern, let's dig deeper
const supportedPatternFileExtensions = PatternEngines.getSupportedFileExtensions();
return (supportedPatternFileExtensions.lastIndexOf(extension) !== -1 ||
PatternEngines.isPseudoPatternJSON(filename));
PatternEngines.isPseudoPatternJSON(filename));
}
});

Expand Down