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