code:
    Linux
    OS X
    Windows
    Coverage
    Downloads

# ignore

`ignore` is a manager, filter and parser which implemented in pure JavaScript according to the [.gitignore spec 2.22.1](http://git-scm.com/docs/gitignore).

`ignore` is used by eslint, gitbook and [many others](https://www.npmjs.com/browse/depended/ignore).

Pay **ATTENTION** that [`minimatch`](https://www.npmjs.org/package/minimatch) (which used by `fstream-ignore`) does not follow the gitignore spec.

To filter filenames according to a .gitignore file, I recommend this npm package, `ignore`.

To parse an `.npmignore` file, you should use `minimatch`, because an `.npmignore` file is parsed by npm using `minimatch` and it does not work in the .gitignore way.

### Tested on

`ignore` is fully tested, and has more than **five hundreds** of unit tests.

- Linux + Node: `0.8` - `7.x`
- Windows + Node: `0.10` - `7.x`, node < `0.10` is not tested due to the lack of support of appveyor.

Actually, `ignore` does not rely on any versions of node specially.

Since `4.0.0`, ignore will no longer support `node < 6` by default, to use in node < 6, `require('ignore/legacy')`. For details, see [CHANGELOG](https://github.com/kaelzhang/node-ignore/blob/master/CHANGELOG.md).

## Table Of Main Contents

- [Usage](#usage)
- [`Pathname` Conventions](#pathname-conventions)
- See Also:
  - [`glob-gitignore`](https://www.npmjs.com/package/glob-gitignore) matches files using patterns and filters them according to gitignore rules.
- [Upgrade Guide](#upgrade-guide)

## Install

```sh
npm i ignore
```

## Usage

```js
import ignore from 'ignore'
const ig = ignore().add(['.abc/*', '!.abc/d/'])
```

### Filter the given paths

```js
const paths = [
  '.abc/a.js',    // filtered out
  '.abc/d/e.js'   // included
]

ig.filter(paths)        // ['.abc/d/e.js']
ig.ignores('.abc/a.js') // true
```

### As the filter function

```js
paths.filter(ig.createFilter()); // ['.abc/d/e.js']
```

### Win32 paths will be handled

```js
ig.filter(['.abc\\a.js', '.abc\\d\\e.js'])
// if the code above runs on windows, the result will be
// ['.abc\\d\\e.js']
```

## Why another ignore?

- `ignore` is a standalone module, and is much simpler so that it could easy work with other programs, unlike [isaacs](https://npmjs.org/~isaacs)'s [fstream-ignore](https://npmjs.org/package/fstream-ignore) which must work with the modules of the fstream family.

- `ignore` only contains utility methods to filter paths according to the specified ignore rules, so
  - `ignore` never try to find out ignore rules by traversing directories or fetching from git configurations.
  - `ignore` don't cares about sub-modules of git projects.

- Exactly according to [gitignore man page](http://git-scm.com/docs/gitignore), fixes some known matching issues of fstream-ignore, such as:
  - '`/*.js`' should only match '`a.js`', but not '`abc/a.js`'.
  - '`**/foo`' should match '`foo`' anywhere.
  - Prevent re-including a file if a parent directory of that file is excluded.
  - Handle trailing whitespaces:
    - `'a '`(one space) should not match `'a  '`(two spaces).
    - `'a \ '` matches `'a  '`
  - All test cases are verified with the result of `git check-ignore`.

# Methods

## .add(pattern: string | Ignore): this
## .add(patterns: Array<string | Ignore>): this

- **pattern** `String | Ignore` An ignore pattern string, or the `Ignore` instance
- **patterns** `Array<String | Ignore>` Array of ignore patterns.

Adds a rule or several rules to the current manager.

Returns `this`

Notice that a line starting with `'#'`(hash) is treated as a comment. Put a backslash (`'\'`) in front of the first hash for patterns that begin with a hash, if you want to ignore a file with a hash at the beginning of the filename.

```js
ignore().add('#abc').ignores('#abc')    // false
ignore().add('\\#abc').ignores('#abc')   // true
```

`pattern` could either be a line of ignore pattern or a string of multiple ignore patterns, which means we could just `ignore().add()` the content of a ignore file:

```js
ignore()
.add(fs.readFileSync(filenameOfGitignore).toString())
.filter(filenames)
```

`pattern` could also be an `ignore` instance, so that we could easily inherit the rules of another `Ignore` instance.

## .addIgnoreFile(path)

REMOVED in `3.x` for now.

To upgrade `ignore@2.x` up to `3.x`, use

```js
import fs from 'fs'

if (fs.existsSync(filename)) {
  ignore().add(fs.readFileSync(filename).toString())
}
```

instead.

## .filter(paths: Array<Pathname>): Array<Pathname>

```ts
type Pathname = string
```

Filters the given array of pathnames, and returns the filtered array.

- **paths** `Array.<Pathname>` The array of `pathname`s to be filtered.

### `Pathname` Conventions:

#### 1. `Pathname` should be a `path.relative()`d pathname

`Pathname` should be a string that have been `path.join()`ed, or the return value of `path.relative()` to the current directory,

```js
// WRONG, an error will be thrown
ig.ignores('./abc')

// WRONG, for it will never happen, and an error will be thrown
// If the gitignore rule locates at the root directory,
// `'/abc'` should be changed to `'abc'`.
// ```
// path.relative('/', '/abc')  -> 'abc'
// ```
ig.ignores('/abc')

// WRONG, that it is an absolute path on Windows, an error will be thrown
ig.ignores('C:\\abc')

// Right
ig.ignores('abc')

// Right
ig.ignores(path.join('./abc'))  // path.join('./abc') -> 'abc'
```

In other words, each `Pathname` here should be a relative path to the directory of the gitignore rules.

Suppose the dir structure is:

```
/path/to/your/repo
    |-- a
    |   |-- a.js
    |
    |-- .b
    |
    |-- .c
         |-- .DS_store
```

Then the `paths` might be like this:

```js
[
  'a/a.js'
  '.b',
  '.c/.DS_store'
]
```

#### 2. filenames and dirnames

`node-ignore` does NO `fs.stat` during path matching, so for the example below:

```js
// First, we add a ignore pattern to ignore a directory
ig.add('config/')

// `ig` does NOT know if 'config', in the real world,
//   is a normal file, directory or something.

ig.ignores('config')
// `ig` treats `config` as a file, so it returns `false`

ig.ignores('config/')
// returns `true`
```

Specially for people who develop some library based on `node-ignore`, it is important to understand that.

Usually, you could use [`glob`](http://npmjs.org/package/glob) with `option.mark = true` to fetch the structure of the current directory:

```js
import glob from 'glob'

glob('**', {
  // Adds a / character to directory matches.
  mark: true
}, (err, files) => {
  if (err) {
    return console.error(err)
  }

  let filtered = ignore().add(patterns).filter(files)
  console.log(filtered)
})
```

## .ignores(pathname: Pathname): boolean

> new in 3.2.0

Returns `Boolean` whether `pathname` should be ignored.

```js
ig.ignores('.abc/a.js')    // true
```

## .createFilter()

Creates a filter function which could filter an array of paths with `Array.prototype.filter`.

Returns `function(path)` the filter function.

## .test(pathname: Pathname) since 5.0.0

Returns `TestResult`

```ts
interface TestResult {
  ignored: boolean
  // true if the `pathname` is finally unignored by some negative pattern
  unignored: boolean
}
```

- `{ignored: true, unignored: false}`: the `pathname` is ignored
- `{ignored: false, unignored: true}`: the `pathname` is unignored
- `{ignored: false, unignored: false}`: the `pathname` is never matched by any ignore rules.

## static `ignore.isPathValid(pathname): boolean` since 5.0.0

Check whether the `pathname` is an valid `path.relative()`d path according to the [convention](#1-pathname-should-be-a-pathrelatived-pathname).

This method is **NOT** used to check if an ignore pattern is valid.

```js
ignore.isPathValid('./foo')  // false
```

## ignore(options)

### `options.ignorecase` since 4.0.0

Similar as the `core.ignorecase` option of [git-config](https://git-scm.com/docs/git-config), `node-ignore` will be case insensitive if `options.ignorecase` is set to `true` (the default value), otherwise case sensitive.

```js
const ig = ignore({
  ignorecase: false
})

ig.add('*.png')

ig.ignores('*.PNG')  // false
```

### `options.ignoreCase?: boolean` since 5.2.0

Which is alternative to `options.ignoreCase`

### `options.allowRelativePaths?: boolean` since 5.2.0

This option brings backward compatibility with projects which based on `ignore@4.x`. If `options.allowRelativePaths` is `true`, `ignore` will not check whether the given path to be tested is [`path.relative()`d](#pathname-conventions).

However, passing a relative path, such as `'./foo'` or `'../foo'`, to test if it is ignored or not is not a good practise, which might lead to unexpected behavior

```js
ignore({
  allowRelativePaths: true
}).ignores('../foo/bar.js') // And it will not throw
```

****

# Upgrade Guide

## Upgrade 4.x -> 5.x

Since `5.0.0`, if an invalid `Pathname` passed into `ig.ignores()`, an error will be thrown, unless `options.allowRelative = true` is passed to the `Ignore` factory.

While `ignore < 5.0.0` did not make sure what the return value was, as well as

```ts
.ignores(pathname: Pathname): boolean

.filter(pathnames: Array<Pathname>): Array<Pathname>

.createFilter(): (pathname: Pathname) => boolean

.test(pathname: Pathname): {ignored: boolean, unignored: boolean}
```

See the convention [here](#1-pathname-should-be-a-pathrelatived-pathname) for details.

If there are invalid pathnames, the conversion and filtration should be done by users.

```js
import {isPathValid} from 'ignore' // introduced in 5.0.0

const paths = [
  // invalid
  //////////////////
  '',
  false,
  '../foo',
  '.',
  //////////////////

  // valid
  'foo'
]
.filter(isValidPath)

ig.filter(paths)
```

## Upgrade 3.x -> 4.x

Since `4.0.0`, `ignore` will no longer support node < 6, to use `ignore` in node < 6:

```js
var ignore = require('ignore/legacy')
```

## Upgrade 2.x -> 3.x

- All `options` of 2.x are unnecessary and removed, so just remove them.
- `ignore()` instance is no longer an [`EventEmitter`](nodejs.org/api/events.html), and all events are unnecessary and removed.
- `.addIgnoreFile()` is removed, see the [.addIgnoreFile](#addignorefilepath) section for details.

****

# Collaborators

- [@whitecolor](https://github.com/whitecolor) *Alex*
- [@SamyPesse](https://github.com/SamyPesse) *Samy Pessé*
- [@azproduction](https://github.com/azproduction) *Mikhail Davydov*
- [@TrySound](https://github.com/TrySound) *Bogdan Chadkin*
- [@JanMattner](https://github.com/JanMattner) *Jan Mattner*
- [@ntwb](https://github.com/ntwb) *Stephen Edgar*
- [@kasperisager](https://github.com/kasperisager) *Kasper Isager*
- [@sandersn](https://github.com/sandersn) *Nathan Shively-Sanders*


code:
type Pathname = string

interface TestResult {
  ignored: boolean
  unignored: boolean
}

export interface Ignore {
  /**
   * Adds one or several rules to the current manager.
   * @param  {string[]} patterns
   * @returns IgnoreBase
   */
  add(patterns: string | Ignore | readonly (string | Ignore)[]): this

  /**
   * Filters the given array of pathnames, and returns the filtered array.
   * NOTICE that each path here should be a relative path to the root of your repository.
   * @param paths the array of paths to be filtered.
   * @returns The filtered array of paths
   */
  filter(pathnames: readonly Pathname[]): Pathname[]

  /**
   * Creates a filter function which could filter
   * an array of paths with Array.prototype.filter.
   */
  createFilter(): (pathname: Pathname) => boolean

  /**
   * Returns Boolean whether pathname should be ignored.
   * @param  {string} pathname a path to check
   * @returns boolean
   */
  ignores(pathname: Pathname): boolean

  /**
   * Returns whether pathname should be ignored or unignored
   * @param  {string} pathname a path to check
   * @returns TestResult
   */
  test(pathname: Pathname): TestResult
}

export interface Options {
  ignorecase?: boolean
  // For compatibility
  ignoreCase?: boolean
  allowRelativePaths?: boolean
}

/**
 * Creates new ignore manager.
 */
declare function ignore(options?: Options): Ignore

declare namespace ignore {
  export function isPathValid (pathname: string): boolean
}

export default ignore

license: mit


code:
// A simple implementation of make-array
function makeArray (subject) {
  return Array.isArray(subject)
    ? subject
    : [subject]
}

const EMPTY = ''
const SPACE = ' '
const ESCAPE = '\\'
const REGEX_TEST_BLANK_LINE = /^\s+$/
const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/
const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/
const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/
const REGEX_SPLITALL_CRLF = /\r?\n/g
// /foo,
// ./foo,
// ../foo,
// .
// ..
const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/

const SLASH = '/'

// Do not use ternary expression here, since "istanbul ignore next" is buggy
let TMP_KEY_IGNORE = 'node-ignore'
/* istanbul ignore else */
if (typeof Symbol !== 'undefined') {
  TMP_KEY_IGNORE = Symbol.for('node-ignore')
}
const KEY_IGNORE = TMP_KEY_IGNORE

const define = (object, key, value) =>
  Object.defineProperty(object, key, {value})

const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g

const RETURN_FALSE = () => false

// Sanitize the range of a regular expression
// The cases are complicated, see test cases for details
const sanitizeRange = range => range.replace(
  REGEX_REGEXP_RANGE,
  (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)
    ? match
    // Invalid range (out of order) which is ok for gitignore rules but
    //   fatal for JavaScript regular expression, so eliminate it.
    : EMPTY
)

// See fixtures #59
const cleanRangeBackSlash = slashes => {
  const {length} = slashes
  return slashes.slice(0, length - length % 2)
}

// > If the pattern ends with a slash,
// > it is removed for the purpose of the following description,
// > but it would only find a match with a directory.
// > In other words, foo/ will match a directory foo and paths underneath it,
// > but will not match a regular file or a symbolic link foo
// >  (this is consistent with the way how pathspec works in general in Git).
// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'
// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call
//      you could use option `mark: true` with `glob`

// '`foo/`' should not continue with the '`..`'
const REPLACERS = [

  [
    // remove BOM
    // TODO:
    // Other similar zero-width characters?
    /^\uFEFF/,
    () => EMPTY
  ],

  // > Trailing spaces are ignored unless they are quoted with backslash ("\")
  [
    // (a\ ) -> (a )
    // (a  ) -> (a)
    // (a \ ) -> (a  )
    /\\?\s+$/,
    match => match.indexOf('\\') === 0
      ? SPACE
      : EMPTY
  ],

  // replace (\ ) with ' '
  [
    /\\\s/g,
    () => SPACE
  ],

  // Escape metacharacters
  // which is written down by users but means special for regular expressions.

  // > There are 12 characters with special meanings:
  // > - the backslash \,
  // > - the caret ^,
  // > - the dollar sign $,
  // > - the period or dot .,
  // > - the vertical bar or pipe symbol |,
  // > - the question mark ?,
  // > - the asterisk or star *,
  // > - the plus sign +,
  // > - the opening parenthesis (,
  // > - the closing parenthesis ),
  // > - and the opening square bracket [,
  // > - the opening curly brace {,
  // > These special characters are often called "metacharacters".
  [
    /[\\$.|*+(){^]/g,
    match => `\\${match}`
  ],

  [
    // > a question mark (?) matches a single character
    /(?!\\)\?/g,
    () => '[^/]'
  ],

  // leading slash
  [

    // > A leading slash matches the beginning of the pathname.
    // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
    // A leading slash matches the beginning of the pathname
    /^\//,
    () => '^'
  ],

  // replace special metacharacter slash after the leading slash
  [
    /\//g,
    () => '\\/'
  ],

  [
    // > A leading "**" followed by a slash means match in all directories.
    // > For example, "**/foo" matches file or directory "foo" anywhere,
    // > the same as pattern "foo".
    // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
    // >   under directory "foo".
    // Notice that the '*'s have been replaced as '\\*'
    /^\^*\\\*\\\*\\\//,

    // '**/foo' <-> 'foo'
    () => '^(?:.*\\/)?'
  ],

  // starting
  [
    // there will be no leading '/'
    //   (which has been replaced by section "leading slash")
    // If starts with '**', adding a '^' to the regular expression also works
    /^(?=[^^])/,
    function startingReplacer () {
      // If has a slash `/` at the beginning or middle
      return !/\/(?!$)/.test(this)
        // > Prior to 2.22.1
        // > If the pattern does not contain a slash /,
        // >   Git treats it as a shell glob pattern
        // Actually, if there is only a trailing slash,
        //   git also treats it as a shell glob pattern

        // After 2.22.1 (compatible but clearer)
        // > If there is a separator at the beginning or middle (or both)
        // > of the pattern, then the pattern is relative to the directory
        // > level of the particular .gitignore file itself.
        // > Otherwise the pattern may also match at any level below
        // > the .gitignore level.
        ? '(?:^|\\/)'

        // > Otherwise, Git treats the pattern as a shell glob suitable for
        // >   consumption by fnmatch(3)
        : '^'
    }
  ],

  // two globstars
  [
    // Use lookahead assertions so that we could match more than one `'/**'`
    /\\\/\\\*\\\*(?=\\\/|$)/g,

    // Zero, one or several directories
    // should not use '*', or it will be replaced by the next replacer

    // Check if it is not the last `'/**'`
    (_, index, str) => index + 6 < str.length

      // case: /**/
      // > A slash followed by two consecutive asterisks then a slash matches
      // >   zero or more directories.
      // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
      // '/**/'
      ? '(?:\\/[^\\/]+)*'

      // case: /**
      // > A trailing `"/**"` matches everything inside.

      // #21: everything inside but it should not include the current folder
      : '\\/.+'
  ],

  // normal intermediate wildcards
  [
    // Never replace escaped '*'
    // ignore rule '\*' will match the path '*'

    // 'abc.*/' -> go
    // 'abc.*'  -> skip this rule,
    //    coz trailing single wildcard will be handed by [trailing wildcard]
    /(^|[^\\]+)(\\\*)+(?=.+)/g,

    // '*.js' matches '.js'
    // '*.js' doesn't match 'abc'
    (_, p1, p2) => {
      // 1.
      // > An asterisk "*" matches anything except a slash.
      // 2.
      // > Other consecutive asterisks are considered regular asterisks
      // > and will match according to the previous rules.
      const unescaped = p2.replace(/\\\*/g, '[^\\/]*')
      return p1 + unescaped
    }
  ],

  [
    // unescape, revert step 3 except for back slash
    // For example, if a user escape a '\\*',
    // after step 3, the result will be '\\\\\\*'
    /\\\\\\(?=[$.|*+(){^])/g,
    () => ESCAPE
  ],

  [
    // '\\\\' -> '\\'
    /\\\\/g,
    () => ESCAPE
  ],

  [
    // > The range notation, e.g. [a-zA-Z],
    // > can be used to match one of the characters in a range.

    // `\` is escaped by step 3
    /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
    (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE
      // '\\[bar]' -> '\\\\[bar\\]'
      ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`
      : close === ']'
        ? endEscape.length % 2 === 0
          // A normal case, and it is a range notation
          // '[bar]'
          // '[bar\\\\]'
          ? `[${sanitizeRange(range)}${endEscape}]`
          // Invalid range notaton
          // '[bar\\]' -> '[bar\\\\]'
          : '[]'
        : '[]'
  ],

  // ending
  [
    // 'js' will not match 'js.'
    // 'ab' will not match 'abc'
    /(?:[^*])$/,

    // WTF!
    // https://git-scm.com/docs/gitignore
    // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
    // which re-fixes #24, #38

    // > If there is a separator at the end of the pattern then the pattern
    // > will only match directories, otherwise the pattern can match both
    // > files and directories.

    // 'js*' will not match 'a.js'
    // 'js/' will not match 'a.js'
    // 'js' will match 'a.js' and 'a.js/'
    match => /\/$/.test(match)
      // foo/ will not match 'foo'
      ? `${match}$`
      // foo matches 'foo' and 'foo/'
      : `${match}(?=$|\\/$)`
  ],

  // trailing wildcard
  [
    /(\^|\\\/)?\\\*$/,
    (_, p1) => {
      const prefix = p1
        // '\^':
        // '/*' does not match EMPTY
        // '/*' does not match everything

        // '\\\/':
        // 'abc/*' does not match 'abc/'
        ? `${p1}[^/]+`

        // 'a*' matches 'a'
        // 'a*' matches 'aa'
        : '[^/]*'

      return `${prefix}(?=$|\\/$)`
    }
  ],
]

// A simple cache, because an ignore rule only has only one certain meaning
const regexCache = Object.create(null)

// @param {pattern}
const makeRegex = (pattern, ignoreCase) => {
  let source = regexCache[pattern]

  if (!source) {
    source = REPLACERS.reduce(
      (prev, current) => prev.replace(current[0], current[1].bind(pattern)),
      pattern
    )
    regexCache[pattern] = source
  }

  return ignoreCase
    ? new RegExp(source, 'i')
    : new RegExp(source)
}

const isString = subject => typeof subject === 'string'

// > A blank line matches no files, so it can serve as a separator for readability.
const checkPattern = pattern => pattern
  && isString(pattern)
  && !REGEX_TEST_BLANK_LINE.test(pattern)
  && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)

  // > A line starting with # serves as a comment.
  && pattern.indexOf('#') !== 0

const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF)

class IgnoreRule {
  constructor (
    origin,
    pattern,
    negative,
    regex
  ) {
    this.origin = origin
    this.pattern = pattern
    this.negative = negative
    this.regex = regex
  }
}

const createRule = (pattern, ignoreCase) => {
  const origin = pattern
  let negative = false

  // > An optional prefix "!" which negates the pattern;
  if (pattern.indexOf('!') === 0) {
    negative = true
    pattern = pattern.substr(1)
  }

  pattern = pattern
  // > Put a backslash ("\") in front of the first "!" for patterns that
  // >   begin with a literal "!", for example, `"\!important!.txt"`.
  .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')
  // > Put a backslash ("\") in front of the first hash for patterns that
  // >   begin with a hash.
  .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#')

  const regex = makeRegex(pattern, ignoreCase)

  return new IgnoreRule(
    origin,
    pattern,
    negative,
    regex
  )
}

const throwError = (message, Ctor) => {
  throw new Ctor(message)
}

const checkPath = (path, originalPath, doThrow) => {
  if (!isString(path)) {
    return doThrow(
      `path must be a string, but got \`${originalPath}\``,
      TypeError
    )
  }

  // We don't know if we should ignore EMPTY, so throw
  if (!path) {
    return doThrow(`path must not be empty`, TypeError)
  }

  // Check if it is a relative path
  if (checkPath.isNotRelative(path)) {
    const r = '`path.relative()`d'
    return doThrow(
      `path should be a ${r} string, but got "${originalPath}"`,
      RangeError
    )
  }

  return true
}

const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path)

checkPath.isNotRelative = isNotRelative
checkPath.convert = p => p

class Ignore {
  constructor ({
    ignorecase = true,
    ignoreCase = ignorecase,
    allowRelativePaths = false
  } = {}) {
    define(this, KEY_IGNORE, true)

    this._rules = []
    this._ignoreCase = ignoreCase
    this._allowRelativePaths = allowRelativePaths
    this._initCache()
  }

  _initCache () {
    this._ignoreCache = Object.create(null)
    this._testCache = Object.create(null)
  }

  _addPattern (pattern) {
    // #32
    if (pattern && pattern[KEY_IGNORE]) {
      this._rules = this._rules.concat(pattern._rules)
      this._added = true
      return
    }

    if (checkPattern(pattern)) {
      const rule = createRule(pattern, this._ignoreCase)
      this._added = true
      this._rules.push(rule)
    }
  }

  // @param {Array<string> | string | Ignore} pattern
  add (pattern) {
    this._added = false

    makeArray(
      isString(pattern)
        ? splitPattern(pattern)
        : pattern
    ).forEach(this._addPattern, this)

    // Some rules have just added to the ignore,
    // making the behavior changed.
    if (this._added) {
      this._initCache()
    }

    return this
  }

  // legacy
  addPattern (pattern) {
    return this.add(pattern)
  }

  //          |           ignored : unignored
  // negative |   0:0   |   0:1   |   1:0   |   1:1
  // -------- | ------- | ------- | ------- | --------
  //     0    |  TEST   |  TEST   |  SKIP   |    X
  //     1    |  TESTIF |  SKIP   |  TEST   |    X

  // - SKIP: always skip
  // - TEST: always test
  // - TESTIF: only test if checkUnignored
  // - X: that never happen

  // @param {boolean} whether should check if the path is unignored,
  //   setting `checkUnignored` to `false` could reduce additional
  //   path matching.

  // @returns {TestResult} true if a file is ignored
  _testOne (path, checkUnignored) {
    let ignored = false
    let unignored = false

    this._rules.forEach(rule => {
      const {negative} = rule
      if (
        unignored === negative && ignored !== unignored
        || negative && !ignored && !unignored && !checkUnignored
      ) {
        return
      }

      const matched = rule.regex.test(path)

      if (matched) {
        ignored = !negative
        unignored = negative
      }
    })

    return {
      ignored,
      unignored
    }
  }

  // @returns {TestResult}
  _test (originalPath, cache, checkUnignored, slices) {
    const path = originalPath
      // Supports nullable path
      && checkPath.convert(originalPath)

    checkPath(
      path,
      originalPath,
      this._allowRelativePaths
        ? RETURN_FALSE
        : throwError
    )

    return this._t(path, cache, checkUnignored, slices)
  }

  _t (path, cache, checkUnignored, slices) {
    if (path in cache) {
      return cache[path]
    }

    if (!slices) {
      // path/to/a.js
      // ['path', 'to', 'a.js']
      slices = path.split(SLASH)
    }

    slices.pop()

    // If the path has no parent directory, just test it
    if (!slices.length) {
      return cache[path] = this._testOne(path, checkUnignored)
    }

    const parent = this._t(
      slices.join(SLASH) + SLASH,
      cache,
      checkUnignored,
      slices
    )

    // If the path contains a parent directory, check the parent first
    return cache[path] = parent.ignored
      // > It is not possible to re-include a file if a parent directory of
      // >   that file is excluded.
      ? parent
      : this._testOne(path, checkUnignored)
  }

  ignores (path) {
    return this._test(path, this._ignoreCache, false).ignored
  }

  createFilter () {
    return path => !this.ignores(path)
  }

  filter (paths) {
    return makeArray(paths).filter(this.createFilter())
  }

  // @returns {TestResult}
  test (path) {
    return this._test(path, this._testCache, true)
  }
}

const factory = options => new Ignore(options)

const isPathValid = path =>
  checkPath(path && checkPath.convert(path), path, RETURN_FALSE)

factory.isPathValid = isPathValid

// Fixes typescript
factory.default = factory

module.exports = factory

// Windows
// --------------------------------------------------------------
/* istanbul ignore if */
if (
  // Detect `process` so that it can run in browsers.
  typeof process !== 'undefined'
  && (
    process.env && process.env.IGNORE_TEST_WIN32
    || process.platform === 'win32'
  )
) {
  /* eslint no-control-regex: "off" */
  const makePosix = str => /^\\\\\?\\/.test(str)
  || /["<>|\u0000-\u001F]+/u.test(str)
    ? str
    : str.replace(/\\/g, '/')

  checkPath.convert = makePosix

  // 'C:\\foo'     <- 'C:\\foo' has been converted to 'C:/'
  // 'd:\\foo'
  const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i
  checkPath.isNotRelative = path =>
    REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path)
    || isNotRelative(path)
}


code:
"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
// A simple implementation of make-array
function makeArray(subject) {
  return Array.isArray(subject) ? subject : [subject];
}
var EMPTY = '';
var SPACE = ' ';
var ESCAPE = '\\';
var REGEX_TEST_BLANK_LINE = /^\s+$/;
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
var REGEX_SPLITALL_CRLF = /\r?\n/g;
// /foo,
// ./foo,
// ../foo,
// .
// ..
var REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/;
var SLASH = '/';

// Do not use ternary expression here, since "istanbul ignore next" is buggy
var TMP_KEY_IGNORE = 'node-ignore';
/* istanbul ignore else */
if (typeof Symbol !== 'undefined') {
  TMP_KEY_IGNORE = Symbol["for"]('node-ignore');
}
var KEY_IGNORE = TMP_KEY_IGNORE;
var define = function define(object, key, value) {
  return Object.defineProperty(object, key, {
    value: value
  });
};
var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
var RETURN_FALSE = function RETURN_FALSE() {
  return false;
};

// Sanitize the range of a regular expression
// The cases are complicated, see test cases for details
var sanitizeRange = function sanitizeRange(range) {
  return range.replace(REGEX_REGEXP_RANGE, function (match, from, to) {
    return from.charCodeAt(0) <= to.charCodeAt(0) ? match
    // Invalid range (out of order) which is ok for gitignore rules but
    //   fatal for JavaScript regular expression, so eliminate it.
    : EMPTY;
  });
};

// See fixtures #59
var cleanRangeBackSlash = function cleanRangeBackSlash(slashes) {
  var length = slashes.length;
  return slashes.slice(0, length - length % 2);
};

// > If the pattern ends with a slash,
// > it is removed for the purpose of the following description,
// > but it would only find a match with a directory.
// > In other words, foo/ will match a directory foo and paths underneath it,
// > but will not match a regular file or a symbolic link foo
// >  (this is consistent with the way how pathspec works in general in Git).
// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'
// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call
//      you could use option `mark: true` with `glob`

// '`foo/`' should not continue with the '`..`'
var REPLACERS = [[
// remove BOM
// TODO:
// Other similar zero-width characters?
/^\uFEFF/, function () {
  return EMPTY;
}],
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
[
// (a\ ) -> (a )
// (a  ) -> (a)
// (a \ ) -> (a  )
/\\?\s+$/, function (match) {
  return match.indexOf('\\') === 0 ? SPACE : EMPTY;
}],
// replace (\ ) with ' '
[/\\\s/g, function () {
  return SPACE;
}],
// Escape metacharacters
// which is written down by users but means special for regular expressions.

// > There are 12 characters with special meanings:
// > - the backslash \,
// > - the caret ^,
// > - the dollar sign $,
// > - the period or dot .,
// > - the vertical bar or pipe symbol |,
// > - the question mark ?,
// > - the asterisk or star *,
// > - the plus sign +,
// > - the opening parenthesis (,
// > - the closing parenthesis ),
// > - and the opening square bracket [,
// > - the opening curly brace {,
// > These special characters are often called "metacharacters".
[/[\\$.|*+(){^]/g, function (match) {
  return "\\".concat(match);
}], [
// > a question mark (?) matches a single character
/(?!\\)\?/g, function () {
  return '[^/]';
}],
// leading slash
[
// > A leading slash matches the beginning of the pathname.
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
// A leading slash matches the beginning of the pathname
/^\//, function () {
  return '^';
}],
// replace special metacharacter slash after the leading slash
[/\//g, function () {
  return '\\/';
}], [
// > A leading "**" followed by a slash means match in all directories.
// > For example, "**/foo" matches file or directory "foo" anywhere,
// > the same as pattern "foo".
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
// >   under directory "foo".
// Notice that the '*'s have been replaced as '\\*'
/^\^*\\\*\\\*\\\//,
// '**/foo' <-> 'foo'
function () {
  return '^(?:.*\\/)?';
}],
// starting
[
// there will be no leading '/'
//   (which has been replaced by section "leading slash")
// If starts with '**', adding a '^' to the regular expression also works
/^(?=[^^])/, function startingReplacer() {
  // If has a slash `/` at the beginning or middle
  return !/\/(?!$)/.test(this)
  // > Prior to 2.22.1
  // > If the pattern does not contain a slash /,
  // >   Git treats it as a shell glob pattern
  // Actually, if there is only a trailing slash,
  //   git also treats it as a shell glob pattern

  // After 2.22.1 (compatible but clearer)
  // > If there is a separator at the beginning or middle (or both)
  // > of the pattern, then the pattern is relative to the directory
  // > level of the particular .gitignore file itself.
  // > Otherwise the pattern may also match at any level below
  // > the .gitignore level.
  ? '(?:^|\\/)'

  // > Otherwise, Git treats the pattern as a shell glob suitable for
  // >   consumption by fnmatch(3)
  : '^';
}],
// two globstars
[
// Use lookahead assertions so that we could match more than one `'/**'`
/\\\/\\\*\\\*(?=\\\/|$)/g,
// Zero, one or several directories
// should not use '*', or it will be replaced by the next replacer

// Check if it is not the last `'/**'`
function (_, index, str) {
  return index + 6 < str.length

  // case: /**/
  // > A slash followed by two consecutive asterisks then a slash matches
  // >   zero or more directories.
  // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
  // '/**/'
  ? '(?:\\/[^\\/]+)*'

  // case: /**
  // > A trailing `"/**"` matches everything inside.

  // #21: everything inside but it should not include the current folder
  : '\\/.+';
}],
// normal intermediate wildcards
[
// Never replace escaped '*'
// ignore rule '\*' will match the path '*'

// 'abc.*/' -> go
// 'abc.*'  -> skip this rule,
//    coz trailing single wildcard will be handed by [trailing wildcard]
/(^|[^\\]+)(\\\*)+(?=.+)/g,
// '*.js' matches '.js'
// '*.js' doesn't match 'abc'
function (_, p1, p2) {
  // 1.
  // > An asterisk "*" matches anything except a slash.
  // 2.
  // > Other consecutive asterisks are considered regular asterisks
  // > and will match according to the previous rules.
  var unescaped = p2.replace(/\\\*/g, '[^\\/]*');
  return p1 + unescaped;
}], [
// unescape, revert step 3 except for back slash
// For example, if a user escape a '\\*',
// after step 3, the result will be '\\\\\\*'
/\\\\\\(?=[$.|*+(){^])/g, function () {
  return ESCAPE;
}], [
// '\\\\' -> '\\'
/\\\\/g, function () {
  return ESCAPE;
}], [
// > The range notation, e.g. [a-zA-Z],
// > can be used to match one of the characters in a range.

// `\` is escaped by step 3
/(\\)?\[([^\]/]*?)(\\*)($|\])/g, function (match, leadEscape, range, endEscape, close) {
  return leadEscape === ESCAPE
  // '\\[bar]' -> '\\\\[bar\\]'
  ? "\\[".concat(range).concat(cleanRangeBackSlash(endEscape)).concat(close) : close === ']' ? endEscape.length % 2 === 0
  // A normal case, and it is a range notation
  // '[bar]'
  // '[bar\\\\]'
  ? "[".concat(sanitizeRange(range)).concat(endEscape, "]") // Invalid range notaton
  // '[bar\\]' -> '[bar\\\\]'
  : '[]' : '[]';
}],
// ending
[
// 'js' will not match 'js.'
// 'ab' will not match 'abc'
/(?:[^*])$/,
// WTF!
// https://git-scm.com/docs/gitignore
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
// which re-fixes #24, #38

// > If there is a separator at the end of the pattern then the pattern
// > will only match directories, otherwise the pattern can match both
// > files and directories.

// 'js*' will not match 'a.js'
// 'js/' will not match 'a.js'
// 'js' will match 'a.js' and 'a.js/'
function (match) {
  return /\/$/.test(match)
  // foo/ will not match 'foo'
  ? "".concat(match, "$") // foo matches 'foo' and 'foo/'
  : "".concat(match, "(?=$|\\/$)");
}],
// trailing wildcard
[/(\^|\\\/)?\\\*$/, function (_, p1) {
  var prefix = p1
  // '\^':
  // '/*' does not match EMPTY
  // '/*' does not match everything

  // '\\\/':
  // 'abc/*' does not match 'abc/'
  ? "".concat(p1, "[^/]+") // 'a*' matches 'a'
  // 'a*' matches 'aa'
  : '[^/]*';
  return "".concat(prefix, "(?=$|\\/$)");
}]];

// A simple cache, because an ignore rule only has only one certain meaning
var regexCache = Object.create(null);

// @param {pattern}
var makeRegex = function makeRegex(pattern, ignoreCase) {
  var source = regexCache[pattern];
  if (!source) {
    source = REPLACERS.reduce(function (prev, current) {
      return prev.replace(current[0], current[1].bind(pattern));
    }, pattern);
    regexCache[pattern] = source;
  }
  return ignoreCase ? new RegExp(source, 'i') : new RegExp(source);
};
var isString = function isString(subject) {
  return typeof subject === 'string';
};

// > A blank line matches no files, so it can serve as a separator for readability.
var checkPattern = function checkPattern(pattern) {
  return pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)

  // > A line starting with # serves as a comment.
  && pattern.indexOf('#') !== 0;
};
var splitPattern = function splitPattern(pattern) {
  return pattern.split(REGEX_SPLITALL_CRLF);
};
var IgnoreRule = /*#__PURE__*/_createClass(function IgnoreRule(origin, pattern, negative, regex) {
  _classCallCheck(this, IgnoreRule);
  this.origin = origin;
  this.pattern = pattern;
  this.negative = negative;
  this.regex = regex;
});
var createRule = function createRule(pattern, ignoreCase) {
  var origin = pattern;
  var negative = false;

  // > An optional prefix "!" which negates the pattern;
  if (pattern.indexOf('!') === 0) {
    negative = true;
    pattern = pattern.substr(1);
  }
  pattern = pattern
  // > Put a backslash ("\") in front of the first "!" for patterns that
  // >   begin with a literal "!", for example, `"\!important!.txt"`.
  .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')
  // > Put a backslash ("\") in front of the first hash for patterns that
  // >   begin with a hash.
  .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#');
  var regex = makeRegex(pattern, ignoreCase);
  return new IgnoreRule(origin, pattern, negative, regex);
};
var throwError = function throwError(message, Ctor) {
  throw new Ctor(message);
};
var checkPath = function checkPath(path, originalPath, doThrow) {
  if (!isString(path)) {
    return doThrow("path must be a string, but got `".concat(originalPath, "`"), TypeError);
  }

  // We don't know if we should ignore EMPTY, so throw
  if (!path) {
    return doThrow("path must not be empty", TypeError);
  }

  // Check if it is a relative path
  if (checkPath.isNotRelative(path)) {
    var r = '`path.relative()`d';
    return doThrow("path should be a ".concat(r, " string, but got \"").concat(originalPath, "\""), RangeError);
  }
  return true;
};
var isNotRelative = function isNotRelative(path) {
  return REGEX_TEST_INVALID_PATH.test(path);
};
checkPath.isNotRelative = isNotRelative;
checkPath.convert = function (p) {
  return p;
};
var Ignore = /*#__PURE__*/function () {
  function Ignore() {
    var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
      _ref$ignorecase = _ref.ignorecase,
      ignorecase = _ref$ignorecase === void 0 ? true : _ref$ignorecase,
      _ref$ignoreCase = _ref.ignoreCase,
      ignoreCase = _ref$ignoreCase === void 0 ? ignorecase : _ref$ignoreCase,
      _ref$allowRelativePat = _ref.allowRelativePaths,
      allowRelativePaths = _ref$allowRelativePat === void 0 ? false : _ref$allowRelativePat;
    _classCallCheck(this, Ignore);
    define(this, KEY_IGNORE, true);
    this._rules = [];
    this._ignoreCase = ignoreCase;
    this._allowRelativePaths = allowRelativePaths;
    this._initCache();
  }
  _createClass(Ignore, [{
    key: "_initCache",
    value: function _initCache() {
      this._ignoreCache = Object.create(null);
      this._testCache = Object.create(null);
    }
  }, {
    key: "_addPattern",
    value: function _addPattern(pattern) {
      // #32
      if (pattern && pattern[KEY_IGNORE]) {
        this._rules = this._rules.concat(pattern._rules);
        this._added = true;
        return;
      }
      if (checkPattern(pattern)) {
        var rule = createRule(pattern, this._ignoreCase);
        this._added = true;
        this._rules.push(rule);
      }
    }

    // @param {Array<string> | string | Ignore} pattern
  }, {
    key: "add",
    value: function add(pattern) {
      this._added = false;
      makeArray(isString(pattern) ? splitPattern(pattern) : pattern).forEach(this._addPattern, this);

      // Some rules have just added to the ignore,
      // making the behavior changed.
      if (this._added) {
        this._initCache();
      }
      return this;
    }

    // legacy
  }, {
    key: "addPattern",
    value: function addPattern(pattern) {
      return this.add(pattern);
    }

    //          |           ignored : unignored
    // negative |   0:0   |   0:1   |   1:0   |   1:1
    // -------- | ------- | ------- | ------- | --------
    //     0    |  TEST   |  TEST   |  SKIP   |    X
    //     1    |  TESTIF |  SKIP   |  TEST   |    X

    // - SKIP: always skip
    // - TEST: always test
    // - TESTIF: only test if checkUnignored
    // - X: that never happen

    // @param {boolean} whether should check if the path is unignored,
    //   setting `checkUnignored` to `false` could reduce additional
    //   path matching.

    // @returns {TestResult} true if a file is ignored
  }, {
    key: "_testOne",
    value: function _testOne(path, checkUnignored) {
      var ignored = false;
      var unignored = false;
      this._rules.forEach(function (rule) {
        var negative = rule.negative;
        if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
          return;
        }
        var matched = rule.regex.test(path);
        if (matched) {
          ignored = !negative;
          unignored = negative;
        }
      });
      return {
        ignored: ignored,
        unignored: unignored
      };
    }

    // @returns {TestResult}
  }, {
    key: "_test",
    value: function _test(originalPath, cache, checkUnignored, slices) {
      var path = originalPath
      // Supports nullable path
      && checkPath.convert(originalPath);
      checkPath(path, originalPath, this._allowRelativePaths ? RETURN_FALSE : throwError);
      return this._t(path, cache, checkUnignored, slices);
    }
  }, {
    key: "_t",
    value: function _t(path, cache, checkUnignored, slices) {
      if (path in cache) {
        return cache[path];
      }
      if (!slices) {
        // path/to/a.js
        // ['path', 'to', 'a.js']
        slices = path.split(SLASH);
      }
      slices.pop();

      // If the path has no parent directory, just test it
      if (!slices.length) {
        return cache[path] = this._testOne(path, checkUnignored);
      }
      var parent = this._t(slices.join(SLASH) + SLASH, cache, checkUnignored, slices);

      // If the path contains a parent directory, check the parent first
      return cache[path] = parent.ignored
      // > It is not possible to re-include a file if a parent directory of
      // >   that file is excluded.
      ? parent : this._testOne(path, checkUnignored);
    }
  }, {
    key: "ignores",
    value: function ignores(path) {
      return this._test(path, this._ignoreCache, false).ignored;
    }
  }, {
    key: "createFilter",
    value: function createFilter() {
      var _this = this;
      return function (path) {
        return !_this.ignores(path);
      };
    }
  }, {
    key: "filter",
    value: function filter(paths) {
      return makeArray(paths).filter(this.createFilter());
    }

    // @returns {TestResult}
  }, {
    key: "test",
    value: function test(path) {
      return this._test(path, this._testCache, true);
    }
  }]);
  return Ignore;
}();
var factory = function factory(options) {
  return new Ignore(options);
};
var isPathValid = function isPathValid(path) {
  return checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
};
factory.isPathValid = isPathValid;

// Fixes typescript
factory["default"] = factory;
module.exports = factory;

// Windows
// --------------------------------------------------------------
/* istanbul ignore if */
if (
// Detect `process` so that it can run in browsers.
typeof process !== 'undefined' && (process.env && process.env.IGNORE_TEST_WIN32 || process.platform === 'win32')) {
  /* eslint no-control-regex: "off" */
  var makePosix = function makePosix(str) {
    return /^\\\\\?\\/.test(str) || /[\0-\x1F"<>\|]+/.test(str) ? str : str.replace(/\\/g, '/');
  };
  checkPath.convert = makePosix;

  // 'C:\\foo'     <- 'C:\\foo' has been converted to 'C:/'
  // 'd:\\foo'
  var REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
  checkPath.isNotRelative = function (path) {
    return REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
  };
}


ignore

description: Ignore is a manager and filter for .gitignore rules, the one used by eslint, gitbook and many others.

prepublishOnly: npm run build

build: babel -o legacy.js index.js

test:lint: eslint .

test:tsc: tsc ./test/ts/simple.ts --lib ES6

test:ts: node ./test/ts/simple.js

tap: tap --reporter classic

test:git: npm run tap test/git-check-ignore.js

test:ignore: npm run tap test/ignore.js

test:others: npm run tap test/others.js

test:cases: npm run tap test/*.js -- --coverage

test:no-coverage: npm run tap test/*.js -- --no-check-coverage

test:only: npm run test:lint && npm run test:tsc && npm run test:ts && npm run test:cases

test: npm run test:only

test:win32: IGNORE_TEST_WIN32=1 npm run test

report: tap --coverage-report=html

posttest: npm run report && codecov

type: git

keywords: ignore .gitignore gitignore npmignore rules manager filter regexp regex fnmatch glob asterisks regular-expression

author: kael

license: MIT


code:
# image-size

[![Build Status](https://circleci.com/gh/image-size/image-size.svg?style=shield)](https://circleci.com/gh/image-size/image-size)
[![Package Version](https://img.shields.io/npm/v/image-size.svg)](https://www.npmjs.com/package/image-size)
[![Downloads](https://img.shields.io/npm/dm/image-size.svg)](http://npm-stat.com/charts.html?package=image-size&author=&from=&to=)

A [Node](https://nodejs.org/en/) module to get dimensions of any image file

## Supported formats

* BMP
* CUR
* DDS
* GIF
* ICNS
* ICO
* J2C
* JP2
* JPEG
* KTX
* PNG
* PNM (PAM, PBM, PFM, PGM, PPM)
* PSD
* SVG
* TGA
* TIFF
* WebP

## Programmatic Usage

```shell
npm install image-size --save
```

or

```shell
yarn add image-size
```

### Synchronous

```javascript
const sizeOf = require('image-size')
const dimensions = sizeOf('images/funny-cats.png')
console.log(dimensions.width, dimensions.height)
```

### Asynchronous

```javascript
const sizeOf = require('image-size')
sizeOf('images/funny-cats.png', function (err, dimensions) {
  console.log(dimensions.width, dimensions.height)
})
```

NOTE: The asynchronous version doesn't work if the input is a Buffer. Use synchronous version instead.

Also, the asynchronous functions have a default concurrency limit of **100**
To change this limit, you can call the `setConcurrency` function like this:

```javascript
const sizeOf = require('image-size')
sizeOf.setConcurrency(123456)
```

### Using promises (nodejs 10.x+)

```javascript
const { promisify } = require('util')
const sizeOf = promisify(require('image-size'))
sizeOf('images/funny-cats.png')
  .then(dimensions => { console.log(dimensions.width, dimensions.height) })
  .catch(err => console.error(err))
```

### Async/Await (Typescript & ES7)

```javascript
const { promisify } = require('util')
const sizeOf = promisify(require('image-size'))
(async () => {
  try {
    const dimensions = await sizeOf('images/funny-cats.png')
    console.log(dimensions.width, dimensions.height)
  } catch (err) {
    console.error(err)
  }
})().then(c => console.log(c))
```

### Multi-size

If the target file is an icon (.ico) or a cursor (.cur), the `width` and `height` will be the ones of the first found image.

An additional `images` array is available and returns the dimensions of all the available images

```javascript
const sizeOf = require('image-size')
const images = sizeOf('images/multi-size.ico').images
for (const dimensions of images) {
  console.log(dimensions.width, dimensions.height)
}
```

### Using a URL

```javascript
const url = require('url')
const http = require('http')

const sizeOf = require('image-size')

const imgUrl = 'http://my-amazing-website.com/image.jpeg'
const options = url.parse(imgUrl)

http.get(options, function (response) {
  const chunks = []
  response.on('data', function (chunk) {
    chunks.push(chunk)
  }).on('end', function() {
    const buffer = Buffer.concat(chunks)
    console.log(sizeOf(buffer))
  })
})
```

You can optionally check the buffer lengths & stop downloading the image after a few kilobytes.
**You don't need to download the entire image**

### Disabling certain image types
```javascript
const imageSize = require('image-size')
imageSize.disableTypes(['tiff', 'ico'])
```

### Disabling all file-system reads
```javascript
const imageSize = require('image-size')
imageSize.disableFS(true)
```

### JPEG image orientation

If the orientation is present in the JPEG EXIF metadata, it will be returned by the function. The orientation value is a [number between 1 and 8](https://exiftool.org/TagNames/EXIF.html#:~:text=0x0112,8%20=%20Rotate%20270%20CW) representing a type of orientation.

```javascript
const sizeOf = require('image-size')
const dimensions = sizeOf('images/photo.jpeg')
console.log(dimensions.orientation)
```

## Command-Line Usage (CLI)

```shell
npm install image-size --global
```

or

```shell
yarn global add image-size
```

followed by

```shell
image-size image1 [image2] [image3] ...
```

## Hosted API

 We also provide a hosted API for image-size which may simplify your use case.

## Credits

not a direct port, but an attempt to have something like
[dabble's imagesize](https://github.com/dabble/imagesize/blob/master/lib/image_size.rb) as a node module.

## [Contributors](Contributors.md)


code:
#!/usr/bin/env node
'use strict'

const fs = require('fs')
const path = require('path')
const { imageSize } = require('..')

const files = process.argv.slice(2)

if (!files.length) {
  console.error('Usage: image-size image1 [image2] [image3] ...')
  process.exit(-1)
}

const red = ['\x1B[31m', '\x1B[39m']
// const bold = ['\x1B[1m',  '\x1B[22m']
const grey = ['\x1B[90m', '\x1B[39m']
const green = ['\x1B[32m', '\x1B[39m']

function colorize(text, color) {
  return color[0] + text + color[1]
}

files.forEach(function (image) {
  try {
    if (fs.existsSync(path.resolve(image))) {
      const greyX = colorize('x', grey)
      const greyImage = colorize(image, grey)
      const size = imageSize(image)
      const sizes = size.images || [size]
      sizes.forEach(size => {
        let greyType = ''
        if (size.type) {
          greyType = colorize(' (' + size.type + ')', grey)
        }
        console.info(
          colorize(size.width, green) + greyX + colorize(size.height, green)
            + ' - ' + greyImage + greyType
        )
      })
    } else {
      console.error('file doesn\'t exist - ', image)
    }
  } catch (e) {
    // console.error(e.stack)
    console.error(colorize(e.message, red), '-', image)
  }
})


code:
/// <reference types="node" />
import { imageType } from './types';
export declare function detector(buffer: Buffer): imageType | undefined;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.detector = void 0;
const types_1 = require("./types");
const keys = Object.keys(types_1.typeHandlers);
// This map helps avoid validating for every single image type
const firstBytes = {
    0x38: 'psd',
    0x42: 'bmp',
    0x44: 'dds',
    0x47: 'gif',
    0x49: 'tiff',
    0x4d: 'tiff',
    0x52: 'webp',
    0x69: 'icns',
    0x89: 'png',
    0xff: 'jpg'
};
function detector(buffer) {
    const byte = buffer[0];
    if (byte in firstBytes) {
        const type = firstBytes[byte];
        if (type && types_1.typeHandlers[type].validate(buffer)) {
            return type;
        }
    }
    const finder = (key) => types_1.typeHandlers[key].validate(buffer);
    return keys.find(finder);
}
exports.detector = detector;


code:
/// <reference types="node" />
import { imageType } from './types';
import { ISizeCalculationResult } from './types/interface';
declare type CallbackFn = (e: Error | null, r?: ISizeCalculationResult) => void;
export default imageSize;
export declare function imageSize(input: Buffer | string): ISizeCalculationResult;
export declare function imageSize(input: string, callback: CallbackFn): void;
export declare const disableFS: (v: boolean) => void;
export declare const disableTypes: (types: imageType[]) => void;
export declare const setConcurrency: (c: number) => void;
export declare const types: string[];


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.types = exports.setConcurrency = exports.disableTypes = exports.disableFS = exports.imageSize = void 0;
const fs = require("fs");
const path = require("path");
const queue_1 = require("queue");
const types_1 = require("./types");
const detector_1 = require("./detector");
// Maximum buffer size, with a default of 512 kilobytes.
// TO-DO: make this adaptive based on the initial signature of the image
const MaxBufferSize = 512 * 1024;
// This queue is for async `fs` operations, to avoid reaching file-descriptor limits
const queue = new queue_1.default({ concurrency: 100, autostart: true });
const globalOptions = {
    disabledFS: false,
    disabledTypes: []
};
/**
 * Return size information based on a buffer
 *
 * @param {Buffer} buffer
 * @param {String} filepath
 * @returns {Object}
 */
function lookup(buffer, filepath) {
    // detect the file type.. don't rely on the extension
    const type = (0, detector_1.detector)(buffer);
    if (typeof type !== 'undefined') {
        if (globalOptions.disabledTypes.indexOf(type) > -1) {
            throw new TypeError('disabled file type: ' + type);
        }
        // find an appropriate handler for this file type
        if (type in types_1.typeHandlers) {
            const size = types_1.typeHandlers[type].calculate(buffer, filepath);
            if (size !== undefined) {
                size.type = type;
                return size;
            }
        }
    }
    // throw up, if we don't understand the file
    throw new TypeError('unsupported file type: ' + type + ' (file: ' + filepath + ')');
}
/**
 * Reads a file into a buffer.
 * @param {String} filepath
 * @returns {Promise<Buffer>}
 */
async function asyncFileToBuffer(filepath) {
    const handle = await fs.promises.open(filepath, 'r');
    try {
        const { size } = await handle.stat();
        if (size <= 0) {
            throw new Error('Empty file');
        }
        const bufferSize = Math.min(size, MaxBufferSize);
        const buffer = Buffer.alloc(bufferSize);
        await handle.read(buffer, 0, bufferSize, 0);
        return buffer;
    }
    finally {
        await handle.close();
    }
}
/**
 * Synchronously reads a file into a buffer, blocking the nodejs process.
 *
 * @param {String} filepath
 * @returns {Buffer}
 */
function syncFileToBuffer(filepath) {
    // read from the file, synchronously
    const descriptor = fs.openSync(filepath, 'r');
    try {
        const { size } = fs.fstatSync(descriptor);
        if (size <= 0) {
            throw new Error('Empty file');
        }
        const bufferSize = Math.min(size, MaxBufferSize);
        const buffer = Buffer.alloc(bufferSize);
        fs.readSync(descriptor, buffer, 0, bufferSize, 0);
        return buffer;
    }
    finally {
        fs.closeSync(descriptor);
    }
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
module.exports = exports = imageSize; // backwards compatibility
exports.default = imageSize;
/**
 * @param {Buffer|string} input - buffer or relative/absolute path of the image file
 * @param {Function=} [callback] - optional function for async detection
 */
function imageSize(input, callback) {
    // Handle buffer input
    if (Buffer.isBuffer(input)) {
        return lookup(input);
    }
    // input should be a string at this point
    if (typeof input !== 'string' || globalOptions.disabledFS) {
        throw new TypeError('invalid invocation. input should be a Buffer');
    }
    // resolve the file path
    const filepath = path.resolve(input);
    if (typeof callback === 'function') {
        queue.push(() => asyncFileToBuffer(filepath)
            .then((buffer) => process.nextTick(callback, null, lookup(buffer, filepath)))
            .catch(callback));
    }
    else {
        const buffer = syncFileToBuffer(filepath);
        return lookup(buffer, filepath);
    }
}
exports.imageSize = imageSize;
const disableFS = (v) => { globalOptions.disabledFS = v; };
exports.disableFS = disableFS;
const disableTypes = (types) => { globalOptions.disabledTypes = types; };
exports.disableTypes = disableTypes;
const setConcurrency = (c) => { queue.concurrency = c; };
exports.setConcurrency = setConcurrency;
exports.types = Object.keys(types_1.typeHandlers);


code:
/// <reference types="node" />
declare type Bits = 16 | 32;
export declare function readUInt(buffer: Buffer, bits: Bits, offset: number, isBigEndian: boolean): number;
export {};


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.readUInt = void 0;
// Abstract reading multi-byte unsigned integers
function readUInt(buffer, bits, offset, isBigEndian) {
    offset = offset || 0;
    const endian = isBigEndian ? 'BE' : 'LE';
    const methodName = ('readUInt' + bits + endian);
    return buffer[methodName].call(buffer, offset);
}
exports.readUInt = readUInt;


code:
export declare const typeHandlers: {
    bmp: import("./types/interface").IImage;
    cur: import("./types/interface").IImage;
    dds: import("./types/interface").IImage;
    gif: import("./types/interface").IImage;
    icns: import("./types/interface").IImage;
    ico: import("./types/interface").IImage;
    j2c: import("./types/interface").IImage;
    jp2: import("./types/interface").IImage;
    jpg: import("./types/interface").IImage;
    ktx: import("./types/interface").IImage;
    png: import("./types/interface").IImage;
    pnm: import("./types/interface").IImage;
    psd: import("./types/interface").IImage;
    svg: import("./types/interface").IImage;
    tga: import("./types/interface").IImage;
    tiff: import("./types/interface").IImage;
    webp: import("./types/interface").IImage;
};
export declare type imageType = keyof typeof typeHandlers;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.typeHandlers = void 0;
// load all available handlers explicitely for browserify support
const bmp_1 = require("./types/bmp");
const cur_1 = require("./types/cur");
const dds_1 = require("./types/dds");
const gif_1 = require("./types/gif");
const icns_1 = require("./types/icns");
const ico_1 = require("./types/ico");
const j2c_1 = require("./types/j2c");
const jp2_1 = require("./types/jp2");
const jpg_1 = require("./types/jpg");
const ktx_1 = require("./types/ktx");
const png_1 = require("./types/png");
const pnm_1 = require("./types/pnm");
const psd_1 = require("./types/psd");
const svg_1 = require("./types/svg");
const tga_1 = require("./types/tga");
const tiff_1 = require("./types/tiff");
const webp_1 = require("./types/webp");
exports.typeHandlers = {
    bmp: bmp_1.BMP,
    cur: cur_1.CUR,
    dds: dds_1.DDS,
    gif: gif_1.GIF,
    icns: icns_1.ICNS,
    ico: ico_1.ICO,
    j2c: j2c_1.J2C,
    jp2: jp2_1.JP2,
    jpg: jpg_1.JPG,
    ktx: ktx_1.KTX,
    png: png_1.PNG,
    pnm: pnm_1.PNM,
    psd: psd_1.PSD,
    svg: svg_1.SVG,
    tga: tga_1.TGA,
    tiff: tiff_1.TIFF,
    webp: webp_1.WEBP,
};


code:
import { IImage } from './interface';
export declare const BMP: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BMP = void 0;
exports.BMP = {
    validate(buffer) {
        return ('BM' === buffer.toString('ascii', 0, 2));
    },
    calculate(buffer) {
        return {
            height: Math.abs(buffer.readInt32LE(22)),
            width: buffer.readUInt32LE(18)
        };
    }
};


code:
import { IImage } from './interface';
export declare const CUR: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CUR = void 0;
const ico_1 = require("./ico");
const TYPE_CURSOR = 2;
exports.CUR = {
    validate(buffer) {
        const reserved = buffer.readUInt16LE(0);
        const imageCount = buffer.readUInt16LE(4);
        if (reserved !== 0 || imageCount === 0) {
            return false;
        }
        const imageType = buffer.readUInt16LE(2);
        return imageType === TYPE_CURSOR;
    },
    calculate(buffer) {
        return ico_1.ICO.calculate(buffer);
    }
};


code:
import { IImage } from './interface';
export declare const DDS: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DDS = void 0;
exports.DDS = {
    validate(buffer) {
        return buffer.readUInt32LE(0) === 0x20534444;
    },
    calculate(buffer) {
        return {
            height: buffer.readUInt32LE(12),
            width: buffer.readUInt32LE(16)
        };
    }
};


code:
import { IImage } from './interface';
export declare const GIF: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GIF = void 0;
const gifRegexp = /^GIF8[79]a/;
exports.GIF = {
    validate(buffer) {
        const signature = buffer.toString('ascii', 0, 6);
        return (gifRegexp.test(signature));
    },
    calculate(buffer) {
        return {
            height: buffer.readUInt16LE(8),
            width: buffer.readUInt16LE(6)
        };
    }
};


code:
import { IImage } from './interface';
export declare const ICNS: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ICNS = void 0;
/**
 * ICNS Header
 *
 * | Offset | Size | Purpose                                                |
 * | 0	    | 4    | Magic literal, must be "icns" (0x69, 0x63, 0x6e, 0x73) |
 * | 4      | 4    | Length of file, in bytes, msb first.                   |
 *
 */
const SIZE_HEADER = 4 + 4; // 8
const FILE_LENGTH_OFFSET = 4; // MSB => BIG ENDIAN
/**
 * Image Entry
 *
 * | Offset | Size | Purpose                                                          |
 * | 0	    | 4    | Icon type, see OSType below.                                     |
 * | 4      | 4    | Length of data, in bytes (including type and length), msb first. |
 * | 8      | n    | Icon data                                                        |
 */
const ENTRY_LENGTH_OFFSET = 4; // MSB => BIG ENDIAN
const ICON_TYPE_SIZE = {
    ICON: 32,
    'ICN#': 32,
    // m => 16 x 16
    'icm#': 16,
    icm4: 16,
    icm8: 16,
    // s => 16 x 16
    'ics#': 16,
    ics4: 16,
    ics8: 16,
    is32: 16,
    s8mk: 16,
    icp4: 16,
    // l => 32 x 32
    icl4: 32,
    icl8: 32,
    il32: 32,
    l8mk: 32,
    icp5: 32,
    ic11: 32,
    // h => 48 x 48
    ich4: 48,
    ich8: 48,
    ih32: 48,
    h8mk: 48,
    // . => 64 x 64
    icp6: 64,
    ic12: 32,
    // t => 128 x 128
    it32: 128,
    t8mk: 128,
    ic07: 128,
    // . => 256 x 256
    ic08: 256,
    ic13: 256,
    // . => 512 x 512
    ic09: 512,
    ic14: 512,
    // . => 1024 x 1024
    ic10: 1024,
};
function readImageHeader(buffer, imageOffset) {
    const imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET;
    return [
        buffer.toString('ascii', imageOffset, imageLengthOffset),
        buffer.readUInt32BE(imageLengthOffset)
    ];
}
function getImageSize(type) {
    const size = ICON_TYPE_SIZE[type];
    return { width: size, height: size, type };
}
exports.ICNS = {
    validate(buffer) {
        return ('icns' === buffer.toString('ascii', 0, 4));
    },
    calculate(buffer) {
        const bufferLength = buffer.length;
        const fileLength = buffer.readUInt32BE(FILE_LENGTH_OFFSET);
        let imageOffset = SIZE_HEADER;
        let imageHeader = readImageHeader(buffer, imageOffset);
        let imageSize = getImageSize(imageHeader[0]);
        imageOffset += imageHeader[1];
        if (imageOffset === fileLength) {
            return imageSize;
        }
        const result = {
            height: imageSize.height,
            images: [imageSize],
            width: imageSize.width
        };
        while (imageOffset < fileLength && imageOffset < bufferLength) {
            imageHeader = readImageHeader(buffer, imageOffset);
            imageSize = getImageSize(imageHeader[0]);
            imageOffset += imageHeader[1];
            result.images.push(imageSize);
        }
        return result;
    }
};


code:
import { IImage } from './interface';
export declare const ICO: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ICO = void 0;
const TYPE_ICON = 1;
/**
 * ICON Header
 *
 * | Offset | Size | Purpose |
 * | 0	    | 2    | Reserved. Must always be 0.  |
 * | 2      | 2    | Image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. |
 * | 4      | 2    | Number of images in the file. |
 *
 */
const SIZE_HEADER = 2 + 2 + 2; // 6
/**
 * Image Entry
 *
 * | Offset | Size | Purpose |
 * | 0	    | 1    | Image width in pixels. Can be any number between 0 and 255. Value 0 means width is 256 pixels. |
 * | 1      | 1    | Image height in pixels. Can be any number between 0 and 255. Value 0 means height is 256 pixels. |
 * | 2      | 1    | Number of colors in the color palette. Should be 0 if the image does not use a color palette. |
 * | 3      | 1    | Reserved. Should be 0. |
 * | 4      | 2    | ICO format: Color planes. Should be 0 or 1. |
 * |        |      | CUR format: The horizontal coordinates of the hotspot in number of pixels from the left. |
 * | 6      | 2    | ICO format: Bits per pixel. |
 * |        |      | CUR format: The vertical coordinates of the hotspot in number of pixels from the top. |
 * | 8      | 4    | The size of the image's data in bytes |
 * | 12     | 4    | The offset of BMP or PNG data from the beginning of the ICO/CUR file |
 *
 */
const SIZE_IMAGE_ENTRY = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4; // 16
function getSizeFromOffset(buffer, offset) {
    const value = buffer.readUInt8(offset);
    return value === 0 ? 256 : value;
}
function getImageSize(buffer, imageIndex) {
    const offset = SIZE_HEADER + (imageIndex * SIZE_IMAGE_ENTRY);
    return {
        height: getSizeFromOffset(buffer, offset + 1),
        width: getSizeFromOffset(buffer, offset)
    };
}
exports.ICO = {
    validate(buffer) {
        const reserved = buffer.readUInt16LE(0);
        const imageCount = buffer.readUInt16LE(4);
        if (reserved !== 0 || imageCount === 0) {
            return false;
        }
        const imageType = buffer.readUInt16LE(2);
        return imageType === TYPE_ICON;
    },
    calculate(buffer) {
        const nbImages = buffer.readUInt16LE(4);
        const imageSize = getImageSize(buffer, 0);
        if (nbImages === 1) {
            return imageSize;
        }
        const imgs = [imageSize];
        for (let imageIndex = 1; imageIndex < nbImages; imageIndex += 1) {
            imgs.push(getImageSize(buffer, imageIndex));
        }
        const result = {
            height: imageSize.height,
            images: imgs,
            width: imageSize.width
        };
        return result;
    }
};


code:
/// <reference types="node" />
export interface ISize {
    width: number | undefined;
    height: number | undefined;
    orientation?: number;
    type?: string;
}
export interface ISizeCalculationResult extends ISize {
    images?: ISize[];
}
export interface IImage {
    validate: (buffer: Buffer) => boolean;
    calculate: (buffer: Buffer, filepath?: string) => ISizeCalculationResult;
}


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });


code:
import { IImage } from './interface';
export declare const J2C: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.J2C = void 0;
exports.J2C = {
    validate(buffer) {
        // TODO: this doesn't seem right. SIZ marker doesn't have to be right after the SOC
        return buffer.toString('hex', 0, 4) === 'ff4fff51';
    },
    calculate(buffer) {
        return {
            height: buffer.readUInt32BE(12),
            width: buffer.readUInt32BE(8),
        };
    }
};


code:
import { IImage } from './interface';
export declare const JP2: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JP2 = void 0;
const BoxTypes = {
    ftyp: '66747970',
    ihdr: '69686472',
    jp2h: '6a703268',
    jp__: '6a502020',
    rreq: '72726571',
    xml_: '786d6c20'
};
const calculateRREQLength = (box) => {
    const unit = box.readUInt8(0);
    let offset = 1 + (2 * unit);
    const numStdFlags = box.readUInt16BE(offset);
    const flagsLength = numStdFlags * (2 + unit);
    offset = offset + 2 + flagsLength;
    const numVendorFeatures = box.readUInt16BE(offset);
    const featuresLength = numVendorFeatures * (16 + unit);
    return offset + 2 + featuresLength;
};
const parseIHDR = (box) => {
    return {
        height: box.readUInt32BE(4),
        width: box.readUInt32BE(8),
    };
};
exports.JP2 = {
    validate(buffer) {
        const signature = buffer.toString('hex', 4, 8);
        const signatureLength = buffer.readUInt32BE(0);
        if (signature !== BoxTypes.jp__ || signatureLength < 1) {
            return false;
        }
        const ftypeBoxStart = signatureLength + 4;
        const ftypBoxLength = buffer.readUInt32BE(signatureLength);
        const ftypBox = buffer.slice(ftypeBoxStart, ftypeBoxStart + ftypBoxLength);
        return ftypBox.toString('hex', 0, 4) === BoxTypes.ftyp;
    },
    calculate(buffer) {
        const signatureLength = buffer.readUInt32BE(0);
        const ftypBoxLength = buffer.readUInt16BE(signatureLength + 2);
        let offset = signatureLength + 4 + ftypBoxLength;
        const nextBoxType = buffer.toString('hex', offset, offset + 4);
        switch (nextBoxType) {
            case BoxTypes.rreq:
                // WHAT ARE THESE 4 BYTES?????
                // eslint-disable-next-line no-case-declarations
                const MAGIC = 4;
                offset = offset + 4 + MAGIC + calculateRREQLength(buffer.slice(offset + 4));
                return parseIHDR(buffer.slice(offset + 8, offset + 24));
            case BoxTypes.jp2h:
                return parseIHDR(buffer.slice(offset + 8, offset + 24));
            default:
                throw new TypeError('Unsupported header found: ' + buffer.toString('ascii', offset, offset + 4));
        }
    }
};


code:
import { IImage } from './interface';
export declare const JPG: IImage;


code:
"use strict";
// NOTE: we only support baseline and progressive JPGs here
// due to the structure of the loader class, we only get a buffer
// with a maximum size of 4096 bytes. so if the SOF marker is outside
// if this range we can't detect the file size correctly.
Object.defineProperty(exports, "__esModule", { value: true });
exports.JPG = void 0;
const readUInt_1 = require("../readUInt");
const EXIF_MARKER = '45786966';
const APP1_DATA_SIZE_BYTES = 2;
const EXIF_HEADER_BYTES = 6;
const TIFF_BYTE_ALIGN_BYTES = 2;
const BIG_ENDIAN_BYTE_ALIGN = '4d4d';
const LITTLE_ENDIAN_BYTE_ALIGN = '4949';
// Each entry is exactly 12 bytes
const IDF_ENTRY_BYTES = 12;
const NUM_DIRECTORY_ENTRIES_BYTES = 2;
function isEXIF(buffer) {
    return (buffer.toString('hex', 2, 6) === EXIF_MARKER);
}
function extractSize(buffer, index) {
    return {
        height: buffer.readUInt16BE(index),
        width: buffer.readUInt16BE(index + 2)
    };
}
function extractOrientation(exifBlock, isBigEndian) {
    // TODO: assert that this contains 0x002A
    // let STATIC_MOTOROLA_TIFF_HEADER_BYTES = 2
    // let TIFF_IMAGE_FILE_DIRECTORY_BYTES = 4
    // TODO: derive from TIFF_IMAGE_FILE_DIRECTORY_BYTES
    const idfOffset = 8;
    // IDF osset works from right after the header bytes
    // (so the offset includes the tiff byte align)
    const offset = EXIF_HEADER_BYTES + idfOffset;
    const idfDirectoryEntries = (0, readUInt_1.readUInt)(exifBlock, 16, offset, isBigEndian);
    for (let directoryEntryNumber = 0; directoryEntryNumber < idfDirectoryEntries; directoryEntryNumber++) {
        const start = offset + NUM_DIRECTORY_ENTRIES_BYTES + (directoryEntryNumber * IDF_ENTRY_BYTES);
        const end = start + IDF_ENTRY_BYTES;
        // Skip on corrupt EXIF blocks
        if (start > exifBlock.length) {
            return;
        }
        const block = exifBlock.slice(start, end);
        const tagNumber = (0, readUInt_1.readUInt)(block, 16, 0, isBigEndian);
        // 0x0112 (decimal: 274) is the `orientation` tag ID
        if (tagNumber === 274) {
            const dataFormat = (0, readUInt_1.readUInt)(block, 16, 2, isBigEndian);
            if (dataFormat !== 3) {
                return;
            }
            // unsinged int has 2 bytes per component
            // if there would more than 4 bytes in total it's a pointer
            const numberOfComponents = (0, readUInt_1.readUInt)(block, 32, 4, isBigEndian);
            if (numberOfComponents !== 1) {
                return;
            }
            return (0, readUInt_1.readUInt)(block, 16, 8, isBigEndian);
        }
    }
}
function validateExifBlock(buffer, index) {
    // Skip APP1 Data Size
    const exifBlock = buffer.slice(APP1_DATA_SIZE_BYTES, index);
    // Consider byte alignment
    const byteAlign = exifBlock.toString('hex', EXIF_HEADER_BYTES, EXIF_HEADER_BYTES + TIFF_BYTE_ALIGN_BYTES);
    // Ignore Empty EXIF. Validate byte alignment
    const isBigEndian = byteAlign === BIG_ENDIAN_BYTE_ALIGN;
    const isLittleEndian = byteAlign === LITTLE_ENDIAN_BYTE_ALIGN;
    if (isBigEndian || isLittleEndian) {
        return extractOrientation(exifBlock, isBigEndian);
    }
}
function validateBuffer(buffer, index) {
    // index should be within buffer limits
    if (index > buffer.length) {
        throw new TypeError('Corrupt JPG, exceeded buffer limits');
    }
    // Every JPEG block must begin with a 0xFF
    if (buffer[index] !== 0xFF) {
        throw new TypeError('Invalid JPG, marker table corrupted');
    }
}
exports.JPG = {
    validate(buffer) {
        const SOIMarker = buffer.toString('hex', 0, 2);
        return ('ffd8' === SOIMarker);
    },
    calculate(buffer) {
        // Skip 4 chars, they are for signature
        buffer = buffer.slice(4);
        let orientation;
        let next;
        while (buffer.length) {
            // read length of the next block
            const i = buffer.readUInt16BE(0);
            if (isEXIF(buffer)) {
                orientation = validateExifBlock(buffer, i);
            }
            // ensure correct format
            validateBuffer(buffer, i);
            // 0xFFC0 is baseline standard(SOF)
            // 0xFFC1 is baseline optimized(SOF)
            // 0xFFC2 is progressive(SOF2)
            next = buffer[i + 1];
            if (next === 0xC0 || next === 0xC1 || next === 0xC2) {
                const size = extractSize(buffer, i + 5);
                // TODO: is orientation=0 a valid answer here?
                if (!orientation) {
                    return size;
                }
                return {
                    height: size.height,
                    orientation,
                    width: size.width
                };
            }
            // move to the next block
            buffer = buffer.slice(i + 2);
        }
        throw new TypeError('Invalid JPG, no size found');
    }
};


code:
import { IImage } from './interface';
export declare const KTX: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KTX = void 0;
const SIGNATURE = 'KTX 11';
exports.KTX = {
    validate(buffer) {
        return SIGNATURE === buffer.toString('ascii', 1, 7);
    },
    calculate(buffer) {
        return {
            height: buffer.readUInt32LE(40),
            width: buffer.readUInt32LE(36),
        };
    }
};


code:
import { IImage } from './interface';
export declare const PNG: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PNG = void 0;
const pngSignature = 'PNG\r\n\x1a\n';
const pngImageHeaderChunkName = 'IHDR';
// Used to detect "fried" png's: http://www.jongware.com/pngdefry.html
const pngFriedChunkName = 'CgBI';
exports.PNG = {
    validate(buffer) {
        if (pngSignature === buffer.toString('ascii', 1, 8)) {
            let chunkName = buffer.toString('ascii', 12, 16);
            if (chunkName === pngFriedChunkName) {
                chunkName = buffer.toString('ascii', 28, 32);
            }
            if (chunkName !== pngImageHeaderChunkName) {
                throw new TypeError('Invalid PNG');
            }
            return true;
        }
        return false;
    },
    calculate(buffer) {
        if (buffer.toString('ascii', 12, 16) === pngFriedChunkName) {
            return {
                height: buffer.readUInt32BE(36),
                width: buffer.readUInt32BE(32)
            };
        }
        return {
            height: buffer.readUInt32BE(20),
            width: buffer.readUInt32BE(16)
        };
    }
};


code:
import { IImage } from './interface';
export declare const PNM: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PNM = void 0;
const PNMTypes = {
    P1: 'pbm/ascii',
    P2: 'pgm/ascii',
    P3: 'ppm/ascii',
    P4: 'pbm',
    P5: 'pgm',
    P6: 'ppm',
    P7: 'pam',
    PF: 'pfm'
};
const Signatures = Object.keys(PNMTypes);
const handlers = {
    default: (lines) => {
        let dimensions = [];
        while (lines.length > 0) {
            const line = lines.shift();
            if (line[0] === '#') {
                continue;
            }
            dimensions = line.split(' ');
            break;
        }
        if (dimensions.length === 2) {
            return {
                height: parseInt(dimensions[1], 10),
                width: parseInt(dimensions[0], 10),
            };
        }
        else {
            throw new TypeError('Invalid PNM');
        }
    },
    pam: (lines) => {
        const size = {};
        while (lines.length > 0) {
            const line = lines.shift();
            if (line.length > 16 || line.charCodeAt(0) > 128) {
                continue;
            }
            const [key, value] = line.split(' ');
            if (key && value) {
                size[key.toLowerCase()] = parseInt(value, 10);
            }
            if (size.height && size.width) {
                break;
            }
        }
        if (size.height && size.width) {
            return {
                height: size.height,
                width: size.width
            };
        }
        else {
            throw new TypeError('Invalid PAM');
        }
    }
};
exports.PNM = {
    validate(buffer) {
        const signature = buffer.toString('ascii', 0, 2);
        return Signatures.includes(signature);
    },
    calculate(buffer) {
        const signature = buffer.toString('ascii', 0, 2);
        const type = PNMTypes[signature];
        // TODO: this probably generates garbage. move to a stream based parser
        const lines = buffer.toString('ascii', 3).split(/[\r\n]+/);
        const handler = handlers[type] || handlers.default;
        return handler(lines);
    }
};


code:
import { IImage } from './interface';
export declare const PSD: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PSD = void 0;
exports.PSD = {
    validate(buffer) {
        return ('8BPS' === buffer.toString('ascii', 0, 4));
    },
    calculate(buffer) {
        return {
            height: buffer.readUInt32BE(14),
            width: buffer.readUInt32BE(18)
        };
    }
};


code:
import { IImage } from './interface';
export declare const SVG: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SVG = void 0;
const svgReg = /<svg\s([^>"']|"[^"]*"|'[^']*')*>/;
const extractorRegExps = {
    height: /\sheight=(['"])([^%]+?)\1/,
    root: svgReg,
    viewbox: /\sviewBox=(['"])(.+?)\1/i,
    width: /\swidth=(['"])([^%]+?)\1/,
};
const INCH_CM = 2.54;
const units = {
    in: 96,
    cm: 96 / INCH_CM,
    em: 16,
    ex: 8,
    m: 96 / INCH_CM * 100,
    mm: 96 / INCH_CM / 10,
    pc: 96 / 72 / 12,
    pt: 96 / 72,
    px: 1
};
const unitsReg = new RegExp(`^([0-9.]+(?:e\\d+)?)(${Object.keys(units).join('|')})?$`);
function parseLength(len) {
    const m = unitsReg.exec(len);
    if (!m) {
        return undefined;
    }
    return Math.round(Number(m[1]) * (units[m[2]] || 1));
}
function parseViewbox(viewbox) {
    const bounds = viewbox.split(' ');
    return {
        height: parseLength(bounds[3]),
        width: parseLength(bounds[2])
    };
}
function parseAttributes(root) {
    const width = root.match(extractorRegExps.width);
    const height = root.match(extractorRegExps.height);
    const viewbox = root.match(extractorRegExps.viewbox);
    return {
        height: height && parseLength(height[2]),
        viewbox: viewbox && parseViewbox(viewbox[2]),
        width: width && parseLength(width[2]),
    };
}
function calculateByDimensions(attrs) {
    return {
        height: attrs.height,
        width: attrs.width,
    };
}
function calculateByViewbox(attrs, viewbox) {
    const ratio = viewbox.width / viewbox.height;
    if (attrs.width) {
        return {
            height: Math.floor(attrs.width / ratio),
            width: attrs.width,
        };
    }
    if (attrs.height) {
        return {
            height: attrs.height,
            width: Math.floor(attrs.height * ratio),
        };
    }
    return {
        height: viewbox.height,
        width: viewbox.width,
    };
}
exports.SVG = {
    validate(buffer) {
        const str = String(buffer);
        return svgReg.test(str);
    },
    calculate(buffer) {
        const root = buffer.toString('utf8').match(extractorRegExps.root);
        if (root) {
            const attrs = parseAttributes(root[0]);
            if (attrs.width && attrs.height) {
                return calculateByDimensions(attrs);
            }
            if (attrs.viewbox) {
                return calculateByViewbox(attrs, attrs.viewbox);
            }
        }
        throw new TypeError('Invalid SVG');
    }
};


code:
import { IImage } from './interface';
export declare const TGA: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TGA = void 0;
exports.TGA = {
    validate(buffer) {
        return buffer.readUInt16LE(0) === 0 && buffer.readUInt16LE(4) === 0;
    },
    calculate(buffer) {
        return {
            height: buffer.readUInt16LE(14),
            width: buffer.readUInt16LE(12),
        };
    }
};


code:
import { IImage } from './interface';
export declare const TIFF: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TIFF = void 0;
// based on http://www.compix.com/fileformattif.htm
// TO-DO: support big-endian as well
const fs = require("fs");
const readUInt_1 = require("../readUInt");
// Read IFD (image-file-directory) into a buffer
function readIFD(buffer, filepath, isBigEndian) {
    const ifdOffset = (0, readUInt_1.readUInt)(buffer, 32, 4, isBigEndian);
    // read only till the end of the file
    let bufferSize = 1024;
    const fileSize = fs.statSync(filepath).size;
    if (ifdOffset + bufferSize > fileSize) {
        bufferSize = fileSize - ifdOffset - 10;
    }
    // populate the buffer
    const endBuffer = Buffer.alloc(bufferSize);
    const descriptor = fs.openSync(filepath, 'r');
    fs.readSync(descriptor, endBuffer, 0, bufferSize, ifdOffset);
    fs.closeSync(descriptor);
    return endBuffer.slice(2);
}
// TIFF values seem to be messed up on Big-Endian, this helps
function readValue(buffer, isBigEndian) {
    const low = (0, readUInt_1.readUInt)(buffer, 16, 8, isBigEndian);
    const high = (0, readUInt_1.readUInt)(buffer, 16, 10, isBigEndian);
    return (high << 16) + low;
}
// move to the next tag
function nextTag(buffer) {
    if (buffer.length > 24) {
        return buffer.slice(12);
    }
}
// Extract IFD tags from TIFF metadata
function extractTags(buffer, isBigEndian) {
    const tags = {};
    let temp = buffer;
    while (temp && temp.length) {
        const code = (0, readUInt_1.readUInt)(temp, 16, 0, isBigEndian);
        const type = (0, readUInt_1.readUInt)(temp, 16, 2, isBigEndian);
        const length = (0, readUInt_1.readUInt)(temp, 32, 4, isBigEndian);
        // 0 means end of IFD
        if (code === 0) {
            break;
        }
        else {
            // 256 is width, 257 is height
            // if (code === 256 || code === 257) {
            if (length === 1 && (type === 3 || type === 4)) {
                tags[code] = readValue(temp, isBigEndian);
            }
            // move to the next tag
            temp = nextTag(temp);
        }
    }
    return tags;
}
// Test if the TIFF is Big Endian or Little Endian
function determineEndianness(buffer) {
    const signature = buffer.toString('ascii', 0, 2);
    if ('II' === signature) {
        return 'LE';
    }
    else if ('MM' === signature) {
        return 'BE';
    }
}
const signatures = [
    // '492049', // currently not supported
    '49492a00',
    '4d4d002a', // Big Endian
    // '4d4d002a', // BigTIFF > 4GB. currently not supported
];
exports.TIFF = {
    validate(buffer) {
        return signatures.includes(buffer.toString('hex', 0, 4));
    },
    calculate(buffer, filepath) {
        if (!filepath) {
            throw new TypeError('Tiff doesn\'t support buffer');
        }
        // Determine BE/LE
        const isBigEndian = determineEndianness(buffer) === 'BE';
        // read the IFD
        const ifdBuffer = readIFD(buffer, filepath, isBigEndian);
        // extract the tags from the IFD
        const tags = extractTags(ifdBuffer, isBigEndian);
        const width = tags[256];
        const height = tags[257];
        if (!width || !height) {
            throw new TypeError('Invalid Tiff. Missing tags');
        }
        return { height, width };
    }
};


code:
import { IImage } from './interface';
export declare const WEBP: IImage;


code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WEBP = void 0;
function calculateExtended(buffer) {
    return {
        height: 1 + buffer.readUIntLE(7, 3),
        width: 1 + buffer.readUIntLE(4, 3)
    };
}
function calculateLossless(buffer) {
    return {
        height: 1 + (((buffer[4] & 0xF) << 10) | (buffer[3] << 2) | ((buffer[2] & 0xC0) >> 6)),
        width: 1 + (((buffer[2] & 0x3F) << 8) | buffer[1])
    };
}
function calculateLossy(buffer) {
    // `& 0x3fff` returns the last 14 bits
    // TO-DO: include webp scaling in the calculations
    return {
        height: buffer.readInt16LE(8) & 0x3fff,
        width: buffer.readInt16LE(6) & 0x3fff
    };
}
exports.WEBP = {
    validate(buffer) {
        const riffHeader = 'RIFF' === buffer.toString('ascii', 0, 4);
        const webpHeader = 'WEBP' === buffer.toString('ascii', 8, 12);
        const vp8Header = 'VP8' === buffer.toString('ascii', 12, 15);
        return (riffHeader && webpHeader && vp8Header);
    },
    calculate(buffer) {
        const chunkHeader = buffer.toString('ascii', 12, 16);
        buffer = buffer.slice(20, 30);
        // Extended webp stream signature
        if (chunkHeader === 'VP8X') {
            const extendedHeader = buffer[0];
            const validStart = (extendedHeader & 0xc0) === 0;
            const validEnd = (extendedHeader & 0x01) === 0;
            if (validStart && validEnd) {
                return calculateExtended(buffer);
            }
            else {
                // TODO: breaking change
                throw new TypeError('Invalid WebP');
            }
        }
        // Lossless webp stream signature
        if (chunkHeader === 'VP8 ' && buffer[0] !== 0x2f) {
            return calculateLossy(buffer);
        }
        // Lossy webp stream signature
        const signature = buffer.toString('hex', 3, 6);
        if (chunkHeader === 'VP8L' && signature !== '9d012a') {
            return calculateLossless(buffer);
        }
        throw new TypeError('Invalid WebP');
    }
};


image-size

description: get dimensions of any image file

pretest: eslint --ext .ts,.js bin lib specs

test: nyc mocha

clean: rm -rf dist docs

generate-docs: typedoc

build: tsc

prepack: yarn clean && yarn build

keywords: image size dimensions resolution width height png jpeg bmp gif psd tga tiff webp svg icns ico cur