Skip to content

Commit

Permalink
Merge pull request #736 from pattern-lab/feature/735-scoped-engines
Browse files Browse the repository at this point in the history
Adding better traverse for pattern engines
  • Loading branch information
bmuenzenmeyer authored Oct 23, 2017
2 parents 80a8017 + c511e7d commit cf5cc22
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 47 deletions.
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

0 comments on commit cf5cc22

Please sign in to comment.