UNPKG

6.87 kBJavaScriptView Raw
1/**
2 * Copyright 2013-2022 the PM2 project authors. All rights reserved.
3 * Use of this source code is governed by a license that
4 * can be found in the LICENSE file.
5 */
6var fs = require('fs'),
7 pth = require('path');
8
9// hacked from node-tabtab 0.0.4 https://github.com/mklabs/node-tabtab.git
10// Itself based on npm completion by @isaac
11
12exports.complete = function complete(name, completer, cb) {
13
14 // cb not there, assume callback is completer and
15 // the completer is the executable itself
16 if(!cb) {
17 cb = completer;
18 completer = name;
19 }
20
21 var env = parseEnv();
22
23 // if not a complete command, return here.
24 if(!env.complete) return cb();
25
26 // if install cmd, add complete script to either ~/.bashrc or ~/.zshrc
27 if(env.install) return install(name, completer, function(err, state{
28 console.log(state || err.message);
29 if(err) return cb(err);
30 cb(null, null, state);
31 });
32
33 // if install cmd, add complete script to either ~/.bashrc or ~/.zshrc
34 if(env.uninstall) return uninstall(name, completer, function(err, state) {
35 console.log(state || err.message);
36 if(err) return cb(err);
37 cb(null, null, state);
38 });
39
40 // if the COMP_* are not in the env, then dump the install script.
41 if(!env.words || !env.point || !env.line) return script(name, completer, function(err, content) {
42 if(err) return cb(err);
43 process.stdout.write(content, function (n) { cb(null, null, content); });
44 process.stdout.on("error", function (er) {
45 // Darwin is a real dick sometimes.
46 //
47 // This is necessary because the "source" or "." program in
48 // bash on OS X closes its file argument before reading
49 // from it, meaning that you get exactly 1 write, which will
50 // work most of the time, and will always raise an EPIPE.
51 //
52 // Really, one should not be tossing away EPIPE errors, or any
53 // errors, so casually. But, without this, `. <(npm completion)`
54 // can never ever work on OS X.
55 // -- isaacs
56 // https://github.com/isaacs/npm/blob/master/lib/completion.js#L162
57 if (er.errno === "EPIPE") er = null
58 cb(er, null, content);
59 });
60 cb(null, null, content);
61 });
62
63 var partial = env.line.substr(0, env.point),
64 last = env.line.split(' ').slice(-1).join(''),
65 lastPartial = partial.split(' ').slice(-1).join(''),
66 prev = env.line.split(' ').slice(0, -1).slice(-1)[0];
67
68 cb(null, {
69 line: env.line,
70 words: env.words,
71 point: env.point,
72 partial: partial,
73 last: last,
74 prev: prev,
75 lastPartial: lastPartial
76 });
77};
78
79// simple helper function to know if the script is run
80// in the context of a completion command. Also mapping the
81// special `<pkgname> completion` cmd.
82exports.isComplete = function isComplete() {
83 var env = parseEnv();
84 return env.complete || (env.words && env.point && env.line);
85};
86
87exports.parseOut = function parseOut(str) {
88 var shorts = str.match(/\s-\w+/g);
89 var longs = str.match(/\s--\w+/g);
90
91 return {
92 shorts: shorts.map(trim).map(cleanPrefix),
93 longs: longs.map(trim).map(cleanPrefix)
94 };
95};
96
97// specific to cake case
98exports.parseTasks = function(str, prefix, reg) {
99 var tasks = str.match(reg || new RegExp('^' + prefix + '\\s[^#]+', 'gm')) || [];
100 return tasks.map(trim).map(function(s) {
101 return s.replace(prefix + ' ', '');
102 });
103};
104
105exports.log = function log(arr, o, prefix) {
106 prefix = prefix || '';
107 arr = Array.isArray(arr) ? arr : [arr];
108 arr.filter(abbrev(o)).forEach(function(v) {
109 console.log(prefix + v);
110 });
111}
112
113function trim (s) {
114 return s.trim();
115}
116
117function cleanPrefix(s) {
118 return s.replace(/-/g, '');
119}
120
121function abbrev(o) { return function(it) {
122 return new RegExp('^' + o.last.replace(/^--?/g, '')).test(it);
123}}
124
125// output the completion.sh script to the console for install instructions.
126// This is actually a 'template' where the package name is used to setup
127// the completion on the right command, and properly name the bash/zsh functions.
128function script(name, completer, cb) {
129 var p = pth.join(__dirname, 'completion.sh');
130
131 fs.readFile(p, 'utf8', function (er, d) {
132 if (er) return cb(er);
133 cb(null, d);
134 });
135}
136
137function install(name, completer, cb) {
138 var markerIn = '###-begin-' + name + '-completion-###',
139 markerOut = '###-end-' + name + '-completion-###';
140
141 var rc, scriptOutput;
142
143 readRc(completer, function(err, file) {
144 if(err) return cb(err);
145
146 var part = file.split(markerIn)[1];
147 if(part) {
148 return cb(null, ' ✗ ' + completer + ' tab-completion has been already installed. Do nothing.');
149 }
150
151 rc = file;
152 next();
153 });
154
155 script(name, completer, function(err, file) {
156 scriptOutput = file;
157 next();
158 });
159
160 function next() {
161 if(!rc || !scriptOutput) return;
162
163 writeRc(rc + scriptOutput, function(err) {
164 if(err) return cb(err);
165 return cb(null, ' ✓ ' + completer + ' tab-completion installed.');
166 });
167 }
168}
169
170function uninstall(name, completer, cb) {
171 var markerIn = '\n\n###-begin-' + name + '-completion-###',
172 markerOut = '###-end-' + name + '-completion-###\n';
173
174 readRc(completer, function(err, file) {
175 if(err) return cb(err);
176
177 var part = file.split(markerIn)[1];
178 if(!part) {
179 return cb(null, ' ✗ ' + completer + ' tab-completion has been already uninstalled. Do nothing.');
180 }
181
182 part = markerIn + part.split(markerOut)[0] + markerOut;
183 writeRc(file.replace(part, ''), function(err) {
184 if(err) return cb(err);
185 return cb(null, ' ✓ ' + completer + ' tab-completion uninstalled.');
186 });
187 });
188}
189
190function readRc(completer, cb) {
191 var file = '.' + process.env.SHELL.match(/\/bin\/(\w+)/)[1] + 'rc',
192 filepath = pth.join(process.env.HOME, file);
193 fs.lstat(filepath, function (err, stats) {
194 if(err) return cb(new Error("No " + file + " file. You'll have to run instead: " + completer + " completion >> ~/" + file));
195 fs.readFile(filepath, 'utf8', cb);
196 });
197}
198
199function writeRc(content, cb) {
200 var file = '.' + process.env.SHELL.match(/\/bin\/(\w+)/)[1] + 'rc',
201 filepath = pth.join(process.env.HOME, file);
202 fs.lstat(filepath, function (err, stats) {
203 if(err) return cb(new Error("No " + file + " file. You'll have to run instead: " + completer + " completion >> ~/" + file));
204 fs.writeFile(filepath, content, cb);
205 });
206}
207
208function installed (marker, completer, cb) {
209 readRc(completer, function(err, file) {
210 if(err) return cb(err);
211 var installed = file.match(marker);
212 return cb(!!installed);
213 });
214}
215
216function parseEnv() {
217 var args = process.argv.slice(2),
218 complete = args[0] === 'completion';
219
220 return {
221 args: args,
222 complete: complete,
223 install: complete && args[1] === 'install',
224 uninstall: complete && args[1] === 'uninstall',
225 words: +process.env.COMP_CWORD,
226 point: +process.env.COMP_POINT,
227 line: process.env.COMP_LINE
228 }
229};