.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / enquirer / lib / interpolate.js
1 'use strict';
2
3 const colors = require('ansi-colors');
4 const clean = (str = '') => {
5   return typeof str === 'string' ? str.replace(/^['"]|['"]$/g, '') : '';
6 };
7
8 /**
9  * This file contains the interpolation and rendering logic for
10  * the Snippet prompt.
11  */
12
13 class Item {
14   constructor(token) {
15     this.name = token.key;
16     this.field = token.field || {};
17     this.value = clean(token.initial || this.field.initial || '');
18     this.message = token.message || this.name;
19     this.cursor = 0;
20     this.input = '';
21     this.lines = [];
22   }
23 }
24
25 const tokenize = async(options = {}, defaults = {}, fn = token => token) => {
26   let unique = new Set();
27   let fields = options.fields || [];
28   let input = options.template;
29   let tabstops = [];
30   let items = [];
31   let keys = [];
32   let line = 1;
33
34   if (typeof input === 'function') {
35     input = await input();
36   }
37
38   let i = -1;
39   let next = () => input[++i];
40   let peek = () => input[i + 1];
41   let push = token => {
42     token.line = line;
43     tabstops.push(token);
44   };
45
46   push({ type: 'bos', value: '' });
47
48   while (i < input.length - 1) {
49     let value = next();
50
51     if (/^[^\S\n ]$/.test(value)) {
52       push({ type: 'text', value });
53       continue;
54     }
55
56     if (value === '\n') {
57       push({ type: 'newline', value });
58       line++;
59       continue;
60     }
61
62     if (value === '\\') {
63       value += next();
64       push({ type: 'text', value });
65       continue;
66     }
67
68     if ((value === '$' || value === '#' || value === '{') && peek() === '{') {
69       let n = next();
70       value += n;
71
72       let token = { type: 'template', open: value, inner: '', close: '', value };
73       let ch;
74
75       while ((ch = next())) {
76         if (ch === '}') {
77           if (peek() === '}') ch += next();
78           token.value += ch;
79           token.close = ch;
80           break;
81         }
82
83         if (ch === ':') {
84           token.initial = '';
85           token.key = token.inner;
86         } else if (token.initial !== void 0) {
87           token.initial += ch;
88         }
89
90         token.value += ch;
91         token.inner += ch;
92       }
93
94       token.template = token.open + (token.initial || token.inner) + token.close;
95       token.key = token.key || token.inner;
96
97       if (defaults.hasOwnProperty(token.key)) {
98         token.initial = defaults[token.key];
99       }
100
101       token = fn(token);
102       push(token);
103
104       keys.push(token.key);
105       unique.add(token.key);
106
107       let item = items.find(item => item.name === token.key);
108       token.field = fields.find(ch => ch.name === token.key);
109
110       if (!item) {
111         item = new Item(token);
112         items.push(item);
113       }
114
115       item.lines.push(token.line - 1);
116       continue;
117     }
118
119     let last = tabstops[tabstops.length - 1];
120     if (last.type === 'text' && last.line === line) {
121       last.value += value;
122     } else {
123       push({ type: 'text', value });
124     }
125   }
126
127   push({ type: 'eos', value: '' });
128   return { input, tabstops, unique, keys, items };
129 };
130
131 module.exports = async prompt => {
132   let options = prompt.options;
133   let required = new Set(options.required === true ? [] : (options.required || []));
134   let defaults = { ...options.values, ...options.initial };
135   let { tabstops, items, keys } = await tokenize(options, defaults);
136
137   let result = createFn('result', prompt, options);
138   let format = createFn('format', prompt, options);
139   let isValid = createFn('validate', prompt, options, true);
140   let isVal = prompt.isValue.bind(prompt);
141
142   return async(state = {}, submitted = false) => {
143     let index = 0;
144
145     state.required = required;
146     state.items = items;
147     state.keys = keys;
148     state.output = '';
149
150     let validate = async(value, state, item, index) => {
151       let error = await isValid(value, state, item, index);
152       if (error === false) {
153         return 'Invalid field ' + item.name;
154       }
155       return error;
156     };
157
158     for (let token of tabstops) {
159       let value = token.value;
160       let key = token.key;
161
162       if (token.type !== 'template') {
163         if (value) state.output += value;
164         continue;
165       }
166
167       if (token.type === 'template') {
168         let item = items.find(ch => ch.name === key);
169
170         if (options.required === true) {
171           state.required.add(item.name);
172         }
173
174         let val = [item.input, state.values[item.value], item.value, value].find(isVal);
175         let field = item.field || {};
176         let message = field.message || token.inner;
177
178         if (submitted) {
179           let error = await validate(state.values[key], state, item, index);
180           if ((error && typeof error === 'string') || error === false) {
181             state.invalid.set(key, error);
182             continue;
183           }
184
185           state.invalid.delete(key);
186           let res = await result(state.values[key], state, item, index);
187           state.output += colors.unstyle(res);
188           continue;
189         }
190
191         item.placeholder = false;
192
193         let before = value;
194         value = await format(value, state, item, index);
195
196         if (val !== value) {
197           state.values[key] = val;
198           value = prompt.styles.typing(val);
199           state.missing.delete(message);
200
201         } else {
202           state.values[key] = void 0;
203           val = `<${message}>`;
204           value = prompt.styles.primary(val);
205           item.placeholder = true;
206
207           if (state.required.has(key)) {
208             state.missing.add(message);
209           }
210         }
211
212         if (state.missing.has(message) && state.validating) {
213           value = prompt.styles.warning(val);
214         }
215
216         if (state.invalid.has(key) && state.validating) {
217           value = prompt.styles.danger(val);
218         }
219
220         if (index === state.index) {
221           if (before !== value) {
222             value = prompt.styles.underline(value);
223           } else {
224             value = prompt.styles.heading(colors.unstyle(value));
225           }
226         }
227
228         index++;
229       }
230
231       if (value) {
232         state.output += value;
233       }
234     }
235
236     let lines = state.output.split('\n').map(l => ' ' + l);
237     let len = items.length;
238     let done = 0;
239
240     for (let item of items) {
241       if (state.invalid.has(item.name)) {
242         item.lines.forEach(i => {
243           if (lines[i][0] !== ' ') return;
244           lines[i] = state.styles.danger(state.symbols.bullet) + lines[i].slice(1);
245         });
246       }
247
248       if (prompt.isValue(state.values[item.name])) {
249         done++;
250       }
251     }
252
253     state.completed = ((done / len) * 100).toFixed(0);
254     state.output = lines.join('\n');
255     return state.output;
256   };
257 };
258
259 function createFn(prop, prompt, options, fallback) {
260   return (value, state, item, index) => {
261     if (typeof item.field[prop] === 'function') {
262       return item.field[prop].call(prompt, value, state, item, index);
263     }
264     return [fallback, value].find(v => prompt.isValue(v));
265   };
266 }