spyne-cli 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/index-cli.js +56 -0
- package/index.js +14 -0
- package/lib/ansi.js +116 -0
- package/lib/combos.js +75 -0
- package/lib/completer.js +52 -0
- package/lib/interpolate.js +266 -0
- package/lib/keypress.js +243 -0
- package/lib/placeholder.js +63 -0
- package/lib/prompt.js +485 -0
- package/lib/prompts/autocomplete.js +113 -0
- package/lib/prompts/basicauth.js +41 -0
- package/lib/prompts/confirm.js +13 -0
- package/lib/prompts/editable.js +136 -0
- package/lib/prompts/form.js +196 -0
- package/lib/prompts/index.js +28 -0
- package/lib/prompts/input.js +55 -0
- package/lib/prompts/invisible.js +11 -0
- package/lib/prompts/list.js +36 -0
- package/lib/prompts/multiselect.js +11 -0
- package/lib/prompts/numeral.js +1 -0
- package/lib/prompts/password.js +18 -0
- package/lib/prompts/quiz.js +37 -0
- package/lib/prompts/scale.js +237 -0
- package/lib/prompts/select.js +139 -0
- package/lib/prompts/snippet.js +185 -0
- package/lib/prompts/sort.js +37 -0
- package/lib/prompts/survey.js +163 -0
- package/lib/prompts/text.js +1 -0
- package/lib/prompts/toggle.js +109 -0
- package/lib/render.js +33 -0
- package/lib/roles.js +46 -0
- package/lib/state.js +69 -0
- package/lib/styles.js +144 -0
- package/lib/symbols.js +66 -0
- package/lib/theme.js +11 -0
- package/lib/timer.js +38 -0
- package/lib/types/array.js +658 -0
- package/lib/types/auth.js +29 -0
- package/lib/types/boolean.js +88 -0
- package/lib/types/index.js +7 -0
- package/lib/types/number.js +86 -0
- package/lib/types/string.js +185 -0
- package/lib/utils.js +268 -0
- package/package.json +45 -0
- package/src/app/channels/.gitkeep +0 -0
- package/src/app/components/.gitkeep +0 -0
- package/src/app/traits/.gitkeep +0 -0
- package/src/spyne-file-prompt.js +63 -0
- package/src/spyne-template-prompts.js +171 -0
- package/src/templates/generate-file-string.js +131 -0
- package/src/templates/generate-prompt-input-fields.js +256 -0
- package/src/templates/generate-prompt-input-object.js +46 -0
- package/src/templates/generate-prompt-output.js +109 -0
- package/src/ui.js +16 -0
- package/src/utils/color-utils.js +36 -0
- package/src/utils/error-logger.js +15 -0
- package/src/utils/file-utils.js +69 -0
- package/tests/generate-file-prompt.test.js +47 -0
- package/tests/generate-file-string.test.js +68 -0
- package/tests/generate-prompt-input-fields-methods.test.js +178 -0
- package/tests/generate-prompt-input-fields.test.js +181 -0
- package/tests/generate-prompt-input-object.test.js +41 -0
- package/tests/generate-prompt-output.test.js +60 -0
- package/tests/index.test.js +13 -0
- package/tests/mocks/answers.js +33 -0
- package/tests/mocks/answers.json +33 -0
- package/tests/mocks/enquirer-data.js +411 -0
- package/tests/mocks/enquirer-data.json +409 -0
- package/tests/utils/file-utils.test.js +70 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const colors = require('ansi-colors');
|
|
4
|
+
const ArrayPrompt = require('../types/array');
|
|
5
|
+
const utils = require('../utils');
|
|
6
|
+
|
|
7
|
+
class LikertScale extends ArrayPrompt {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
super(options);
|
|
10
|
+
this.widths = [].concat(options.messageWidth || 50);
|
|
11
|
+
this.align = [].concat(options.align || 'left');
|
|
12
|
+
this.linebreak = options.linebreak || false;
|
|
13
|
+
this.edgeLength = options.edgeLength || 3;
|
|
14
|
+
this.newline = options.newline || '\n ';
|
|
15
|
+
let start = options.startNumber || 1;
|
|
16
|
+
if (typeof this.scale === 'number') {
|
|
17
|
+
this.scaleKey = false;
|
|
18
|
+
this.scale = Array(this.scale).fill(0).map((v, i) => ({ name: i + start }));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async reset() {
|
|
23
|
+
this.tableized = false;
|
|
24
|
+
await super.reset();
|
|
25
|
+
return this.render();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
tableize() {
|
|
29
|
+
if (this.tableized === true) return;
|
|
30
|
+
this.tableized = true;
|
|
31
|
+
let longest = 0;
|
|
32
|
+
|
|
33
|
+
for (let ch of this.choices) {
|
|
34
|
+
longest = Math.max(longest, ch.message.length);
|
|
35
|
+
ch.scaleIndex = ch.initial || 2;
|
|
36
|
+
ch.scale = [];
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < this.scale.length; i++) {
|
|
39
|
+
ch.scale.push({ index: i });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.widths[0] = Math.min(this.widths[0], longest + 3);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async dispatch(s, key) {
|
|
46
|
+
if (this.multiple) {
|
|
47
|
+
return this[key.name] ? await this[key.name](s, key) : await super.dispatch(s, key);
|
|
48
|
+
}
|
|
49
|
+
this.alert();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
heading(msg, item, i) {
|
|
53
|
+
return this.styles.strong(msg);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
separator() {
|
|
57
|
+
return this.styles.muted(this.symbols.ellipsis);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
right() {
|
|
61
|
+
let choice = this.focused;
|
|
62
|
+
if (choice.scaleIndex >= this.scale.length - 1) return this.alert();
|
|
63
|
+
choice.scaleIndex++;
|
|
64
|
+
return this.render();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
left() {
|
|
68
|
+
let choice = this.focused;
|
|
69
|
+
if (choice.scaleIndex <= 0) return this.alert();
|
|
70
|
+
choice.scaleIndex--;
|
|
71
|
+
return this.render();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
indent() {
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
format() {
|
|
79
|
+
if (this.state.submitted) {
|
|
80
|
+
let values = this.choices.map(ch => this.styles.info(ch.index));
|
|
81
|
+
return values.join(', ');
|
|
82
|
+
}
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pointer() {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Render the scale "Key". Something like:
|
|
92
|
+
* @return {String}
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
renderScaleKey() {
|
|
96
|
+
if (this.scaleKey === false) return '';
|
|
97
|
+
if (this.state.submitted) return '';
|
|
98
|
+
let scale = this.scale.map(item => ` ${item.name} - ${item.message}`);
|
|
99
|
+
let key = ['', ...scale].map(item => this.styles.muted(item));
|
|
100
|
+
return key.join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Render the heading row for the scale.
|
|
105
|
+
* @return {String}
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
renderScaleHeading(max) {
|
|
109
|
+
let keys = this.scale.map(ele => ele.name);
|
|
110
|
+
if (typeof this.options.renderScaleHeading === 'function') {
|
|
111
|
+
keys = this.options.renderScaleHeading.call(this, max);
|
|
112
|
+
}
|
|
113
|
+
let diff = this.scaleLength - keys.join('').length;
|
|
114
|
+
let spacing = Math.round(diff / (keys.length - 1));
|
|
115
|
+
let names = keys.map(key => this.styles.strong(key));
|
|
116
|
+
let headings = names.join(' '.repeat(spacing));
|
|
117
|
+
let padding = ' '.repeat(this.widths[0]);
|
|
118
|
+
return this.margin[3] + padding + this.margin[1] + headings;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Render a scale indicator => ◯ or ◉ by default
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
scaleIndicator(choice, item, i) {
|
|
126
|
+
if (typeof this.options.scaleIndicator === 'function') {
|
|
127
|
+
return this.options.scaleIndicator.call(this, choice, item, i);
|
|
128
|
+
}
|
|
129
|
+
let enabled = choice.scaleIndex === item.index;
|
|
130
|
+
if (item.disabled) return this.styles.hint(this.symbols.radio.disabled);
|
|
131
|
+
if (enabled) return this.styles.success(this.symbols.radio.on);
|
|
132
|
+
return this.symbols.radio.off;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Render the actual scale => ◯────◯────◉────◯────◯
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
renderScale(choice, i) {
|
|
140
|
+
let scale = choice.scale.map(item => this.scaleIndicator(choice, item, i));
|
|
141
|
+
let padding = this.term === 'Hyper' ? '' : ' ';
|
|
142
|
+
return scale.join(padding + this.symbols.line.repeat(this.edgeLength));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Render a choice, including scale =>
|
|
147
|
+
* "The website is easy to navigate. ◯───◯───◉───◯───◯"
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
async renderChoice(choice, i) {
|
|
151
|
+
await this.onChoice(choice, i);
|
|
152
|
+
|
|
153
|
+
let focused = this.index === i;
|
|
154
|
+
let pointer = await this.pointer(choice, i);
|
|
155
|
+
let hint = await choice.hint;
|
|
156
|
+
|
|
157
|
+
if (hint && !utils.hasColor(hint)) {
|
|
158
|
+
hint = this.styles.muted(hint);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let pad = str => this.margin[3] + str.replace(/\s+$/, '').padEnd(this.widths[0], ' ');
|
|
162
|
+
let newline = this.newline;
|
|
163
|
+
let ind = this.indent(choice);
|
|
164
|
+
let message = await this.resolve(choice.message, this.state, choice, i);
|
|
165
|
+
let scale = await this.renderScale(choice, i);
|
|
166
|
+
let margin = this.margin[1] + this.margin[3];
|
|
167
|
+
this.scaleLength = colors.unstyle(scale).length;
|
|
168
|
+
this.widths[0] = Math.min(this.widths[0], this.width - this.scaleLength - margin.length);
|
|
169
|
+
let msg = utils.wordWrap(message, { width: this.widths[0], newline });
|
|
170
|
+
let lines = msg.split('\n').map(line => pad(line) + this.margin[1]);
|
|
171
|
+
|
|
172
|
+
if (focused) {
|
|
173
|
+
scale = this.styles.info(scale);
|
|
174
|
+
lines = lines.map(line => this.styles.info(line));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
lines[0] += scale;
|
|
178
|
+
|
|
179
|
+
if (this.linebreak) lines.push('');
|
|
180
|
+
return [ind + pointer, lines.join('\n')].filter(Boolean);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async renderChoices() {
|
|
184
|
+
if (this.state.submitted) return '';
|
|
185
|
+
this.tableize();
|
|
186
|
+
let choices = this.visible.map(async(ch, i) => await this.renderChoice(ch, i));
|
|
187
|
+
let visible = await Promise.all(choices);
|
|
188
|
+
let heading = await this.renderScaleHeading();
|
|
189
|
+
return this.margin[0] + [heading, ...visible.map(v => v.join(' '))].join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async render() {
|
|
193
|
+
let { submitted, size } = this.state;
|
|
194
|
+
|
|
195
|
+
let prefix = await this.prefix();
|
|
196
|
+
let separator = await this.separator();
|
|
197
|
+
let message = await this.message();
|
|
198
|
+
|
|
199
|
+
let prompt = '';
|
|
200
|
+
if (this.options.promptLine !== false) {
|
|
201
|
+
prompt = [prefix, message, separator, ''].join(' ');
|
|
202
|
+
this.state.prompt = prompt;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let header = await this.header();
|
|
206
|
+
let output = await this.format();
|
|
207
|
+
let key = await this.renderScaleKey();
|
|
208
|
+
let help = await this.error() || await this.hint();
|
|
209
|
+
let body = await this.renderChoices();
|
|
210
|
+
let footer = await this.footer();
|
|
211
|
+
let err = this.emptyError;
|
|
212
|
+
|
|
213
|
+
if (output) prompt += output;
|
|
214
|
+
if (help && !prompt.includes(help)) prompt += ' ' + help;
|
|
215
|
+
|
|
216
|
+
if (submitted && !output && !body.trim() && this.multiple && err != null) {
|
|
217
|
+
prompt += this.styles.danger(err);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.clear(size);
|
|
221
|
+
this.write([header, prompt, key, body, footer].filter(Boolean).join('\n'));
|
|
222
|
+
if (!this.state.submitted) {
|
|
223
|
+
this.write(this.margin[2]);
|
|
224
|
+
}
|
|
225
|
+
this.restore();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
submit() {
|
|
229
|
+
this.value = {};
|
|
230
|
+
for (let choice of this.choices) {
|
|
231
|
+
this.value[choice.name] = choice.scaleIndex;
|
|
232
|
+
}
|
|
233
|
+
return this.base.submit.call(this);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = LikertScale;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ArrayPrompt = require('../types/array');
|
|
4
|
+
const utils = require('../utils');
|
|
5
|
+
|
|
6
|
+
class SelectPrompt extends ArrayPrompt {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
super(options);
|
|
9
|
+
this.emptyError = this.options.emptyError || 'No items were selected';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async dispatch(s, key) {
|
|
13
|
+
if (this.multiple) {
|
|
14
|
+
return this[key.name] ? await this[key.name](s, key) : await super.dispatch(s, key);
|
|
15
|
+
}
|
|
16
|
+
this.alert();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
separator() {
|
|
20
|
+
if (this.options.separator) return super.separator();
|
|
21
|
+
let sep = this.styles.muted(this.symbols.ellipsis);
|
|
22
|
+
return this.state.submitted ? super.separator() : sep;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pointer(choice, i) {
|
|
26
|
+
return (!this.multiple || this.options.pointer) ? super.pointer(choice, i) : '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
indicator(choice, i) {
|
|
30
|
+
return this.multiple ? super.indicator(choice, i) : '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
choiceMessage(choice, i) {
|
|
34
|
+
let message = this.resolve(choice.message, this.state, choice, i);
|
|
35
|
+
if (choice.role === 'heading' && !utils.hasColor(message)) {
|
|
36
|
+
message = this.styles.strong(message);
|
|
37
|
+
}
|
|
38
|
+
return this.resolve(message, this.state, choice, i);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
choiceSeparator() {
|
|
42
|
+
return ':';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async renderChoice(choice, i) {
|
|
46
|
+
await this.onChoice(choice, i);
|
|
47
|
+
|
|
48
|
+
let focused = this.index === i;
|
|
49
|
+
let pointer = await this.pointer(choice, i);
|
|
50
|
+
let check = await this.indicator(choice, i) + (choice.pad || '');
|
|
51
|
+
let hint = await this.resolve(choice.hint, this.state, choice, i);
|
|
52
|
+
|
|
53
|
+
if (hint && !utils.hasColor(hint)) {
|
|
54
|
+
hint = this.styles.muted(hint);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let ind = this.indent(choice);
|
|
58
|
+
let msg = await this.choiceMessage(choice, i);
|
|
59
|
+
let line = () => [this.margin[3], ind + pointer + check, msg, this.margin[1], hint].filter(Boolean).join(' ');
|
|
60
|
+
|
|
61
|
+
if (choice.role === 'heading') {
|
|
62
|
+
return line();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (choice.disabled) {
|
|
66
|
+
if (!utils.hasColor(msg)) {
|
|
67
|
+
msg = this.styles.disabled(msg);
|
|
68
|
+
}
|
|
69
|
+
return line();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (focused) {
|
|
73
|
+
msg = this.styles.em(msg);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return line();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async renderChoices() {
|
|
80
|
+
if (this.state.loading === 'choices') {
|
|
81
|
+
return this.styles.warning('Loading choices');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (this.state.submitted) return '';
|
|
85
|
+
let choices = this.visible.map(async(ch, i) => await this.renderChoice(ch, i));
|
|
86
|
+
let visible = await Promise.all(choices);
|
|
87
|
+
if (!visible.length) visible.push(this.styles.danger('No matching choices'));
|
|
88
|
+
let result = this.margin[0] + visible.join('\n');
|
|
89
|
+
let header;
|
|
90
|
+
|
|
91
|
+
if (this.options.choicesHeader) {
|
|
92
|
+
header = await this.resolve(this.options.choicesHeader, this.state);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return [header, result].filter(Boolean).join('\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
format() {
|
|
99
|
+
if (!this.state.submitted || this.state.cancelled) return '';
|
|
100
|
+
if (Array.isArray(this.selected)) {
|
|
101
|
+
return this.selected.map(choice => this.styles.primary(choice.name)).join(', ');
|
|
102
|
+
}
|
|
103
|
+
return this.styles.primary(this.selected.name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async render() {
|
|
107
|
+
let { submitted, size } = this.state;
|
|
108
|
+
|
|
109
|
+
let prompt = '';
|
|
110
|
+
let header = await this.header();
|
|
111
|
+
let prefix = await this.prefix();
|
|
112
|
+
let separator = await this.separator();
|
|
113
|
+
let message = await this.message();
|
|
114
|
+
|
|
115
|
+
if (this.options.promptLine !== false) {
|
|
116
|
+
prompt = [prefix, message, separator, ''].join(' ');
|
|
117
|
+
this.state.prompt = prompt;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let output = await this.format();
|
|
121
|
+
let help = (await this.error()) || (await this.hint());
|
|
122
|
+
let body = await this.renderChoices();
|
|
123
|
+
let footer = await this.footer();
|
|
124
|
+
|
|
125
|
+
if (output) prompt += output;
|
|
126
|
+
if (help && !prompt.includes(help)) prompt += ' ' + help;
|
|
127
|
+
|
|
128
|
+
if (submitted && !output && !body.trim() && this.multiple && this.emptyError != null) {
|
|
129
|
+
prompt += this.styles.danger(this.emptyError);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.clear(size);
|
|
133
|
+
this.write([header, prompt, body, footer].filter(Boolean).join('\n'));
|
|
134
|
+
this.write(this.margin[2]);
|
|
135
|
+
this.restore();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = SelectPrompt;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const colors = require('ansi-colors');
|
|
4
|
+
const interpolate = require('../interpolate');
|
|
5
|
+
const Prompt = require('../prompt');
|
|
6
|
+
|
|
7
|
+
class SnippetPrompt extends Prompt {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(options);
|
|
10
|
+
this.cursorHide();
|
|
11
|
+
this.reset(true);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async initialize() {
|
|
15
|
+
this.interpolate = await interpolate(this);
|
|
16
|
+
await super.initialize();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async reset(first) {
|
|
20
|
+
this.state.keys = [];
|
|
21
|
+
this.state.invalid = new Map();
|
|
22
|
+
this.state.missing = new Set();
|
|
23
|
+
this.state.completed = 0;
|
|
24
|
+
this.state.values = {};
|
|
25
|
+
|
|
26
|
+
if (first !== true) {
|
|
27
|
+
await this.initialize();
|
|
28
|
+
await this.render();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
moveCursor(n) {
|
|
33
|
+
let item = this.getItem();
|
|
34
|
+
this.cursor += n;
|
|
35
|
+
item.cursor += n;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
dispatch(ch, key) {
|
|
39
|
+
if (!key.code && !key.ctrl && ch != null && this.getItem()) {
|
|
40
|
+
this.append(ch, key);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.alert();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
append(ch, key) {
|
|
47
|
+
let item = this.getItem();
|
|
48
|
+
let prefix = item.input.slice(0, this.cursor);
|
|
49
|
+
let suffix = item.input.slice(this.cursor);
|
|
50
|
+
this.input = item.input = `${prefix}${ch}${suffix}`;
|
|
51
|
+
this.moveCursor(1);
|
|
52
|
+
this.render();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
delete() {
|
|
56
|
+
let item = this.getItem();
|
|
57
|
+
if (this.cursor <= 0 || !item.input) return this.alert();
|
|
58
|
+
let suffix = item.input.slice(this.cursor);
|
|
59
|
+
let prefix = item.input.slice(0, this.cursor - 1);
|
|
60
|
+
this.input = item.input = `${prefix}${suffix}`;
|
|
61
|
+
this.moveCursor(-1);
|
|
62
|
+
this.render();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
increment(i) {
|
|
66
|
+
return i >= this.state.keys.length - 1 ? 0 : i + 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
decrement(i) {
|
|
70
|
+
return i <= 0 ? this.state.keys.length - 1 : i - 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
first() {
|
|
74
|
+
this.state.index = 0;
|
|
75
|
+
this.render();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
last() {
|
|
79
|
+
this.state.index = this.state.keys.length - 1;
|
|
80
|
+
this.render();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
right() {
|
|
84
|
+
if (this.cursor >= this.input.length) return this.alert();
|
|
85
|
+
this.moveCursor(1);
|
|
86
|
+
this.render();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
left() {
|
|
90
|
+
if (this.cursor <= 0) return this.alert();
|
|
91
|
+
this.moveCursor(-1);
|
|
92
|
+
this.render();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
prev() {
|
|
96
|
+
this.state.index = this.decrement(this.state.index);
|
|
97
|
+
this.getItem();
|
|
98
|
+
this.render();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
next() {
|
|
102
|
+
this.state.index = this.increment(this.state.index);
|
|
103
|
+
this.getItem();
|
|
104
|
+
this.render();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
up() {
|
|
108
|
+
this.prev();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
down() {
|
|
112
|
+
this.next();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
format(value) {
|
|
116
|
+
let color = this.state.completed < 100 ? this.styles.warning : this.styles.success;
|
|
117
|
+
if (this.state.submitted === true && this.state.completed !== 100) {
|
|
118
|
+
color = this.styles.danger;
|
|
119
|
+
}
|
|
120
|
+
return color(`${this.state.completed}% completed`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async render() {
|
|
124
|
+
let { index, keys = [], submitted, size } = this.state;
|
|
125
|
+
|
|
126
|
+
let newline = [this.options.newline, '\n'].find(v => v != null);
|
|
127
|
+
let prefix = await this.prefix();
|
|
128
|
+
let separator = await this.separator();
|
|
129
|
+
let message = await this.message();
|
|
130
|
+
|
|
131
|
+
let prompt = [prefix, message, separator].filter(Boolean).join(' ');
|
|
132
|
+
this.state.prompt = prompt;
|
|
133
|
+
|
|
134
|
+
let header = await this.header();
|
|
135
|
+
let error = (await this.error()) || '';
|
|
136
|
+
let hint = (await this.hint()) || '';
|
|
137
|
+
let body = submitted ? '' : await this.interpolate(this.state);
|
|
138
|
+
|
|
139
|
+
let key = this.state.key = keys[index] || '';
|
|
140
|
+
let input = await this.format(key);
|
|
141
|
+
let footer = await this.footer();
|
|
142
|
+
if (input) prompt += ' ' + input;
|
|
143
|
+
if (hint && !input && this.state.completed === 0) prompt += ' ' + hint;
|
|
144
|
+
|
|
145
|
+
this.clear(size);
|
|
146
|
+
let lines = [header, prompt, body, footer, error.trim()];
|
|
147
|
+
this.write(lines.filter(Boolean).join(newline));
|
|
148
|
+
this.restore();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getItem(name) {
|
|
152
|
+
let { items, keys, index } = this.state;
|
|
153
|
+
let item = items.find(ch => ch.name === keys[index]);
|
|
154
|
+
if (item && item.input != null) {
|
|
155
|
+
this.input = item.input;
|
|
156
|
+
this.cursor = item.cursor;
|
|
157
|
+
}
|
|
158
|
+
return item;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async submit() {
|
|
162
|
+
if (typeof this.interpolate !== 'function') await this.initialize();
|
|
163
|
+
await this.interpolate(this.state, true);
|
|
164
|
+
|
|
165
|
+
let { invalid, missing, output, values } = this.state;
|
|
166
|
+
if (invalid.size) {
|
|
167
|
+
let err = '';
|
|
168
|
+
for (let [key, value] of invalid) err += `Invalid ${key}: ${value}\n`;
|
|
169
|
+
this.state.error = err;
|
|
170
|
+
return super.submit();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (missing.size) {
|
|
174
|
+
this.state.error = 'Required: ' + [...missing.keys()].join(', ');
|
|
175
|
+
return super.submit();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let lines = colors.unstyle(output).split('\n');
|
|
179
|
+
let result = lines.map(v => v.slice(1)).join('\n');
|
|
180
|
+
this.value = { values, result };
|
|
181
|
+
return super.submit();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = SnippetPrompt;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const hint = '(Use <shift>+<up/down> to sort)';
|
|
4
|
+
const Prompt = require('./select');
|
|
5
|
+
|
|
6
|
+
class Sort extends Prompt {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
super({ ...options, reorder: false, sort: true, multiple: true });
|
|
9
|
+
this.state.hint = [this.options.hint, hint].find(this.isValue.bind(this));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
indicator() {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async renderChoice(choice, i) {
|
|
17
|
+
let str = await super.renderChoice(choice, i);
|
|
18
|
+
let sym = this.symbols.identicalTo + ' ';
|
|
19
|
+
let pre = (this.index === i && this.sorting) ? this.styles.muted(sym) : ' ';
|
|
20
|
+
if (this.options.drag === false) pre = '';
|
|
21
|
+
if (this.options.numbered === true) {
|
|
22
|
+
return pre + `${i + 1} - ` + str;
|
|
23
|
+
}
|
|
24
|
+
return pre + str;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get selected() {
|
|
28
|
+
return this.choices;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
submit() {
|
|
32
|
+
this.value = this.choices.map(choice => choice.value);
|
|
33
|
+
return super.submit();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = Sort;
|