summaryrefslogtreecommitdiff
path: root/deps/npm/lib/utils/excludes.js
blob: 6df89ccaf4d786326d14d44b3d233d8597d5a5e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// build up a set of exclude lists in order of precedence:
// [ ["!foo", "bar"]
// , ["foo", "!bar"] ]
// being *included* will override a previous exclusion,
// and being excluded will override a previous inclusion.
//
// Each time the tar file-list generator thingie enters a new directory,
// it calls "addIgnoreFile(dir, list, cb)".  If an ignore file is found,
// then it is added to the list and the cb() is called with an
// child of the original list, so that we don't have
// to worry about popping it off at the right time, since other
// directories will continue to use the original parent list.
//
// If no ignore file is found, then the original list is returned.
//
// To start off with, ~/.{npm,git}ignore is added, as is
// prefix/{npm,git}ignore, effectively treated as if they were in the
// base package directory.

exports.addIgnoreFile = addIgnoreFile
exports.readIgnoreFile = readIgnoreFile
exports.parseIgnoreFile = parseIgnoreFile
exports.test = test
exports.filter = filter

var path = require("path")
  , fs = require("graceful-fs")
  , minimatch = require("minimatch")
  , relativize = require("./relativize.js")
  , log = require("./log.js")

// todo: memoize

// read an ignore file, or fall back to the
// "gitBase" file in the same directory.
function readIgnoreFile (file, gitBase, cb) {
  //log.warn(file, "ignoreFile")
  if (!file) return cb(null, "")
  fs.readFile(file, function (er, data) {
    if (!er || !gitBase) return cb(null, data || "")
    var gitFile = path.resolve(path.dirname(file), gitBase)
    fs.readFile(gitFile, function (er, data) {
      return cb(null, data || "")
    })
  })
}

// read a file, and then return the list of patterns
function parseIgnoreFile (file, gitBase, dir, cb) {
  readIgnoreFile(file, gitBase, function (er, data) {
    data = data ? data.toString("utf8") : ""

    data = data.split(/[\r\n]+/).map(function (p) {
      return p.trim()
    }).filter(function (p) {
      return p.length && p.charAt(0) !== "#"
    })
    data.dir = dir
    return cb(er, data)
  })
}

// add an ignore file to an existing list which can
// then be passed to the test() function. If the ignore
// file doesn't exist, then the list is unmodified. If
// it is, then a concat-child of the original is returned,
// so that this is suitable for walking a directory tree.
function addIgnoreFile (file, gitBase, list, dir, cb) {
  if (typeof cb !== "function") cb = dir, dir = path.dirname(file)
  if (typeof cb !== "function") cb = list, list = []
  parseIgnoreFile(file, gitBase, dir, function (er, data) {
    if (!er && data) {
      // package.json "files" array trumps everything
      // Make sure it's always last.
      if (list.length && list[list.length-1].packageFiles) {
        list = list.concat([data, list.pop()])
      } else {
        list = list.concat([data])
      }
    }
    cb(er, list)
  })
}


// no IO
// loop through the lists created in the functions above, and test to
// see if a file should be included or not, given those exclude lists.
function test (file, excludeList) {
  if (path.basename(file) === "package.json") return true
  //log.warn(file, "test file")
  //log.warn(excludeList, "test list")
  var incRe = /^\!(\!\!)*/
    , excluded = false
  for (var i = 0, l = excludeList.length; i < l; i ++) {
    var excludes = excludeList[i]
      , dir = excludes.dir

    // chop the filename down to be relative to excludeDir
    var rf = relativize(file, dir, true)
    rf = rf.replace(/^\.\//, "")

    for (var ii = 0, ll = excludes.length; ii < ll; ii ++) {
      //log.warn(JSON.stringify(excludes[ii]), "ex")
      var ex = excludes[ii].replace(/^(!*)\.\//, "$1")
        , inc = !!ex.match(incRe)

      // excluding/including a dir excludes/includes all the files in it.
      if (ex.slice(-1) === "/") ex += "**"

      // if this is not an inclusion attempt, and someone else
      // excluded it, then just continue, because there's nothing
      // that can be done here to change the exclusion.
      if (!inc && excluded) continue

      // if it's an inclusion attempt, and the file has not been
      // excluded, then skip it, because there's no need to try again.
      if (inc && !excluded) continue

      // if it matches the pattern, then it should be excluded.
      excluded = !!minimatch(rf, ex, { baseMatch: true })
      //if (inc) excluded = !excluded

      //if (excluded) {
      //  console.error("excluded %s %s", rf, ex)
      //}

      // if you include foo, then it also includes foo/bar.js
      if (inc && excluded && ex.slice(-3) !== "/**") {
        excluded = minimatch(rf, ex + "/**", { baseMatch: true })
        // console.error(rf, ex + "/**", inc, excluded)
      }
    }
    //log.warn([rf, excluded, excludes], "file, excluded, excludes")
  }
  // true if it *should* be included
  // log.warn([file, excludeList, excluded], "file, excluded")
  return !excluded
}

// returns a function suitable for Array#filter
function filter (dir, list) { return function (file) {
  file = file.trim()
  return file && test(path.resolve(dir, file), list)
}}