From eff9c54cd2e46bf0f5985142bd495b9630afc35b Mon Sep 17 00:00:00 2001 From: Joel Kemp Date: Sun, 26 Jul 2015 16:17:14 -0400 Subject: [PATCH] Initial --- .gitignore | 1 + .jscsrc | 3 ++ .jshintrc | 3 ++ .travis.yml | 8 +++ bin/cli.js | 28 ++++++++++ index.js | 83 +++++++++++++++++++++++++++++ package.json | 53 +++++++++++++++++++ readme.md | 59 +++++++++++++++++++++ test/test.js | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 385 insertions(+) create mode 100644 .gitignore create mode 100644 .jscsrc create mode 100644 .jshintrc create mode 100644 .travis.yml create mode 100644 bin/cli.js create mode 100644 index.js create mode 100644 package.json create mode 100644 readme.md create mode 100644 test/test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..a663e1f --- /dev/null +++ b/.jscsrc @@ -0,0 +1,3 @@ +{ +"preset": "google" +} \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..21d695e --- /dev/null +++ b/.jshintrc @@ -0,0 +1,3 @@ +{ +"unused": true +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..77a702f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - "0.10" + +notifications: + email: false + +sudo: false \ No newline at end of file diff --git a/bin/cli.js b/bin/cli.js new file mode 100644 index 0000000..b338d6a --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +'use strict'; + +var program = require('commander'); +var cabinet = require('../'); + +program + .version(require('../package.json').version) + .usage('[options] ') + .option('-d, --directory ', 'root of all files') + .option('-c, --config [path]', 'location of a RequireJS config file for AMD') + .option('-f, --filename [path]', 'file containing the dependency') + .parse(process.argv); + +var filename = program.filename; +var directory = program.directory; +var config = program.config; +var dep = program.args[0]; + +var result = cabinet({ + partial: dep, + filename: filename, + directory: directory, + config: config +}); + +console.log(result); \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..f98b0b4 --- /dev/null +++ b/index.js @@ -0,0 +1,83 @@ +var path = require('path'); +var debug = require('debug')('cabinet'); + +var getModuleType = require('module-definition'); + +var amdLookup = require('module-lookup-amd'); +var stylusLookup = require('stylus-lookup'); +var sassLookup = require('sass-lookup'); +var resolveDependencyPath = require('resolve-dependency-path'); +var assign = require('lodash.assign'); + +var defaultLookups = {}; + +module.exports = function(options) { + // Lazy binding for test stubbing purposes + defaultLookups = assign(defaultLookups, { + '.js': jsLookup, + '.scss': sassLookup, + '.sass': sassLookup, + '.styl': stylusLookup + }); + + var partial = options.partial; + var filename = options.filename; + var directory = options.directory; + var config = options.config; + + var ext = path.extname(filename); + + var resolver = defaultLookups[ext]; + + if (!resolver) { + debug('using generic resolver'); + resolver = resolveDependencyPath; + } + + debug('found a resolver for ' + ext); + return resolver(partial, filename, directory, config); +}; + +/** + * Register a custom lookup resolver for a file extension + * + * @param {String} extension - The file extension that should use the resolver + * @param {Function} lookupStrategy - A resolver of partial paths + */ +module.exports.register = function(extension, lookupStrategy) { + defaultLookups[extension] = lookupStrategy; +}; + +/** + * @private + * @param {String} partial + * @param {String} filename + * @param {String} directory + * @param {String} config + * @return {String} + */ +function jsLookup(partial, filename, directory, config) { + var type = getModuleType.sync(filename); + + switch (type) { + case 'amd': + debug('using amd resolver'); + return amdLookup(config, partial, filename, directory) + case 'commonjs': + debug('using commonjs resolver'); + return commonJSLookup(partial); + case 'es6': + default: + debug('using generic resolver for es6'); + return resolveDependencyPath(partial, filename, directory); + } +} + +/** + * @private + * @param {String} partial + * @return {String} + */ +function commonJSLookup(partial) { + return require.resolve(partial); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..37174de --- /dev/null +++ b/package.json @@ -0,0 +1,53 @@ +{ + "name": "filing-cabinet", + "version": "1.0.0", + "description": "Find files based on partial paths", + "main": "index.js", + "bin": { + "filing-cabinet": "cli.js" + }, + "directories": { + "test": "test" + }, + "scripts": { + "test": "jscs index.js test/test.js && mocha" + }, + "repository": { + "type": "git", + "url": "https://github.com/mrjoelkemp/node-filing-cabinet.git" + }, + "keywords": [ + "lookup", + "es6", + "amd", + "commonjs", + "sass", + "stylus", + "partial", + "resolution", + "paths" + ], + "author": "Joel Kemp (http://www.mrjoelkemp.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/mrjoelkemp/node-filing-cabinet/issues" + }, + "homepage": "https://github.com/mrjoelkemp/node-filing-cabinet", + "devDependencies": { + "jscs": "~1.13.1", + "mocha": "~2.2.5", + "mock-fs": "~3.0.0", + "rewire": "~2.3.4", + "sinon": "~1.15.4" + }, + "dependencies": { + "commander": "~2.8.1", + "debug": "~2.2.0", + "lodash.assign": "~3.2.0", + "module-definition": "~2.2.2", + "module-lookup-amd": "~2.0.4", + "resolve-dependency-path": "~1.0.2", + "sass-lookup": "~1.0.2", + "stylus-lookup": "~1.0.0" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..f7773e9 --- /dev/null +++ b/readme.md @@ -0,0 +1,59 @@ +### filing-cabinet [![npm](http://img.shields.io/npm/v/filing-cabinet.svg)](https://npmjs.org/package/filing-cabinet) [![npm](http://img.shields.io/npm/dm/filing-cabinet.svg)](https://npmjs.org/package/filing-cabinet) + +> Look up a filename based on a partial path + +`npm install filing-cabinet` + +### Usage + +```js + +var cabinet = require('filing-cabinet'); + +var result = cabinet({ + partial: 'somePartialPath', + directory: 'path/to/all/files', + filename: 'path/to/parent/file', + config: 'path/to/requirejs/config' +}); + +console.log(result); +``` + +* `partial`: the dependency path + * This could be in any of the registered languages + + +### Registered languages + +By default, filing-cabinet provides support for the following languages: + +* JavaScript (all files with the `.js` extension) +* Sass (`.scss` and `.sass`) +* Stylus (`.styl`) + +You can register resolvers for new languages via `cabinet.register(extension, resolver)`. + +* `extension`: the extension of the file that should use the custom resolver (ex: '.py', '.php') +* `resolver`: a function that accepts the following (ordered) arguments that were given to cabinet: + * `partial` + * `filename` + * `directory` + * `config` + +For examples of resolver implementations, take a look at the default language resolvers: + +* [sass-lookup](https://github.com/mrjoelkemp/node-sass-lookup) +* [stylus-lookup](https://github.com/mrjoelkemp/node-stylus-lookup) +* [amdLookup](https://github.com/mrjoelkemp/node-module-lookup-amd) + +If a given extension does not have a registered resolver, cabinet will use +a generic file resolver which is basically `require('path').join` with a bit of extension defaulting logic. + +### Shell script + +* Requires a global install `npm install -g filing-cabinet` + +`filing-cabinet [options] ` + +* See `filing-cabinet --help` for details on the options \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..c10a77e --- /dev/null +++ b/test/test.js @@ -0,0 +1,147 @@ +var assert = require('assert'); +var sinon = require('sinon'); +var rewire = require('rewire'); +var mock = require('mock-fs'); + +var cabinet = rewire('../'); + +describe('filing-cabinet', function() { + describe('JavaScript', function() { + beforeEach(function() { + mock({ + 'js': { + 'es6': { + 'foo.js': 'import bar from "./bar";', + 'bar.js': 'export default function() {};' + }, + 'amd': { + 'foo.js': 'define(["./bar"], function(bar){ return bar; });', + 'bar.js': 'define({});' + }, + 'commonjs': { + 'foo.js': 'var bar = require("./bar");', + 'bar.js': 'module.exports = function() {};' + } + } + }); + }); + + describe('es6', function() { + it('uses a generic resolver', function() { + var stub = sinon.stub(); + var revert = cabinet.__set__('resolveDependencyPath', stub); + + var path = cabinet({ + partial: './bar', + filename: 'js/es6/foo.js', + directory: 'js/es6/' + }); + + assert.ok(stub.called); + + revert(); + }); + }); + + describe('amd', function() { + it('uses the amd resolver', function() { + var stub = sinon.stub(); + var revert = cabinet.__set__('amdLookup', stub); + + var path = cabinet({ + partial: './bar', + filename: 'js/amd/foo.js', + directory: 'js/amd/' + }); + + assert.ok(stub.called); + + revert(); + }); + }); + + describe('commonjs', function() { + it('uses require\'s resolver', function() { + var stub = sinon.stub(); + var revert = cabinet.__set__('commonJSLookup', stub); + + var path = cabinet({ + partial: './bar', + filename: 'js/commonjs/foo.js', + directory: 'js/commonjs/' + }); + + assert.ok(stub.called); + + revert(); + }); + }); + }); + + describe('CSS', function() { + describe('Sass', function() { + it('uses the sass resolver for .scss files', function() { + var stub = sinon.stub(); + var revert = cabinet.__set__('sassLookup', stub); + + var path = cabinet({ + partial: './bar', + filename: 'js/sass/foo.scss', + directory: 'js/sass/' + }); + + assert.ok(stub.called); + + revert(); + }); + + it('uses the sass resolver for .sass files', function() { + var stub = sinon.stub(); + var revert = cabinet.__set__('sassLookup', stub); + + var path = cabinet({ + partial: './bar', + filename: 'sass/foo.sass', + directory: 'sass/' + }); + + assert.ok(stub.called); + + revert(); + }); + }); + + describe('stylus', function() { + it('uses the stylus resolver', function() { + var stub = sinon.stub(); + var revert = cabinet.__set__('stylusLookup', stub); + + var path = cabinet({ + partial: './bar', + filename: 'stylus/foo.styl', + directory: 'stylus/' + }); + + assert.ok(stub.called); + + revert(); + }); + }); + }); + + describe('.register', function() { + it('registers a custom resolver for a given extension', function() { + var stub = sinon.stub().returns('foo'); + cabinet.register('.foobar', stub); + + var path = cabinet({ + partial: './bar', + filename: 'js/amd/foo.foobar', + directory: 'js/amd/' + }); + + assert.ok(stub.called); + assert.equal(path, 'foo'); + }); + }); +});