<commit_before>
// remove a package.  if it has dependents, then fail, and demand that they be
// uninstalled first.  If activee, then fail, and depand that it be deactivated
// first.

module.exports = uninstall

uninstall.usage = "npm uninstall <name>[@<version> [<name>[@<version>] ...]"
                + "\nnpm rm <name>[@<version> [<name>[@<version>] ...]"

var rm = require("./utils/rm-rf")
  , fs = require("./utils/graceful-fs")
  , log = require("./utils/log")
  , readJson = require("./utils/read-json")
  , path = require("path")
  , npm = require("../npm")
  , chain = require("./utils/chain")
  , lifecycle = require("./utils/lifecycle")
  , semver = require("./utils/semver")
  , readInstalled = require("./utils/read-installed")
  , mkdir = require("./utils/mkdir-p")
  , asyncMap = require("./utils/async-map")
  , loadPackageDefaults = require("./utils/load-package-defaults")

function uninstall (args, cb) {

  // for each arg:
  // unpack if no version found
  unpackArgs(args, function (er, args) {
    if (er) return cb(er)
    if (!args || !args.length) return cb()
    var rb = npm.ROLLBACK
    npm.ROLLBACK = true
    chain
      ( [firstPart, args]
      , [secondPart, args]
      , function (er) {
          npm.ROLLBACK = rb
          cb(er)
        }
      )
  })
}
function unpackArgs (rawArgs, cb) {
  readInstalled([], function (er, installed) {
    if (er) return cb(er)
    var args = []
      , argData = []
    rawArgs.filter(function (a) { return a }).forEach(function (arg) {
      arg = Array.isArray(arg) ? arg : arg.split("@")
      var ver = arg[1] || ""
        , pkg = arg[0] || ""
      arg = arg.join("@")
      if (!installed.hasOwnProperty(pkg)) return log(arg, "not installed")
      log.silly( Object.keys(installed[pkg]).join("\n"), "installed "+pkg)
      Object.keys(installed[pkg]).forEach(function (v) {
        if (semver.satisfies(v, ver)) args.push([pkg, v])
      })
    })
    asyncMap(args, function (arg, cb) {
      var jsonFile = path.join(npm.dir,arg[0],arg[1],"package","package.json")
      readJson( jsonFile
              , arg[1]
              , function (er, data) {
                  if (er) return cb(er)
                  loadPackageDefaults(data, function (er, data) {
                    if (er) return cb(er)
                    data.version = arg[1]
                    data._id = data.name+"@"+data.version
                    argData.push(data)
                    argData[data._id] = data
                    // todo: remove this kludge.  0.1.28
                    argData[data.name+"-"+data.version] = data
                    cb()
                  })
                }
              )
    }, function (er) {
      return cb(er, argData)
    })
  })
}
function firstPart (args, cb) {
  asyncMap(args, function (arg, cb) {
    var pkgdir = path.join(npm.dir, arg.name, arg.version)
    log.silly(Object.keys(args), "uninstall firstPart args")
    chain
      ( [lifecycle, arg, "preuninstall"]
      , [lifecycle, arg, "uninstall"]
      , [checkActive, arg]
      , [checkDependents, arg, args]
      , [log, "safe to uninstall: "+arg._id, "uninstall"]
      , cb
      )
  }, log.er(cb, "Failed safety check"))
}
// if this package is active, and auto-deactivate isn't true, then fail
function checkActive (pkg, cb) {
  var version = pkg.version
    , name = pkg.name
    , active = path.join(npm.dir, name, "active")
  fs.readlink(active, function (er, active) {
    if (er) return cb() // nothing active
    if (path.basename(active||"") !== version) return cb() // not active
    if (!npm.config.get("auto-deactivate")) return cb(new Error(
      "cannot remove active package.\n"+
      "      npm deactivate "+name+"\n"+
      "and then retry."))
    pkg._active = true
    cb()
  })
}

// if anything depends on this package, which isn't already in the "others" list,
// then fail horribly.
function checkDependents (pkg, others, cb) {
  var depDir = path.join(npm.dir, pkg.name, pkg.version, "dependents")
  fs.readdir(depDir, function (er, deps) {
    if (er) return cb() // no dependents
    for (var i = 0, l = deps.length; i < l; i ++) {
      var dep = deps[i]
      // if this dependent is on the list, then it's ok,
      // because it'll be removed, too.
      if (!others[dep]) return cb(new Error(
        pkg.name+"@"+pkg.version+" depended upon by "+deps.join(",")+".\n"
        +"Cannot remove"))
    }
    cb()
  })
}

function secondPart (args, cb) {
  asyncMap(args, function (arg, cb) {
    var name = arg.name
    chain
      ( function (cb) {
          if (!arg._active) return cb()
          log.verbose("auto-deactivate", "uninstall "+arg._id)
          npm.commands.deactivate([name], log.er(cb,
              "Failed to deactivate"))
        }
      , [ asyncMap, [arg]
        , function (a, cb) { log.verbose("remove links", "uninstall "+a._id, cb) }
        , removeDependentLinks
        , removeLinkedDeps
        , function (a, cb) { log.verbose("remove bins", "uninstall "+a._id, cb) }
        , removeBins
        , rmMans
        , function (a, cb) { log.verbose("remove public modules", "uninstall "+a._id, cb) }
        // todo: remove this kludge v0.1.28
        , function (arg, cb) {
            rm(path.join(npm.root, arg.name+"-"+arg.version), cb)
          }
        // todo: remove this kludge v0.1.28
        , function (arg, cb) {
            rm(path.join(npm.root, arg.name+"-"+arg.version+".js"), cb)
          }
        , function (arg, cb) {
            rm(path.join(npm.root, arg.name+"@"+arg.version), cb)
          }
        , function (arg, cb) {
            rm(path.join(npm.root, arg.name+"@"+arg.version+".js"), cb)
          }
        ]
      , [ lifecycle, arg, "postuninstall" ]
      , [ log.verbose, "remove package dir", "uninstall "+arg._id]
      , [ rm, path.join(npm.dir, arg.name, arg.version) ]
      , function (cb) {
          fs.readdir(path.join(npm.dir, arg.name), function (er, vers) {
            if (er) return cb()
            if (vers.length > 0) return cb()
            // remove the root of this package.
            rm(path.join(npm.dir, arg.name), cb)
          })
        }
      , [log, "complete", "uninstall "+arg._id]
      , cb
      )
  }, cb)
}
function rmMans (pkg, cb) {
  var manroot = npm.config.get("manroot")
  if (!pkg.man || !manroot) return cb()
  asyncMap(pkg.man, function (man, cb) {
    var parseMan = man.match(/(.*)\.([0-9]+)(\.gz)?$/)
      , stem = parseMan[1]
      , sxn = parseMan[2]<commit_msg>Add recursive remove support.  Major refactor.<commit_after>
// remove a package.  if it has dependents, then fail, and demand that they be
// uninstalled first.  If activee, then fail, and depand that it be deactivated
// first.

module.exports = uninstall

uninstall.usage = "npm uninstall <name>[@<version> [<name>[@<version>] ...]"
                + "\nnpm rm <name>[@<version> [<name>[@<version>] ...]"

var rm = require("./utils/rm-rf")
  , fs = require("./utils/graceful-fs")
  , log = require("./utils/log")
  , readJson = require("./utils/read-json")
  , path = require("path")
  , npm = require("../npm")
  , chain = require("./utils/chain")
  , lifecycle = require("./utils/lifecycle")
  , semver = require("./utils/semver")
  , readInstalled = require("./utils/read-installed")
  , mkdir = require("./utils/mkdir-p")
  , asyncMap = require("./utils/async-map")
  , loadPackageDefaults = require("./utils/load-package-defaults")

function uninstall (args, cb) {
  // for each arg:
  // unpack version ranges (empty version is like "*", remove all)
  // if in recursive mode, then also add dependents at this stage.
  // this way, it won't whine about not being able to remove anything, ever.
  unpackArgs(args, function (er, args) {
    if (args.length) log(args, "removing")
    if (er) return cb(er)
    if (!args || !args.length) return cb()
    var rb = npm.ROLLBACK
    npm.ROLLBACK = true
    function cb_ (er, removed) {
      npm.ROLLBACK = rb
      return cb(er, removed)
    }
    verifySafety(args, function (er, data) {
      if (er) return cb_(er, data)
      log.silly(data, "data")
      // now "data" is an array of packages to remove.
      // all of them exist and are safe to delete.
      secondPart(data, cb_)
    })
  })
}

// if any deps are found, and are valid (have a package.json),
// and are not also on the list, then yell about it.
// while we're at it, read the json here so that the secondPart
// gets the data necessary to actually remove stuff.
function verifySafety (args, cb) {
  var force = npm.config.get("force")
    , removeList = {}
  args.forEach(function (arg) { removeList[arg.join("@")] = true })
  asyncMap(args, function (pv, cb) {
    getDependents(pv, function (er, deps) {
      var pkg = pv.join("@")
      asyncMap(deps, function (dep, cb) {
        if (removeList[dep]) {
          return cb() // already going to be removed, ignore it.
        }
        // read the package.json file
        dep = dep.split("@")
        var depName = dep[0]
          , depVer = dep.slice(1).join("@")
        readJson( path.join(npm.dir, depName, depVer, "package", "package.json")
                , function (er, data) {
                    // ignore anything that doesn't have a valid package.json
                    // it's not a real dep, just a stray link from a historical bug.
                    return (er) ? cb() : cb(null, data)
                  }
                )
      }, function (er, depData) {
        // if there's anything in depData, and we're not forcing, then fail
        if (er) return cb(er)
        var depNames = depData.map(function (d) { return d._id })
        if (depNames.length) {
          if (force) {
            log.warn( "These packages depend on "+pkg+" and may cease to function"
                    , "forced uninstall"
                    )
            log.warn(depNames, "forced uninstall")
          } else return cb(new Error(pkg+" depended upon by "+depNames.join(",")+".\n"
                                    +"Cannot remove"))
        }
        readJson(path.join(npm.dir, pv[0], pv[1], "package","package.json"), cb)
      })
    })
  }, cb)
}


function unpackArgs (rawArgs, cb) {
  var recursive = npm.config.get("recursive")
    , seen = {}
  asyncMap(rawArgs, function (arg, cb) {
    if (!arg) return cb()
    arg = Array.isArray(arg) ? arg : arg.split("@")
    var pkg = arg[0]
      , ver = arg.slice(1).join("@")
    if (!pkg) return log.warn(arg, "bad arg", cb)
    arg = arg.join("@")
    if (semver.validRange(ver) === null) {
      return log.warn(ver, "bad version/range", cb)
    }
    fs.readdir(path.join(npm.dir, pkg), function (er, vers) {
      if (er) return log(arg, "not installed", cb)
      var pvs = selectVersions(pkg, vers, ver).filter(function (pv) {
        pv = pv.join("@")
        return (seen.hasOwnProperty(pv)) ? false : seen[pv] = true
      })
      if (!recursive) cb(null, pvs)
      else selectDependents(pvs, function (er, deps) {
        if (er) return cb(er)
        if (deps.length) {
          log.warn("Removing packages that depend on "+arg, "recursive uninstall")
          log.warn(deps, "recursive uninstall")
          rawArgs.push.apply(rawArgs, deps)
        }
        cb(null, pvs)
      })
    })
  }, cb)
}
function selectDependents (pvs, cb) { asyncMap(pvs, getDependents, cb) }
function getDependents (pv, cb) {
  fs.readdir(path.join(npm.dir, pv[0], pv[1], "dependents"), function (er, deps) {
    // error means no dependents, most likely
    cb(null, deps || [])
  })
}
function selectVersions (pkg, versions, range) {
  return versions.filter(function (v) {
    return semver.satisfies(v, range)
  }).map(function (v) {
    return [pkg, v]
  })
}

function secondPart (args, cb) {
  log.silly(args, "uninstall secondPart")
  asyncMap(args, function (arg, cb) {
    var name = arg.name
    chain
      ( [lifecycle, arg, "preuninstall"]
      , [lifecycle, arg, "uninstall"]
      , function (cb) {
          // get the active version
          var active = path.join(npm.dir, arg.name, "active")
          fs.lstat(active, function (er, s) {
            if (er || !s.isSymbolicLink()) return log("not symlink", "auto-deactive", cb)
            fs.readlink(active, function (er, ver) {
              if (er || ver !== "./" + arg.version) return cb()
              if (!npm.config.get("auto-deactivate")) return cb(new Error(
                "cannot remove active package.\n"+
                "      npm deactivate "+name+"\n"+
                "and then retry."))
              log.verbose("auto-deactivate", "uninstall "+arg._id)
              npm.commands.deactivate([name], log.er(cb,
                "Failed to deactivate "+name))
            })
          })
        }
      , [ asyncMap, [arg]
        , function (a, cb) { log.verbose("remove links", "uninstall "+a._id, cb) }
        , removeDependentLinks
        , removeLinkedDeps
        , function (a, cb) { log.verbose("remove bins", "uninstall "+a._id, cb) }
        , removeBins
        , rmMans
        , function (a, cb) { log.verbose("remove public modules", "uninstall "+a._id, cb) }
        // todo: remove this kludge v0.1.28
        , function (arg, cb) {
            rm(path.join(npm.root, arg.name+"-"+arg.version), cb)
          }
        // todo: remove this kludge v0.1.28
        , function (arg, cb) {
            rm(path.join(npm.root, arg.name+"-"+arg.version+".js"), cb)
          }
        , function (arg, cb) {
            rm(path.join(npm.root, arg.name+"@"+arg.version), cb)
          }
        , function (arg, cb) {
            rm(path.join(npm.root, arg.name+"@"+arg.version+".js"), cb)
          }
        ]
      , [ lifecycle, arg, "postuninstall" ]
      , [ log.verbose, "remove package dir", "uninstall "+arg._id]
      , [ rm, path.join(npm.dir, arg.name, arg.version) ]
      , function (cb) {
          fs.readdir(path.join(npm.dir, arg.name), function (er, vers) {
            if (er) return cb()
            vers = vers.filter(function (v) { return v !== "active" })
            if (vers.length > 0) return cb()
            // remove the root of this package.
            rm(path.join(npm.dir, arg.name), cb)
          })
        }
      , [log, "complete", "uninstall "+arg._id]
      , cb
      )
  }, cb)
}
function rmMans (pkg, cb) {
  var manroot = npm.config.get("manroot")
  if (!pkg.man || !manroot) return cb()
  asyncMap(pkg.man, function (man, cb) {
    var parseMan = man.match(/(.*)\.([0-9]+)(\.gz)?$/)
      , stem = parseMan[1]
      , sxn = parseMan[2]
{'fraction_non_alphanumeric': 0.16293797755902803, 'fraction_numerical': 0.0030976801817305705, 'mean_word_length': 2.161697497279652, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4285802', 'n_tokens_mistral': 4853, 'n_tokens_neox': 4701, 'n_words': 1252}