{"size": 11499, "ext": "js", "max_stars_repo_path": "string.js", "max_stars_repo_name": "micosmogames/core", "max_stars_repo_stars_event_min_datetime": "2019-08-27T19:43:48.000Z", "max_stars_repo_stars_event_max_datetime": "2019-08-27T19:43:48.000Z", "max_issues_repo_path": "string.js", "max_issues_repo_name": "micosmogames/core", "max_issues_repo_issues_event_min_datetime": null, "max_issues_repo_issues_event_max_datetime": null, "max_forks_repo_path": "string.js", "max_forks_repo_name": "micosmogames/core", "max_forks_repo_forks_event_min_datetime": null, "max_forks_repo_forks_event_max_datetime": null}
{"max_stars_count": 1, "max_issues_count": null, "max_forks_count": null, "avg_line_length": 41.3633093525, "max_line_length": 135, "alphanum_fraction": 0.6156187495}
/**
 * string.js
 *
 * String related services
 *
 * StringBuilder
 * -------------
 * Constructs a string by collecting the parts into an array. The final result is answered by joining the parts.
 *
 * parseNameValues string format:
 * ------------------------------
 * [: <sep>][ ( <type> ) or ( [ <type ] ) ] <name> : <value>[, <value> , ...] <sep> ...
 *    where:
 *      <type> : s - trim str, rs - raw str, i - int, f - float, b - boolean
 *               v2 - { x, y }, v3 - { x, y, z}, v4 - { x, y, z, w }
 *               Defaults to 's'.
 *      <sep> : Specifies the separator character for the next and subsequent name/value(s) pair.
 *              '::' resets to the separator passed with the fn call. Defaults to ';'.
 *      <name> : The name of the pair
 *      <value> : The value of the pair. If type is of the form '([<type])' then the value is an array
 *                of <type> with values separated by a ','.
 */

'use strict';

const { isOperator, isWhitespace } = require('./character');
const { hasOwnProperty } = require('./object');

const _ = undefined;

module.exports = {
  StringBuilder,
  parseNameValues,
  stringifyNameValues,
  skip,
  skipRight,
  skipLeft
}

const StringBuilderPrototype = Object.create(Object.prototype, {
  name: { value: 'StringBuilder', enumerable: true }, // Deprecated. Use 'isaStringBuilder'
  isaStringBuilder: { value: true, enumerable: true },
  charAt: { value(idx) { return this.toString()[idx] }, enumerable: true },
  length: { value() { return this.toString().length }, enumerable: true },
  pop: { value() { return this.remove(this.segmentCount() - 1) }, enumerable: true },
  shift: { value() { return this.remove(0) }, enumerable: true },
  substr: { value(...args) { return this.toString().substr(...args) }, enumerable: true },
  substring: { value (...args) { return this.toString().substring(...args) }, enumerable: true },
  splice: { value(...args) { return this.toString().splice(...args) }, enumerable: true }
});

function StringBuilder () {
  let str; const aStrings = [];
  return Object.create(StringBuilderPrototype, {
    append: { value(s) { aStrings.push(typeof s === 'string' ? s : String(s)); str = undefined; return this }, enumerable: true },
    remove: { value(idx) { const a = aStrings.splice(idx, 1); return a[0] }, enumerable: true },
    clear: { value() { aStrings.length = 0; str = undefined; return this }, enumerable: true },
    atGet: { value(idx) { return (aStrings[idx]) }, enumerable: true }, // Returns the string segment at idx position
    atPut: { value(idx, s) { str = undefined; aStrings[idx] = typeof s === 'string' ? s : String(s); return this }, enumerable: true },
    segmentCount: { value() { return aStrings.length }, enumerable: true },
    toString: { value() { return str || (str = aStrings.join('')) }, enumerable: true },
  });
}

const ParseNameValuesDefaultOptions = {
  entrySeparator: ';', // Character seperating name/value entries.
  nameValueSeparator: ':', // Character separating the name and value of an entry
  valuesSeparator: ',', // Character seperating array values.
  appendDuplicates: false, // True to create an array of values for duplicate named entries, otherwise overwrite
  stringifyTypes: false // True to output type prefix when stringifying name/values.
}

function parseNameValues(data, oTgt = Object.create(null), options = ParseNameValuesDefaultOptions) {
  let vSep = options.valuesSeparator || ParseNameValuesDefaultOptions.valuesSeparator;
  let nvSep = options.nameValueSeparator || ParseNameValuesDefaultOptions.nameValueSeparator
  let eSep = options.entrySeparator || ParseNameValuesDefaultOptions.entrySeparator
  for (let iData = 0, ieSep = 0; iData < data.length; iData = ieSep + 1) {
    iData = skipRight(data, iData);
    // First of all see if a new entry, value and/or name/value separator has been specified.
    // ; - entry lead  , - values lead  : name/value lead
    for (let i = ';:,'.length; i > 0; i--, iData += 2) {
      const iLead = ';:,'.indexOf(data[iData]); const ch2 = data[iData + 1];
      if (iLead < 0 || !(isOperator(ch2) || isWhitespace(ch2))) break;
      switch (';:,'[iLead]) {
      case ';': eSep = ch2; break;
      case ',': vSep = ch2; break;
      case ':': nvSep = ch2; break;
      }
    }

    if ((ieSep = data.indexOf(eSep, iData)) < 0) ieSep = data.length;
    // Now process the content of this name/value pair.
    var tyHand = TypeHandler.s;
    var tyArray = false;
    if (data[iData] === '(') {
      var iStart = iData + 1;
      var iEnd = data.indexOf(')', iData);
      if (iEnd < 0)
        throw new Error(`micosmo:core:string:parseNameValues: Missing ')' for value type '${data.substring(iData)}'`);
      iData = iEnd + 1; iStart = skipRight(data, iStart);
      if (data[iStart] === '[') {
        tyArray = true; iStart++; const i = data.indexOf(']', iStart);
        if (i < 0 || i > iEnd)
          throw new Error(`micosmo:core:string:parseNameValues:: Missing ']' for value type '${data.substring(iStart - 2)}'`);
        iEnd = i;
      }
      [iStart, iEnd] = skip(data, iStart, iEnd - 1);
      tyHand = TypeHandler[data[iStart]]; // Match on first ty character.
      if (tyHand && iEnd === iStart + 1)
        tyHand = tyHand[data[iEnd]]; // Match on second ty character if present.
      if (!tyHand || typeof tyHand !== 'function' || iEnd - iStart > 1)
        throw new Error(`micosmo:core:string:parseNameValues: Invalid value type '${data.substring(iStart, iEnd + 1)}'`);
    }
    const i = data.indexOf(nvSep, iData);
    if (i < 0) {
      if (tyHand === TypeHandler.s || tyHand === TypeHandler.t) {
        const name = getString(data, iData, ieSep - 1);
        if (name) oTgt[name] = '';
        return oTgt;
      }
      throw new Error(`micosmo:core:string:parseNameValues: Invalid value for '${data.substring(iData, ieSep)}'`);
    }
    const name = getString(data, iData, i - 1);
    if (!name)
      throw new Error(`micosmo:core:string:parseNameValues: Missing name for value '${data.substring(i, ieSep)}'`);
    const val = tyArray ? parseValues(data, tyHand, i + 1, ieSep - 1, vSep) : tyHand(data, i + 1, ieSep - 1);
    if (oTgt[name] && options.appendDuplicates)
      oTgt[name] = (Array.isArray(oTgt[name]) ? oTgt[name] : [oTgt[name]]).concat(val);
    else
      oTgt[name] = val;
  }
  return oTgt;
}

function skipRight(s, iStart = 0) {
  for (; iStart < s.length && isWhitespace(s[iStart]); iStart++);
  return iStart;
}

function skipLeft(s, iEnd = s.length - 1) {
  for (; iEnd >= 0 && isWhitespace(s[iEnd]); iEnd--);
  return iEnd;
}

const SkipRetVals = [];
function skip(s, iStart, iEnd) {
  SkipRetVals[0] = skipRight(s, iStart); SkipRetVals[1] = skipLeft(s, iEnd);
  return SkipRetVals;
}

function parseValues(data, tyHand, iStart, iEnd, vSep) {
  const wsSep = isWhitespace(vSep);
  const vals = [];
  for (let i = data.indexOf(vSep, iStart); iStart <= iEnd + 1; iStart = i + 1, i = data.indexOf(vSep, iStart)) {
    if (i < 0 || i > iEnd) { vals.push(tyHand(data, iStart, iEnd)); break }
    if (wsSep && i === iStart) {
      // Treat leading whitespace vSep as just white space. In this case can't have zero length string values.
      while (i < iEnd && data[++i] === vSep);
      if ((i = data.indexOf(vSep, i)) < 0 || i > iEnd) { vals.push(tyHand(data, iStart, iEnd)); break }
    }
    vals.push(tyHand(data, iStart, i - 1));
  }
  return vals;
}

function getString(s, iStart, iEnd) {
  iStart = skipRight(s, iStart); iEnd = skipLeft(s, iEnd);
  return iStart > 0 || iEnd < s.length - 1 ? s.substring(iStart, iEnd + 1) : s;
}

var TypeHandler = {
  b(s, iStart) { iStart = skipRight(s, iStart); let i = 0; while (i < 4 && s[iStart++] === 'true'[i++]); return i === 4 },
  f(s, iStart, iEnd) { return parseFloat(getString(s, iStart, iEnd)) },
  i(s, iStart, iEnd) { return parseInt(getString(s, iStart, iEnd)) },
  s(s, iStart, iEnd) { return getString(s, iStart, iEnd) },
  t(s, iStart, iEnd) { return iStart > 0 || iEnd < s.length - 1 ? s.substring(iStart, iEnd + 1) : s },
  v: {
    '2'(s, iStart, iEnd) { return toVector(s, iStart, iEnd, 2) },
    '3'(s, iStart, iEnd) { return toVector(s, iStart, iEnd, 3) },
    '4'(s, iStart, iEnd) { return toVector(s, iStart, iEnd, 4) }
  }
};

const SymVector = Symbol('MicosmoVector');
const VectorProps = ['x', 'y', 'z', 'w'];
const VectorTypes = [_, _, 'v2', 'v3', 'v4'];

function toVector(s, iStart, iEnd, sz) {
  const vec = Object.create(null); vec[SymVector] = VectorTypes[sz];
  let count = 0;
  for (let i = s.indexOf(' ', iStart); i <= iEnd && count < sz; iStart = i + 1, i = s.indexOf(' ', iStart)) {
    if (i < 0) { if (iStart <= iEnd) vec[VectorProps[count++]] = TypeHandler.f(s, iStart, iEnd); break }
    if (iStart > i - 1) continue;
    vec[VectorProps[count++]] = TypeHandler.f(s, iStart, i - 1);
  }
  for (; count < sz; vec[VectorProps[count++]] = 0.0);
  return vec;
}

const Sb = StringBuilder();
function stringifyNameValues(oNameValues, options = ParseNameValuesDefaultOptions) {
  const vSep = options.valuesSeparator || ParseNameValuesDefaultOptions.valuesSeparator;
  const nvSep = options.nameValueSeparator || ParseNameValuesDefaultOptions.nameValueSeparator
  const eSep = options.entrySeparator || ParseNameValuesDefaultOptions.entrySeparator
  const flArrayAsDuplicates = options.appendDuplicates;
  const fl = options.stringifyTypes;
  Sb.clear();
  for (const prop in oNameValues) {
    if (!hasOwnProperty(oNameValues, prop)) continue;
    const val = oNameValues[prop];
    if (Array.isArray(val)) {
      if (flArrayAsDuplicates) {
        val.forEach(v => { addTypePrefix(v, fl); Sb.append(prop).append(nvSep).append(getValueString(v)).append(eSep) });
        continue;
      }
      if (val.length === 0) {
        Sb.append('([s])').append(prop).append(nvSep).append(eSep);
        continue;
      }
      addArrayPrefix(val, fl); Sb.append(prop).append(nvSep); val.forEach(v => { Sb.append(getValueString(v)).append(vSep) });
      Sb.atPut(Sb.segmentCount() - 1, eSep);
    } else {
      addTypePrefix(val, fl); Sb.append(prop).append(nvSep).append(getValueString(val)).append(eSep);
    }
  }
  return Sb.toString();
}

function addArrayPrefix(arr, flTypes) {
  if (!flTypes) return;
  var tyCode = getValueType(arr[0]);
  for (let i = 1; i < arr.length; i++) {
    const ty = getValueType(arr[i]);
    if (ty === tyCode) continue;
    if ((tyCode === 'i' && ty === 'f') || (tyCode === 'f' && ty === 'i')) {
      tyCode = 'f';
      continue;
    }
    tyCode = 's';
    break;
  }
  Sb.append(`([${tyCode}])`)
}

function addTypePrefix(v, flTypes) {
  if (!flTypes) return;
  const tyCode = getValueType(v);
  if (tyCode !== 's') Sb.append(`(${tyCode})`);
}

const StringifyVector = {
  v2(v) { return `${v.x} ${v.y}` },
  v3(v) { return `${v.x} ${v.y} ${v.z}` },
  v4(v) { return `${v.x} ${v.y} ${v.z} ${v.w}` }
}

function getValueString(v, tyCode) {
  const ty = typeof v;
  if (ty === 'string') return v;
  if (ty !== 'object') return String(v);
  if (v[SymVector]) return StringifyVector[v[SymVector]](v);
  return String(v);
}

function getValueType(v) {
  const ty = typeof v;
  switch (ty) {
  case 'boolean': return 'b';
  case 'number': return Number.isInteger(v) ? 'i' : 'f';
  case 'string': return (v.length === 0 || (!isWhitespace(v[0] && !isWhitespace(v[v.length - 1])))) ? 's' : 't';
  case 'object':
    if (v === null) return 's';
    if (v[SymVector]) return v[SymVector];
  default:
    return 's';
  }
}
