help.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. module.exports = help
  2. help.completion = function (opts, cb) {
  3. if (opts.conf.argv.remain.length > 2) return cb(null, [])
  4. getSections(cb)
  5. }
  6. var path = require('path')
  7. var spawn = require('./utils/spawn')
  8. var npm = require('./npm.js')
  9. var log = require('npmlog')
  10. var openUrl = require('./utils/open-url')
  11. var glob = require('glob')
  12. var didYouMean = require('./utils/did-you-mean')
  13. var cmdList = require('./config/cmd-list').cmdList
  14. var shorthands = require('./config/cmd-list').shorthands
  15. var commands = cmdList.concat(Object.keys(shorthands))
  16. var output = require('./utils/output.js')
  17. function help (args, cb) {
  18. var argv = npm.config.get('argv').cooked
  19. var argnum = 0
  20. if (args.length === 2 && ~~args[0]) {
  21. argnum = ~~args.shift()
  22. }
  23. // npm help foo bar baz: search topics
  24. if (args.length > 1 && args[0]) {
  25. return npm.commands['help-search'](args, argnum, cb)
  26. }
  27. var section = npm.deref(args[0]) || args[0]
  28. // npm help <noargs>: show basic usage
  29. if (!section) {
  30. var valid = argv[0] === 'help' ? 0 : 1
  31. return npmUsage(valid, cb)
  32. }
  33. // npm <command> -h: show command usage
  34. if (npm.config.get('usage') &&
  35. npm.commands[section] &&
  36. npm.commands[section].usage) {
  37. npm.config.set('loglevel', 'silent')
  38. log.level = 'silent'
  39. output(npm.commands[section].usage)
  40. return cb()
  41. }
  42. // npm apihelp <section>: Prefer section 3 over section 1
  43. var apihelp = argv.length && argv[0].indexOf('api') !== -1
  44. var pref = apihelp ? [3, 1, 5, 7] : [1, 3, 5, 7]
  45. if (argnum) {
  46. pref = [ argnum ].concat(pref.filter(function (n) {
  47. return n !== argnum
  48. }))
  49. }
  50. // npm help <section>: Try to find the path
  51. var manroot = path.resolve(__dirname, '..', 'man')
  52. // legacy
  53. if (section === 'global') section = 'folders'
  54. else if (section.match(/.*json/)) section = section.replace('.json', '-json')
  55. // find either /section.n or /npm-section.n
  56. // The glob is used in the glob. The regexp is used much
  57. // further down. Globs and regexps are different
  58. var compextglob = '.+(gz|bz2|lzma|[FYzZ]|xz)'
  59. var compextre = '\\.(gz|bz2|lzma|[FYzZ]|xz)$'
  60. var f = '+(npm-' + section + '|' + section + ').[0-9]?(' + compextglob + ')'
  61. return glob(manroot + '/*/' + f, function (er, mans) {
  62. if (er) return cb(er)
  63. if (!mans.length) return npm.commands['help-search'](args, cb)
  64. mans = mans.map(function (man) {
  65. var ext = path.extname(man)
  66. if (man.match(new RegExp(compextre))) man = path.basename(man, ext)
  67. return man
  68. })
  69. viewMan(pickMan(mans, pref), cb)
  70. })
  71. }
  72. function pickMan (mans, pref_) {
  73. var nre = /([0-9]+)$/
  74. var pref = {}
  75. pref_.forEach(function (sect, i) {
  76. pref[sect] = i
  77. })
  78. mans = mans.sort(function (a, b) {
  79. var an = a.match(nre)[1]
  80. var bn = b.match(nre)[1]
  81. return an === bn ? (a > b ? -1 : 1)
  82. : pref[an] < pref[bn] ? -1
  83. : 1
  84. })
  85. return mans[0]
  86. }
  87. function viewMan (man, cb) {
  88. var nre = /([0-9]+)$/
  89. var num = man.match(nre)[1]
  90. var section = path.basename(man, '.' + num)
  91. // at this point, we know that the specified man page exists
  92. var manpath = path.join(__dirname, '..', 'man')
  93. var env = {}
  94. Object.keys(process.env).forEach(function (i) {
  95. env[i] = process.env[i]
  96. })
  97. env.MANPATH = manpath
  98. var viewer = npm.config.get('viewer')
  99. var conf
  100. switch (viewer) {
  101. case 'woman':
  102. var a = ['-e', '(woman-find-file \'' + man + '\')']
  103. conf = { env: env, stdio: 'inherit' }
  104. var woman = spawn('emacsclient', a, conf)
  105. woman.on('close', cb)
  106. break
  107. case 'browser':
  108. openUrl(htmlMan(man), 'help available at the following URL', cb)
  109. break
  110. default:
  111. conf = { env: env, stdio: 'inherit' }
  112. var manProcess = spawn('man', [num, section], conf)
  113. manProcess.on('close', cb)
  114. break
  115. }
  116. }
  117. function htmlMan (man) {
  118. var sect = +man.match(/([0-9]+)$/)[1]
  119. var f = path.basename(man).replace(/[.]([0-9]+)$/, '')
  120. switch (sect) {
  121. case 1:
  122. sect = 'cli-commands'
  123. break
  124. case 5:
  125. sect = 'configuring-npm'
  126. break
  127. case 7:
  128. sect = 'using-npm'
  129. break
  130. default:
  131. throw new Error('invalid man section: ' + sect)
  132. }
  133. return path.resolve(__dirname, '..', 'docs', 'public', sect, f, 'index.html')
  134. }
  135. function npmUsage (valid, cb) {
  136. npm.config.set('loglevel', 'silent')
  137. log.level = 'silent'
  138. output([
  139. '\nUsage: npm <command>',
  140. '',
  141. 'where <command> is one of:',
  142. npm.config.get('long') ? usages()
  143. : ' ' + wrap(commands),
  144. '',
  145. 'npm <command> -h quick help on <command>',
  146. 'npm -l display full usage info',
  147. 'npm help <term> search for help on <term>',
  148. 'npm help npm involved overview',
  149. '',
  150. 'Specify configs in the ini-formatted file:',
  151. ' ' + npm.config.get('userconfig'),
  152. 'or on the command line via: npm <command> --key value',
  153. 'Config info can be viewed via: npm help config',
  154. '',
  155. 'npm@' + npm.version + ' ' + path.dirname(__dirname)
  156. ].join('\n'))
  157. if (npm.argv.length > 1) {
  158. output(didYouMean(npm.argv[1], commands))
  159. }
  160. cb(valid)
  161. }
  162. function usages () {
  163. // return a string of <command>: <usage>
  164. var maxLen = 0
  165. return Object.keys(npm.commands).filter(function (c) {
  166. return c === npm.deref(c)
  167. }).reduce(function (set, c) {
  168. set.push([c, npm.commands[c].usage || ''])
  169. maxLen = Math.max(maxLen, c.length)
  170. return set
  171. }, []).map(function (item) {
  172. var c = item[0]
  173. var usage = item[1]
  174. return '\n ' +
  175. c + (new Array(maxLen - c.length + 2).join(' ')) +
  176. (usage.split('\n').join('\n' + (new Array(maxLen + 6).join(' '))))
  177. }).join('\n')
  178. }
  179. function wrap (arr) {
  180. var out = ['']
  181. var l = 0
  182. var line
  183. line = process.stdout.columns
  184. if (!line) {
  185. line = 60
  186. } else {
  187. line = Math.min(60, Math.max(line - 16, 24))
  188. }
  189. arr.sort(function (a, b) { return a < b ? -1 : 1 })
  190. .forEach(function (c) {
  191. if (out[l].length + c.length + 2 < line) {
  192. out[l] += ', ' + c
  193. } else {
  194. out[l++] += ','
  195. out[l] = c
  196. }
  197. })
  198. return out.join('\n ').substr(2)
  199. }
  200. function getSections (cb) {
  201. var g = path.resolve(__dirname, '../man/man[0-9]/*.[0-9]')
  202. glob(g, function (er, files) {
  203. if (er) return cb(er)
  204. cb(null, Object.keys(files.reduce(function (acc, file) {
  205. file = path.basename(file).replace(/\.[0-9]+$/, '')
  206. file = file.replace(/^npm-/, '')
  207. acc[file] = true
  208. return acc
  209. }, { help: true })))
  210. })
  211. }