| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 | '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));  };}
 |