| 'use strict'; | 
|   | 
| const colors = require('ansi-colors'); | 
| const clean = (str = '') => { | 
|   return typeof str === 'string' ? str.replace(/^['"]|['"]$/g, '') : ''; | 
| }; | 
|   | 
| /** | 
|  * This file contains the interpolation and rendering logic for | 
|  * the Snippet prompt. | 
|  */ | 
|   | 
| class Item { | 
|   constructor(token) { | 
|     this.name = token.key; | 
|     this.field = token.field || {}; | 
|     this.value = clean(token.initial || this.field.initial || ''); | 
|     this.message = token.message || this.name; | 
|     this.cursor = 0; | 
|     this.input = ''; | 
|     this.lines = []; | 
|   } | 
| } | 
|   | 
| const tokenize = async(options = {}, defaults = {}, fn = token => token) => { | 
|   let unique = new Set(); | 
|   let fields = options.fields || []; | 
|   let input = options.template; | 
|   let tabstops = []; | 
|   let items = []; | 
|   let keys = []; | 
|   let line = 1; | 
|   | 
|   if (typeof input === 'function') { | 
|     input = await input(); | 
|   } | 
|   | 
|   let i = -1; | 
|   let next = () => input[++i]; | 
|   let peek = () => input[i + 1]; | 
|   let push = token => { | 
|     token.line = line; | 
|     tabstops.push(token); | 
|   }; | 
|   | 
|   push({ type: 'bos', value: '' }); | 
|   | 
|   while (i < input.length - 1) { | 
|     let value = next(); | 
|   | 
|     if (/^[^\S\n ]$/.test(value)) { | 
|       push({ type: 'text', value }); | 
|       continue; | 
|     } | 
|   | 
|     if (value === '\n') { | 
|       push({ type: 'newline', value }); | 
|       line++; | 
|       continue; | 
|     } | 
|   | 
|     if (value === '\\') { | 
|       value += next(); | 
|       push({ type: 'text', value }); | 
|       continue; | 
|     } | 
|   | 
|     if ((value === '$' || value === '#' || value === '{') && peek() === '{') { | 
|       let n = next(); | 
|       value += n; | 
|   | 
|       let token = { type: 'template', open: value, inner: '', close: '', value }; | 
|       let ch; | 
|   | 
|       while ((ch = next())) { | 
|         if (ch === '}') { | 
|           if (peek() === '}') ch += next(); | 
|           token.value += ch; | 
|           token.close = ch; | 
|           break; | 
|         } | 
|   | 
|         if (ch === ':') { | 
|           token.initial = ''; | 
|           token.key = token.inner; | 
|         } else if (token.initial !== void 0) { | 
|           token.initial += ch; | 
|         } | 
|   | 
|         token.value += ch; | 
|         token.inner += ch; | 
|       } | 
|   | 
|       token.template = token.open + (token.initial || token.inner) + token.close; | 
|       token.key = token.key || token.inner; | 
|   | 
|       if (defaults.hasOwnProperty(token.key)) { | 
|         token.initial = defaults[token.key]; | 
|       } | 
|   | 
|       token = fn(token); | 
|       push(token); | 
|   | 
|       keys.push(token.key); | 
|       unique.add(token.key); | 
|   | 
|       let item = items.find(item => item.name === token.key); | 
|       token.field = fields.find(ch => ch.name === token.key); | 
|   | 
|       if (!item) { | 
|         item = new Item(token); | 
|         items.push(item); | 
|       } | 
|   | 
|       item.lines.push(token.line - 1); | 
|       continue; | 
|     } | 
|   | 
|     let last = tabstops[tabstops.length - 1]; | 
|     if (last.type === 'text' && last.line === line) { | 
|       last.value += value; | 
|     } else { | 
|       push({ type: 'text', value }); | 
|     } | 
|   } | 
|   | 
|   push({ type: 'eos', value: '' }); | 
|   return { input, tabstops, unique, keys, items }; | 
| }; | 
|   | 
| module.exports = async prompt => { | 
|   let options = prompt.options; | 
|   let required = new Set(options.required === true ? [] : (options.required || [])); | 
|   let defaults = { ...options.values, ...options.initial }; | 
|   let { tabstops, items, keys } = await tokenize(options, defaults); | 
|   | 
|   let result = createFn('result', prompt, options); | 
|   let format = createFn('format', prompt, options); | 
|   let isValid = createFn('validate', prompt, options, true); | 
|   let isVal = prompt.isValue.bind(prompt); | 
|   | 
|   return async(state = {}, submitted = false) => { | 
|     let index = 0; | 
|   | 
|     state.required = required; | 
|     state.items = items; | 
|     state.keys = keys; | 
|     state.output = ''; | 
|   | 
|     let validate = async(value, state, item, index) => { | 
|       let error = await isValid(value, state, item, index); | 
|       if (error === false) { | 
|         return 'Invalid field ' + item.name; | 
|       } | 
|       return error; | 
|     }; | 
|   | 
|     for (let token of tabstops) { | 
|       let value = token.value; | 
|       let key = token.key; | 
|   | 
|       if (token.type !== 'template') { | 
|         if (value) state.output += value; | 
|         continue; | 
|       } | 
|   | 
|       if (token.type === 'template') { | 
|         let item = items.find(ch => ch.name === key); | 
|   | 
|         if (options.required === true) { | 
|           state.required.add(item.name); | 
|         } | 
|   | 
|         let val = [item.input, state.values[item.value], item.value, value].find(isVal); | 
|         let field = item.field || {}; | 
|         let message = field.message || token.inner; | 
|   | 
|         if (submitted) { | 
|           let error = await validate(state.values[key], state, item, index); | 
|           if ((error && typeof error === 'string') || error === false) { | 
|             state.invalid.set(key, error); | 
|             continue; | 
|           } | 
|   | 
|           state.invalid.delete(key); | 
|           let res = await result(state.values[key], state, item, index); | 
|           state.output += colors.unstyle(res); | 
|           continue; | 
|         } | 
|   | 
|         item.placeholder = false; | 
|   | 
|         let before = value; | 
|         value = await format(value, state, item, index); | 
|   | 
|         if (val !== value) { | 
|           state.values[key] = val; | 
|           value = prompt.styles.typing(val); | 
|           state.missing.delete(message); | 
|   | 
|         } else { | 
|           state.values[key] = void 0; | 
|           val = `<${message}>`; | 
|           value = prompt.styles.primary(val); | 
|           item.placeholder = true; | 
|   | 
|           if (state.required.has(key)) { | 
|             state.missing.add(message); | 
|           } | 
|         } | 
|   | 
|         if (state.missing.has(message) && state.validating) { | 
|           value = prompt.styles.warning(val); | 
|         } | 
|   | 
|         if (state.invalid.has(key) && state.validating) { | 
|           value = prompt.styles.danger(val); | 
|         } | 
|   | 
|         if (index === state.index) { | 
|           if (before !== value) { | 
|             value = prompt.styles.underline(value); | 
|           } else { | 
|             value = prompt.styles.heading(colors.unstyle(value)); | 
|           } | 
|         } | 
|   | 
|         index++; | 
|       } | 
|   | 
|       if (value) { | 
|         state.output += value; | 
|       } | 
|     } | 
|   | 
|     let lines = state.output.split('\n').map(l => ' ' + l); | 
|     let len = items.length; | 
|     let done = 0; | 
|   | 
|     for (let item of items) { | 
|       if (state.invalid.has(item.name)) { | 
|         item.lines.forEach(i => { | 
|           if (lines[i][0] !== ' ') return; | 
|           lines[i] = state.styles.danger(state.symbols.bullet) + lines[i].slice(1); | 
|         }); | 
|       } | 
|   | 
|       if (prompt.isValue(state.values[item.name])) { | 
|         done++; | 
|       } | 
|     } | 
|   | 
|     state.completed = ((done / len) * 100).toFixed(0); | 
|     state.output = lines.join('\n'); | 
|     return state.output; | 
|   }; | 
| }; | 
|   | 
| function createFn(prop, prompt, options, fallback) { | 
|   return (value, state, item, index) => { | 
|     if (typeof item.field[prop] === 'function') { | 
|       return item.field[prop].call(prompt, value, state, item, index); | 
|     } | 
|     return [fallback, value].find(v => prompt.isValue(v)); | 
|   }; | 
| } |