From 3a18e0d2da6a1c432a5a7f188d4de33cd3b51e89 Mon Sep 17 00:00:00 2001 From: Doug Swain Date: Sat, 6 May 2017 12:47:19 -0400 Subject: [PATCH 1/4] Add in new public function - lookupAll(). This finds all MIME types assocaited with a particular file extension. --- README.md | 15 ++++++++++ index.js | 77 +++++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 4 ++- test/test.js | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4579db6..1cbe350 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,21 @@ mime.lookup('folder/.htaccess') // false mime.lookup('cats') // false ``` +### mime.lookupAll(path) + +Find all MIME type associated with a file. + +```js +mime.lookupAll('json') // ['application/json'] +mime.lookupAll('.rtf') // ['application/rtf', 'text/rtf'] +mime.lookupAll('file.bmp') // ['image/bmp', 'image/x-ms-bmp'] +mime.lookupAll('folder/file.js') // ['application/javascript'] +mime.lookupAll('folder/.htaccess') // [] + +mime.lookupAll('cats') // [] +mime.lookupAll(42) // false +``` + ### mime.contentType(type) Create a full content-type header given a content-type or extension. diff --git a/index.js b/index.js index 6e0ea43..b9702d8 100644 --- a/index.js +++ b/index.js @@ -22,6 +22,7 @@ var extname = require('path').extname var extractTypeRegExp = /^\s*([^;\s]*)(?:;|\s|$)/ var textTypeRegExp = /^text\//i +var typeSets = {} /** * Module exports. @@ -34,11 +35,15 @@ exports.contentType = contentType exports.extension = extension exports.extensions = Object.create(null) exports.lookup = lookup +exports.lookupAll = lookupAll exports.types = Object.create(null) // Populate the extensions/types maps populateMaps(exports.extensions, exports.types) +// Populate the extensions->[types] set +populateTypeSets(typeSets) + /** * Get the default charset for a MIME type. * @@ -123,13 +128,16 @@ function extension (type) { } /** - * Lookup the MIME type for a file path/extension. - * + * Obtain the extension of a filename or filepath. + * If the path is not a string or a proper extension isn't found, + * false is returned. + * The path is case insensitive (so hello.html and HELLO.HTML are equal). + * * @param {string} path - * @return {boolean|string} + * @return {boolean|string} the file extension if available. false otherwise. */ -function lookup (path) { +function extractExtension (path) { if (!path || typeof path !== 'string') { return false } @@ -143,9 +151,44 @@ function lookup (path) { return false } + return extension +} + +/** + * Lookup the MIME type for a file path/extension. + * + * @param {string} path + * @return {boolean|string} + */ + +function lookup (path) { + var extension = extractExtension(path) + + if (!extension) { + return false + } + return exports.types[extension] || false } +/** + * Find all MIME types that are associated with a file extensions. + * + * @param {string} path or file extension + * @return {boolean|array} + */ + +function lookupAll (path) { + var extension = extractExtension(path) + + if (!extension) { + return false + } + + return typeSets[extension] || [] +} + + /** * Populate the extensions and types maps. * @private @@ -186,3 +229,29 @@ function populateMaps (extensions, types) { } }) } + +/** + * Populate the set where extension->[types] + * An example is .rtf -> ['application/rtf', 'text/rtf'] + * + * @private + * @param {object>} the map to fill in + */ + +function populateTypeSets (typeSets) { + Object.keys(db).forEach(function (type) { + var exts = db[type].extensions + + if (!exts || !exts.length) { + return + } + + exts.forEach(function (ext) { + if (typeSets[ext]) { + typeSets[ext].push(type) + } else { + typeSets[ext] = [type] + } + }) + }) +} diff --git a/package.json b/package.json index a54aa2f..9df152c 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "eslint-plugin-promise": "3.5.0", "eslint-plugin-standard": "2.1.1", "istanbul": "0.4.5", - "mocha": "1.21.5" + "mocha": "1.21.5", + "nodemon": "^1.11.0" }, "files": [ "HISTORY.md", @@ -35,6 +36,7 @@ "scripts": { "lint": "eslint .", "test": "mocha --reporter spec test/test.js", + "test:watch": "nodemon --exec npm test", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot test/test.js", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter dot test/test.js" } diff --git a/test/test.js b/test/test.js index 472cbcc..69614ab 100644 --- a/test/test.js +++ b/test/test.js @@ -224,4 +224,77 @@ describe('mimeTypes', function () { }) }) }) + + describe('.lookupAll(extension)', function () { + it('should return a list with multiple mime types when many exist', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('.rtf'), ['application/rtf', 'text/rtf']) + }) + + it('should return a list with one mime type when only one exists', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('.html'), ['text/html']) + }) + + it('should work without a leading dot', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('rtf'), ['application/rtf', 'text/rtf']) + }) + + it('should be case-insensitive', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('RtF'), ['application/rtf', 'text/rtf']) + assert.deepStrictEqual(mimeTypes.lookupAll('.HTML'), ['text/html']) + }) + + it('should return an empty list for an unknown extension', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('bogus'), []) + }) + + it('should return false for non-strings', function () { + assert.strictEqual(mimeTypes.lookupAll(null), false) + assert.strictEqual(mimeTypes.lookupAll(undefined), false) + assert.strictEqual(mimeTypes.lookupAll(3.141592), false) + assert.strictEqual(mimeTypes.lookupAll({}), false) + }) + }) + + describe('.lookupAll(path)', function () { + it('should return mime type for file name', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('page.html'), ['text/html']) + }) + + it('should return mime type for relative path', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('path/to/page.html'), ['text/html']) + assert.deepStrictEqual(mimeTypes.lookupAll('path\\to\\doc.rtf'), ['application/rtf', 'text/rtf']) + }) + + it('should return mime type for absolute path', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('/path/to/page.html'), ['text/html']) + assert.deepStrictEqual(mimeTypes.lookupAll('C:\\path\\to\\doc.rtf'), ['application/rtf', 'text/rtf']) + }) + + it('should be case insensitive', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('/path/to/PAGE.HTML'), ['text/html']) + assert.deepStrictEqual(mimeTypes.lookupAll('C:\\path\\to\\DOC.RTF'), ['application/rtf', 'text/rtf']) + }) + + it('should return an empty list for unknown extension', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('/path/to/file.bogus'), []) + }) + + it('should return false for path without extension', function () { + assert.strictEqual(mimeTypes.lookupAll('/path/to/json'), false) + }) + + describe('lookupAll() - path with dotfile', function () { + it('should return false when extension-less', function () { + assert.strictEqual(mimeTypes.lookupAll('/path/to/.json'), false) + }) + + it('should return all mime types when there is extension', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('/path/to/.config.json'), ['application/json']) + }) + + it('should return all mime types when there is extension, but no path', function () { + assert.deepStrictEqual(mimeTypes.lookupAll('.config.json'), ['application/json']) + }) + }) + }) }) From 7c34504023b7982d7b730bc7e6bd15580e8c7aa2 Mon Sep 17 00:00:00 2001 From: Doug Swain Date: Sat, 6 May 2017 13:43:07 -0400 Subject: [PATCH 2/4] Fix minor lint issues. --- index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.js b/index.js index b9702d8..ca91454 100644 --- a/index.js +++ b/index.js @@ -132,7 +132,7 @@ function extension (type) { * If the path is not a string or a proper extension isn't found, * false is returned. * The path is case insensitive (so hello.html and HELLO.HTML are equal). - * + * * @param {string} path * @return {boolean|string} the file extension if available. false otherwise. */ @@ -188,7 +188,6 @@ function lookupAll (path) { return typeSets[extension] || [] } - /** * Populate the extensions and types maps. * @private From ca04fe00b5f2cd26f5bc2dd0ee4f5e9b73cb450c Mon Sep 17 00:00:00 2001 From: Doug Swain Date: Sat, 6 May 2017 14:36:38 -0400 Subject: [PATCH 3/4] Update test to work on older versions of Node. --- test/test.js | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/test/test.js b/test/test.js index 69614ab..b30af35 100644 --- a/test/test.js +++ b/test/test.js @@ -2,6 +2,12 @@ var assert = require('assert') var mimeTypes = require('..') +var allMatch = function (actual, expected) { + expected.forEach(function (expectedType) { + assert.ok(actual.indexOf(expectedType) > -1) + }) +} + describe('mimeTypes', function () { describe('.charset(type)', function () { it('should return "UTF-8" for "application/json"', function () { @@ -227,24 +233,24 @@ describe('mimeTypes', function () { describe('.lookupAll(extension)', function () { it('should return a list with multiple mime types when many exist', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('.rtf'), ['application/rtf', 'text/rtf']) + allMatch(mimeTypes.lookupAll('.rtf'), ['application/rtf', 'text/rtf']) }) it('should return a list with one mime type when only one exists', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('.html'), ['text/html']) + allMatch(mimeTypes.lookupAll('.html'), ['text/html']) }) it('should work without a leading dot', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('rtf'), ['application/rtf', 'text/rtf']) + allMatch(mimeTypes.lookupAll('rtf'), ['application/rtf', 'text/rtf']) }) it('should be case-insensitive', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('RtF'), ['application/rtf', 'text/rtf']) - assert.deepStrictEqual(mimeTypes.lookupAll('.HTML'), ['text/html']) + allMatch(mimeTypes.lookupAll('RtF'), ['application/rtf', 'text/rtf']) + allMatch(mimeTypes.lookupAll('.HTML'), ['text/html']) }) it('should return an empty list for an unknown extension', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('bogus'), []) + allMatch(mimeTypes.lookupAll('bogus'), []) }) it('should return false for non-strings', function () { @@ -257,26 +263,26 @@ describe('mimeTypes', function () { describe('.lookupAll(path)', function () { it('should return mime type for file name', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('page.html'), ['text/html']) + allMatch(mimeTypes.lookupAll('page.html'), ['text/html']) }) it('should return mime type for relative path', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('path/to/page.html'), ['text/html']) - assert.deepStrictEqual(mimeTypes.lookupAll('path\\to\\doc.rtf'), ['application/rtf', 'text/rtf']) + allMatch(mimeTypes.lookupAll('path/to/page.html'), ['text/html']) + allMatch(mimeTypes.lookupAll('path\\to\\doc.rtf'), ['application/rtf', 'text/rtf']) }) it('should return mime type for absolute path', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('/path/to/page.html'), ['text/html']) - assert.deepStrictEqual(mimeTypes.lookupAll('C:\\path\\to\\doc.rtf'), ['application/rtf', 'text/rtf']) + allMatch(mimeTypes.lookupAll('/path/to/page.html'), ['text/html']) + allMatch(mimeTypes.lookupAll('C:\\path\\to\\doc.rtf'), ['application/rtf', 'text/rtf']) }) it('should be case insensitive', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('/path/to/PAGE.HTML'), ['text/html']) - assert.deepStrictEqual(mimeTypes.lookupAll('C:\\path\\to\\DOC.RTF'), ['application/rtf', 'text/rtf']) + allMatch(mimeTypes.lookupAll('/path/to/PAGE.HTML'), ['text/html']) + allMatch(mimeTypes.lookupAll('C:\\path\\to\\DOC.RTF'), ['application/rtf', 'text/rtf']) }) it('should return an empty list for unknown extension', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('/path/to/file.bogus'), []) + allMatch(mimeTypes.lookupAll('/path/to/file.bogus'), []) }) it('should return false for path without extension', function () { @@ -289,11 +295,11 @@ describe('mimeTypes', function () { }) it('should return all mime types when there is extension', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('/path/to/.config.json'), ['application/json']) + allMatch(mimeTypes.lookupAll('/path/to/.config.json'), ['application/json']) }) it('should return all mime types when there is extension, but no path', function () { - assert.deepStrictEqual(mimeTypes.lookupAll('.config.json'), ['application/json']) + allMatch(mimeTypes.lookupAll('.config.json'), ['application/json']) }) }) }) From 6b5ef747f958d848f29e9f7108efcc6f0d7b6479 Mon Sep 17 00:00:00 2001 From: Doug Swain Date: Sat, 6 May 2017 14:36:54 -0400 Subject: [PATCH 4/4] Remove test:watch command to support older environment. --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 9df152c..a54aa2f 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,7 @@ "eslint-plugin-promise": "3.5.0", "eslint-plugin-standard": "2.1.1", "istanbul": "0.4.5", - "mocha": "1.21.5", - "nodemon": "^1.11.0" + "mocha": "1.21.5" }, "files": [ "HISTORY.md", @@ -36,7 +35,6 @@ "scripts": { "lint": "eslint .", "test": "mocha --reporter spec test/test.js", - "test:watch": "nodemon --exec npm test", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot test/test.js", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter dot test/test.js" }