1 |
|
2 | module.exports = help
|
3 |
|
4 | help.completion = function (opts, cb) {
|
5 | if (opts.conf.argv.remain.length > 2) return cb(null, [])
|
6 | getSections(cb)
|
7 | }
|
8 |
|
9 | var path = require('path')
|
10 | var spawn = require('./utils/spawn')
|
11 | var npm = require('./npm.js')
|
12 | var log = require('npmlog')
|
13 | var openUrl = require('./utils/open-url')
|
14 | var glob = require('glob')
|
15 | var didYouMean = require('./utils/did-you-mean')
|
16 | var cmdList = require('./config/cmd-list').cmdList
|
17 | var shorthands = require('./config/cmd-list').shorthands
|
18 | var commands = cmdList.concat(Object.keys(shorthands))
|
19 | var output = require('./utils/output.js')
|
20 |
|
21 | function 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 |
|
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 |
|
37 | if (!section) {
|
38 | var valid = argv[0] === 'help' ? 0 : 1
|
39 | return npmUsage(valid, cb)
|
40 | }
|
41 |
|
42 |
|
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 |
|
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 |
|
62 | var manroot = path.resolve(__dirname, '..', 'man')
|
63 |
|
64 |
|
65 | if (section === 'global') section = 'folders'
|
66 | else if (section === 'json') section = 'package.json'
|
67 |
|
68 |
|
69 |
|
70 |
|
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 |
|
90 | function 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 |
|
106 | function viewMan (man, cb) {
|
107 | var nre = /([0-9]+)$/
|
108 | var num = man.match(nre)[1]
|
109 | var section = path.basename(man, '.' + num)
|
110 |
|
111 |
|
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 |
|
141 | function htmlMan (man) {
|
142 | var sect = +man.match(/([0-9]+)$/)[1]
|
143 | var f = path.basename(man).replace(/([0-9]+)$/, 'html')
|
144 | switch (sect) {
|
145 | case 1:
|
146 | sect = 'cli'
|
147 | break
|
148 | case 3:
|
149 | sect = 'api'
|
150 | break
|
151 | case 5:
|
152 | sect = 'files'
|
153 | break
|
154 | case 7:
|
155 | sect = 'misc'
|
156 | break
|
157 | default:
|
158 | throw new Error('invalid man section: ' + sect)
|
159 | }
|
160 | return path.resolve(__dirname, '..', 'html', 'doc', sect, f)
|
161 | }
|
162 |
|
163 | function npmUsage (valid, cb) {
|
164 | npm.config.set('loglevel', 'silent')
|
165 | log.level = 'silent'
|
166 | output([
|
167 | '\nUsage: npm <command>',
|
168 | '',
|
169 | 'where <command> is one of:',
|
170 | npm.config.get('long') ? usages()
|
171 | : ' ' + wrap(commands),
|
172 | '',
|
173 | 'npm <command> -h quick help on <command>',
|
174 | 'npm -l display full usage info',
|
175 | 'npm help <term> search for help on <term>',
|
176 | 'npm help npm involved overview',
|
177 | '',
|
178 | 'Specify configs in the ini-formatted file:',
|
179 | ' ' + npm.config.get('userconfig'),
|
180 | 'or on the command line via: npm <command> --key value',
|
181 | 'Config info can be viewed via: npm help config',
|
182 | '',
|
183 | 'npm@' + npm.version + ' ' + path.dirname(__dirname)
|
184 | ].join('\n'))
|
185 |
|
186 | if (npm.argv.length > 1) {
|
187 | output(didYouMean(npm.argv[1], commands))
|
188 | }
|
189 |
|
190 | cb(valid)
|
191 | }
|
192 |
|
193 | function usages () {
|
194 |
|
195 | var maxLen = 0
|
196 | return Object.keys(npm.commands).filter(function (c) {
|
197 | return c === npm.deref(c)
|
198 | }).reduce(function (set, c) {
|
199 | set.push([c, npm.commands[c].usage || ''])
|
200 | maxLen = Math.max(maxLen, c.length)
|
201 | return set
|
202 | }, []).map(function (item) {
|
203 | var c = item[0]
|
204 | var usage = item[1]
|
205 | return '\n ' +
|
206 | c + (new Array(maxLen - c.length + 2).join(' ')) +
|
207 | (usage.split('\n').join('\n' + (new Array(maxLen + 6).join(' '))))
|
208 | }).join('\n')
|
209 | }
|
210 |
|
211 | function wrap (arr) {
|
212 | var out = ['']
|
213 | var l = 0
|
214 | var line
|
215 |
|
216 | line = process.stdout.columns
|
217 | if (!line) {
|
218 | line = 60
|
219 | } else {
|
220 | line = Math.min(60, Math.max(line - 16, 24))
|
221 | }
|
222 |
|
223 | arr.sort(function (a, b) { return a < b ? -1 : 1 })
|
224 | .forEach(function (c) {
|
225 | if (out[l].length + c.length + 2 < line) {
|
226 | out[l] += ', ' + c
|
227 | } else {
|
228 | out[l++] += ','
|
229 | out[l] = c
|
230 | }
|
231 | })
|
232 | return out.join('\n ').substr(2)
|
233 | }
|
234 |
|
235 | function getSections (cb) {
|
236 | var g = path.resolve(__dirname, '../man/man[0-9]/*.[0-9]')
|
237 | glob(g, function (er, files) {
|
238 | if (er) return cb(er)
|
239 |
|
240 | cb(null, Object.keys(files.reduce(function (acc, file) {
|
241 | file = path.basename(file).replace(/\.[0-9]+$/, '')
|
242 | file = file.replace(/^npm-/, '')
|
243 | acc[file] = true
|
244 | return acc
|
245 | }, { help: true })))
|
246 | })
|
247 | }
|