Permalink
Browse files

First pass at v2

  • Loading branch information...
broofa committed Jun 16, 2017
1 parent ccbac35 commit a32be4341815d29bd581230fe1616e0ea013584a
Showing with 464 additions and 185 deletions.
  1. +3 −1 .gitignore
  2. +62 −0 Mime.js
  3. +75 −9 README.md
  4. +97 −7 build/build.js
  5. +219 −57 build/test.js
  6. +2 −0 index.js
  7. +0 −108 mime.js
  8. +4 −3 package.json
  9. +1 −0 types/other.json
  10. +1 −0 types/standard.json
View
@@ -1,2 +1,4 @@
*.sw*
.DS_Store
node_modules
npm-debug.log
package-lock.json
View
62 Mime.js
@@ -0,0 +1,62 @@
class Mime {
constructor(...typeMaps) {
this.types = Object.create(null);
this.extensions = Object.create(null);
typeMaps.forEach(map => this.define(map));
// Deprecated API
this.lookup = this.getType;
}
/**
* Define mimetype -> extension mappings. Each key is a mime-type that maps
* to an array of extensions associated with the type. The first extension is
* used as the default extension for the type.
*
* e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
*
* @param map (Object) type definitions
*/
define(typeMap, force) {
for (let type in typeMap) {
const extensions = typeMap[type];
for (let i = 0; i < extensions.length; i++) {
const ext = extensions[i];
if (!force && (ext in this.types)) {
throw new Error(`Attempt to change mapping for "${ext}" extension from "${this.types[ext]}" to "${type}". Pass \`force=true\` to allow this, otherwise remove "${ext}" from the list of extensions for "${type}".`);
}
this.types[ext] = type;
}
// Use first extension as default
if (force || !this.extensions[type]) {
this.extensions[type] = extensions[0];
}
}
}
/**
* Lookup a mime type based on extension
*/
getType(path) {
path = String(path);
const last = path.replace(/.*[\/\\]/, '').toLowerCase();
const ext = last.replace(/.*\./, '').toLowerCase();
const hasPath = last.length < path.length;
const hasDot = ext.length < last.length - 1;
return (hasDot || !hasPath) && this.types[ext] || null;
}
/**
* Return file extension associated with a mime type
*/
getExtension(type) {
type = /^\s*([^;\s]*)/.test(type) && RegExp.$1;
return type && this.extensions[type.toLowerCase()] || null;
}
}
module.exports = Mime;
View
@@ -1,25 +1,90 @@
# mime
An opinionated and compact mime API
Comprehensive MIME type mapping API based on mime-db module.
## Requirements
CommonJS & ES6
Using something else? Install the old version via `npm install mime@1.3.6`
## Install
```
npm install mime
```
## Usage
```
// Get a Mime object pre-loaded with all 900+ types in mime-db
const mime = require('mime');
// ... or, for a more compact set of mappings
const mime = require('mime/lite') ; // Omits vendor and experimental types
// ... or roll your own
const Mime = require('mime/Mime');
const mime = new Mime({
'text/plain': ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini'],
'text/html': ['html', 'htm', 'shtml'],
// etc...
});
```
Install with [npm](http://github.com/isaacs/npm):
## Mime API
npm install mime
### new Mime(extensions_by_type)
## Contributing / Testing
### mime.types[*extension*]
Note: On ES6 systems this object will be Read-only
npm run test
### mime.extensions[*type*]
Note: On ES6 systems this object will be Read-only
### mime.typeForPath(*path*)
Shortcut for `mime.types[mime.extname(extension_or_path)]`
NOTE: If
### mime.extname(path)
Get extension of a file name or path. Similar to NodeJS' `path.extname`, but slightly different. E.g.
```
mime.extname(''); // => null
mime.extname('abc'); // => null
mime.extname('.abc'); // => 'abc'
mime.extname('hello.abc'); // => 'abc'
mime.extname('hello/world.abc'); // => 'abc'
mime.extname('hello\\world.abc'); // => 'abc'
mime.extname('coder/says.what/hello\\world.abc'); // => 'abc'
```
## Command Line
mime [path_string]
/\.([^\/\\]+$)/.test('a.b\/gef.abc') && RegExp.$1
E.g.
```
```
Note: This behavior is different from legacy `mime.lookup()` behavior. Passing
a bare extension will result in `null`.
### mime.charset(type)
## Command Line
```
mime [path_string]
> mime scripts/jquery.js
application/javascript
```
============================ ============================ ============================
============================ ============================ ============================
============================ ============================ ============================
============================ ============================ ============================
============================ ============================ ============================
============================ ============================ ============================
============================ ============================ ============================
============================ ============================ ============================
> mime scripts/jquery.js
application/javascript
## API - Queries
@@ -47,6 +112,7 @@ mime.extension('application/octet-stream'); // => 'bin'
```
### mime.charsets.lookup()
de
Map mime-type to charset
View
104 build/build.js 100644 → 100755
@@ -1,11 +1,101 @@
var db = require('mime-db');
#!/usr/bin/env node
var mapByType = {};
Object.keys(db).forEach(function(key) {
var extensions = db[key].extensions;
if (extensions) {
mapByType[key] = extensions;
const fs = require('fs');
const path = require('path');
let db = require('mime-db');
let chalk = require('chalk');
const STANDARD_FACET_SCORE = 900;
// Get a mimetype "score" that can be used to resolve conflicts over extensions
// in a deterministic way
function getScore(entry) {
let pri = 0;
const [type, subtype] = entry.type.split('/');
const facet = /^([a-z]+\.|x-)/.test(subtype) && RegExp.$1 || undefined;
// https://tools.ietf.org/html/rfc6838#section-3 defines "facets" that can be
// used to distinguish standard .vs. vendore .vs. experimental .vs. personal
// mime types.
switch (facet) {
case 'vnd.': pri += 400; break;
case 'x.': pri += 300; break;
case 'x-': pri += 200; break;
case 'prs.': pri += 100; break;
default: pri += STANDARD_FACET_SCORE;
}
// Use mime-db's logic for ranking by source
switch(entry.source) {
// Prioritize by source (same as mime-types module)
case 'iana': pri += 40; break;
case 'apache': pri += 20; break;
case 'nginx': pri += 10; break;
default: pri += 30; break;
}
// Prefer text over other types (mainly to resolve text/xml v application/xml)
switch(type) {
case 'application': pri += 1; break;
default: break;
}
// All other things being equal, use length
pri += 1 - entry.type.length/100;
return pri;
}
const byExtension = {};
// Clear out any conflict extensions in mime-db
for (let type in db) {
let entry = db[type];
entry.type = type;
if (!entry.extensions) continue;
entry.extensions.forEach(ext => {
if (ext in byExtension) {
const e0 = entry, e1 = byExtension[ext];
e0.pri = getScore(e0);
e1.pri = getScore(e1);
let drop = e0.pri < e1.pri ? e0 : e1, keep = e0.pri >= e1.pri ? e0 : e1;
drop.extensions = drop.extensions.filter(e => e != ext);
console.log(`${ext}: Keeping ${chalk.green(keep.type)} (${keep.pri}), dropping ${chalk.red(drop.type)} (${drop.pri})`);
}
byExtension[ext] = entry;
});
//maps[map][key] = extensions;
}
function writeTypesFile(types, path) {
fs.writeFileSync(path, JSON.stringify(types));
}
// Segregate into standard and non-standard types based on facet per
// https://tools.ietf.org/html/rfc6838#section-3.1
const standard = {};
const other = {};
Object.keys(db).sort().forEach(k => {
const entry = db[k];
if (entry.extensions) {
if (getScore(entry) >= STANDARD_FACET_SCORE) {
standard[entry.type] = entry.extensions;
} else {
other[entry.type] = entry.extensions;
}
}
});
console.log(JSON.stringify(mapByType));
writeTypesFile(standard, path.join(__dirname, '../types', 'standard.json'));
writeTypesFile(other, path.join(__dirname, '../types', 'other.json'));
//console.log(JSON.stringify(maps, null, 2));
Oops, something went wrong.

0 comments on commit a32be43

Please sign in to comment.