Document-9b4560a1.js
  1  'use strict';
  2  
  3  var PlainValue = require('./PlainValue-ec8e588e.js');
  4  var resolveSeq = require('./resolveSeq-d03cb037.js');
  5  var Schema = require('./Schema-88e323a7.js');
  6  
  7  const defaultOptions = {
  8    anchorPrefix: 'a',
  9    customTags: null,
 10    indent: 2,
 11    indentSeq: true,
 12    keepCstNodes: false,
 13    keepNodeTypes: true,
 14    keepBlobsInJSON: true,
 15    mapAsMap: false,
 16    maxAliasCount: 100,
 17    prettyErrors: false,
 18    // TODO Set true in v2
 19    simpleKeys: false,
 20    version: '1.2'
 21  };
 22  const scalarOptions = {
 23    get binary() {
 24      return resolveSeq.binaryOptions;
 25    },
 26  
 27    set binary(opt) {
 28      Object.assign(resolveSeq.binaryOptions, opt);
 29    },
 30  
 31    get bool() {
 32      return resolveSeq.boolOptions;
 33    },
 34  
 35    set bool(opt) {
 36      Object.assign(resolveSeq.boolOptions, opt);
 37    },
 38  
 39    get int() {
 40      return resolveSeq.intOptions;
 41    },
 42  
 43    set int(opt) {
 44      Object.assign(resolveSeq.intOptions, opt);
 45    },
 46  
 47    get null() {
 48      return resolveSeq.nullOptions;
 49    },
 50  
 51    set null(opt) {
 52      Object.assign(resolveSeq.nullOptions, opt);
 53    },
 54  
 55    get str() {
 56      return resolveSeq.strOptions;
 57    },
 58  
 59    set str(opt) {
 60      Object.assign(resolveSeq.strOptions, opt);
 61    }
 62  
 63  };
 64  const documentOptions = {
 65    '1.0': {
 66      schema: 'yaml-1.1',
 67      merge: true,
 68      tagPrefixes: [{
 69        handle: '!',
 70        prefix: PlainValue.defaultTagPrefix
 71      }, {
 72        handle: '!!',
 73        prefix: 'tag:private.yaml.org,2002:'
 74      }]
 75    },
 76    1.1: {
 77      schema: 'yaml-1.1',
 78      merge: true,
 79      tagPrefixes: [{
 80        handle: '!',
 81        prefix: '!'
 82      }, {
 83        handle: '!!',
 84        prefix: PlainValue.defaultTagPrefix
 85      }]
 86    },
 87    1.2: {
 88      schema: 'core',
 89      merge: false,
 90      tagPrefixes: [{
 91        handle: '!',
 92        prefix: '!'
 93      }, {
 94        handle: '!!',
 95        prefix: PlainValue.defaultTagPrefix
 96      }]
 97    }
 98  };
 99  
100  function stringifyTag(doc, tag) {
101    if ((doc.version || doc.options.version) === '1.0') {
102      const priv = tag.match(/^tag:private\.yaml\.org,2002:([^:/]+)$/);
103      if (priv) return '!' + priv[1];
104      const vocab = tag.match(/^tag:([a-zA-Z0-9-]+)\.yaml\.org,2002:(.*)/);
105      return vocab ? `!${vocab[1]}/${vocab[2]}` : `!${tag.replace(/^tag:/, '')}`;
106    }
107  
108    let p = doc.tagPrefixes.find(p => tag.indexOf(p.prefix) === 0);
109  
110    if (!p) {
111      const dtp = doc.getDefaults().tagPrefixes;
112      p = dtp && dtp.find(p => tag.indexOf(p.prefix) === 0);
113    }
114  
115    if (!p) return tag[0] === '!' ? tag : `!<${tag}>`;
116    const suffix = tag.substr(p.prefix.length).replace(/[!,[\]{}]/g, ch => ({
117      '!': '%21',
118      ',': '%2C',
119      '[': '%5B',
120      ']': '%5D',
121      '{': '%7B',
122      '}': '%7D'
123    })[ch]);
124    return p.handle + suffix;
125  }
126  
127  function getTagObject(tags, item) {
128    if (item instanceof resolveSeq.Alias) return resolveSeq.Alias;
129  
130    if (item.tag) {
131      const match = tags.filter(t => t.tag === item.tag);
132      if (match.length > 0) return match.find(t => t.format === item.format) || match[0];
133    }
134  
135    let tagObj, obj;
136  
137    if (item instanceof resolveSeq.Scalar) {
138      obj = item.value; // TODO: deprecate/remove class check
139  
140      const match = tags.filter(t => t.identify && t.identify(obj) || t.class && obj instanceof t.class);
141      tagObj = match.find(t => t.format === item.format) || match.find(t => !t.format);
142    } else {
143      obj = item;
144      tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass);
145    }
146  
147    if (!tagObj) {
148      const name = obj && obj.constructor ? obj.constructor.name : typeof obj;
149      throw new Error(`Tag not resolved for ${name} value`);
150    }
151  
152    return tagObj;
153  } // needs to be called before value stringifier to allow for circular anchor refs
154  
155  
156  function stringifyProps(node, tagObj, {
157    anchors,
158    doc
159  }) {
160    const props = [];
161    const anchor = doc.anchors.getName(node);
162  
163    if (anchor) {
164      anchors[anchor] = node;
165      props.push(`&${anchor}`);
166    }
167  
168    if (node.tag) {
169      props.push(stringifyTag(doc, node.tag));
170    } else if (!tagObj.default) {
171      props.push(stringifyTag(doc, tagObj.tag));
172    }
173  
174    return props.join(' ');
175  }
176  
177  function stringify(item, ctx, onComment, onChompKeep) {
178    const {
179      anchors,
180      schema
181    } = ctx.doc;
182    let tagObj;
183  
184    if (!(item instanceof resolveSeq.Node)) {
185      const createCtx = {
186        aliasNodes: [],
187        onTagObj: o => tagObj = o,
188        prevObjects: new Map()
189      };
190      item = schema.createNode(item, true, null, createCtx);
191  
192      for (const alias of createCtx.aliasNodes) {
193        alias.source = alias.source.node;
194        let name = anchors.getName(alias.source);
195  
196        if (!name) {
197          name = anchors.newName();
198          anchors.map[name] = alias.source;
199        }
200      }
201    }
202  
203    if (item instanceof resolveSeq.Pair) return item.toString(ctx, onComment, onChompKeep);
204    if (!tagObj) tagObj = getTagObject(schema.tags, item);
205    const props = stringifyProps(item, tagObj, ctx);
206    if (props.length > 0) ctx.indentAtStart = (ctx.indentAtStart || 0) + props.length + 1;
207    const str = typeof tagObj.stringify === 'function' ? tagObj.stringify(item, ctx, onComment, onChompKeep) : item instanceof resolveSeq.Scalar ? resolveSeq.stringifyString(item, ctx, onComment, onChompKeep) : item.toString(ctx, onComment, onChompKeep);
208    if (!props) return str;
209    return item instanceof resolveSeq.Scalar || str[0] === '{' || str[0] === '[' ? `${props} ${str}` : `${props}\n${ctx.indent}${str}`;
210  }
211  
212  class Anchors {
213    static validAnchorNode(node) {
214      return node instanceof resolveSeq.Scalar || node instanceof resolveSeq.YAMLSeq || node instanceof resolveSeq.YAMLMap;
215    }
216  
217    constructor(prefix) {
218      PlainValue._defineProperty(this, "map", Object.create(null));
219  
220      this.prefix = prefix;
221    }
222  
223    createAlias(node, name) {
224      this.setAnchor(node, name);
225      return new resolveSeq.Alias(node);
226    }
227  
228    createMergePair(...sources) {
229      const merge = new resolveSeq.Merge();
230      merge.value.items = sources.map(s => {
231        if (s instanceof resolveSeq.Alias) {
232          if (s.source instanceof resolveSeq.YAMLMap) return s;
233        } else if (s instanceof resolveSeq.YAMLMap) {
234          return this.createAlias(s);
235        }
236  
237        throw new Error('Merge sources must be Map nodes or their Aliases');
238      });
239      return merge;
240    }
241  
242    getName(node) {
243      const {
244        map
245      } = this;
246      return Object.keys(map).find(a => map[a] === node);
247    }
248  
249    getNames() {
250      return Object.keys(this.map);
251    }
252  
253    getNode(name) {
254      return this.map[name];
255    }
256  
257    newName(prefix) {
258      if (!prefix) prefix = this.prefix;
259      const names = Object.keys(this.map);
260  
261      for (let i = 1; true; ++i) {
262        const name = `${prefix}${i}`;
263        if (!names.includes(name)) return name;
264      }
265    } // During parsing, map & aliases contain CST nodes
266  
267  
268    resolveNodes() {
269      const {
270        map,
271        _cstAliases
272      } = this;
273      Object.keys(map).forEach(a => {
274        map[a] = map[a].resolved;
275      });
276  
277      _cstAliases.forEach(a => {
278        a.source = a.source.resolved;
279      });
280  
281      delete this._cstAliases;
282    }
283  
284    setAnchor(node, name) {
285      if (node != null && !Anchors.validAnchorNode(node)) {
286        throw new Error('Anchors may only be set for Scalar, Seq and Map nodes');
287      }
288  
289      if (name && /[\x00-\x19\s,[\]{}]/.test(name)) {
290        throw new Error('Anchor names must not contain whitespace or control characters');
291      }
292  
293      const {
294        map
295      } = this;
296      const prev = node && Object.keys(map).find(a => map[a] === node);
297  
298      if (prev) {
299        if (!name) {
300          return prev;
301        } else if (prev !== name) {
302          delete map[prev];
303          map[name] = node;
304        }
305      } else {
306        if (!name) {
307          if (!node) return null;
308          name = this.newName();
309        }
310  
311        map[name] = node;
312      }
313  
314      return name;
315    }
316  
317  }
318  
319  const visit = (node, tags) => {
320    if (node && typeof node === 'object') {
321      const {
322        tag
323      } = node;
324  
325      if (node instanceof resolveSeq.Collection) {
326        if (tag) tags[tag] = true;
327        node.items.forEach(n => visit(n, tags));
328      } else if (node instanceof resolveSeq.Pair) {
329        visit(node.key, tags);
330        visit(node.value, tags);
331      } else if (node instanceof resolveSeq.Scalar) {
332        if (tag) tags[tag] = true;
333      }
334    }
335  
336    return tags;
337  };
338  
339  const listTagNames = node => Object.keys(visit(node, {}));
340  
341  function parseContents(doc, contents) {
342    const comments = {
343      before: [],
344      after: []
345    };
346    let body = undefined;
347    let spaceBefore = false;
348  
349    for (const node of contents) {
350      if (node.valueRange) {
351        if (body !== undefined) {
352          const msg = 'Document contains trailing content not separated by a ... or --- line';
353          doc.errors.push(new PlainValue.YAMLSyntaxError(node, msg));
354          break;
355        }
356  
357        const res = resolveSeq.resolveNode(doc, node);
358  
359        if (spaceBefore) {
360          res.spaceBefore = true;
361          spaceBefore = false;
362        }
363  
364        body = res;
365      } else if (node.comment !== null) {
366        const cc = body === undefined ? comments.before : comments.after;
367        cc.push(node.comment);
368      } else if (node.type === PlainValue.Type.BLANK_LINE) {
369        spaceBefore = true;
370  
371        if (body === undefined && comments.before.length > 0 && !doc.commentBefore) {
372          // space-separated comments at start are parsed as document comments
373          doc.commentBefore = comments.before.join('\n');
374          comments.before = [];
375        }
376      }
377    }
378  
379    doc.contents = body || null;
380  
381    if (!body) {
382      doc.comment = comments.before.concat(comments.after).join('\n') || null;
383    } else {
384      const cb = comments.before.join('\n');
385  
386      if (cb) {
387        const cbNode = body instanceof resolveSeq.Collection && body.items[0] ? body.items[0] : body;
388        cbNode.commentBefore = cbNode.commentBefore ? `${cb}\n${cbNode.commentBefore}` : cb;
389      }
390  
391      doc.comment = comments.after.join('\n') || null;
392    }
393  }
394  
395  function resolveTagDirective({
396    tagPrefixes
397  }, directive) {
398    const [handle, prefix] = directive.parameters;
399  
400    if (!handle || !prefix) {
401      const msg = 'Insufficient parameters given for %TAG directive';
402      throw new PlainValue.YAMLSemanticError(directive, msg);
403    }
404  
405    if (tagPrefixes.some(p => p.handle === handle)) {
406      const msg = 'The %TAG directive must only be given at most once per handle in the same document.';
407      throw new PlainValue.YAMLSemanticError(directive, msg);
408    }
409  
410    return {
411      handle,
412      prefix
413    };
414  }
415  
416  function resolveYamlDirective(doc, directive) {
417    let [version] = directive.parameters;
418    if (directive.name === 'YAML:1.0') version = '1.0';
419  
420    if (!version) {
421      const msg = 'Insufficient parameters given for %YAML directive';
422      throw new PlainValue.YAMLSemanticError(directive, msg);
423    }
424  
425    if (!documentOptions[version]) {
426      const v0 = doc.version || doc.options.version;
427      const msg = `Document will be parsed as YAML ${v0} rather than YAML ${version}`;
428      doc.warnings.push(new PlainValue.YAMLWarning(directive, msg));
429    }
430  
431    return version;
432  }
433  
434  function parseDirectives(doc, directives, prevDoc) {
435    const directiveComments = [];
436    let hasDirectives = false;
437  
438    for (const directive of directives) {
439      const {
440        comment,
441        name
442      } = directive;
443  
444      switch (name) {
445        case 'TAG':
446          try {
447            doc.tagPrefixes.push(resolveTagDirective(doc, directive));
448          } catch (error) {
449            doc.errors.push(error);
450          }
451  
452          hasDirectives = true;
453          break;
454  
455        case 'YAML':
456        case 'YAML:1.0':
457          if (doc.version) {
458            const msg = 'The %YAML directive must only be given at most once per document.';
459            doc.errors.push(new PlainValue.YAMLSemanticError(directive, msg));
460          }
461  
462          try {
463            doc.version = resolveYamlDirective(doc, directive);
464          } catch (error) {
465            doc.errors.push(error);
466          }
467  
468          hasDirectives = true;
469          break;
470  
471        default:
472          if (name) {
473            const msg = `YAML only supports %TAG and %YAML directives, and not %${name}`;
474            doc.warnings.push(new PlainValue.YAMLWarning(directive, msg));
475          }
476  
477      }
478  
479      if (comment) directiveComments.push(comment);
480    }
481  
482    if (prevDoc && !hasDirectives && '1.1' === (doc.version || prevDoc.version || doc.options.version)) {
483      const copyTagPrefix = ({
484        handle,
485        prefix
486      }) => ({
487        handle,
488        prefix
489      });
490  
491      doc.tagPrefixes = prevDoc.tagPrefixes.map(copyTagPrefix);
492      doc.version = prevDoc.version;
493    }
494  
495    doc.commentBefore = directiveComments.join('\n') || null;
496  }
497  
498  function assertCollection(contents) {
499    if (contents instanceof resolveSeq.Collection) return true;
500    throw new Error('Expected a YAML collection as document contents');
501  }
502  
503  class Document {
504    constructor(options) {
505      this.anchors = new Anchors(options.anchorPrefix);
506      this.commentBefore = null;
507      this.comment = null;
508      this.contents = null;
509      this.directivesEndMarker = null;
510      this.errors = [];
511      this.options = options;
512      this.schema = null;
513      this.tagPrefixes = [];
514      this.version = null;
515      this.warnings = [];
516    }
517  
518    add(value) {
519      assertCollection(this.contents);
520      return this.contents.add(value);
521    }
522  
523    addIn(path, value) {
524      assertCollection(this.contents);
525      this.contents.addIn(path, value);
526    }
527  
528    delete(key) {
529      assertCollection(this.contents);
530      return this.contents.delete(key);
531    }
532  
533    deleteIn(path) {
534      if (resolveSeq.isEmptyPath(path)) {
535        if (this.contents == null) return false;
536        this.contents = null;
537        return true;
538      }
539  
540      assertCollection(this.contents);
541      return this.contents.deleteIn(path);
542    }
543  
544    getDefaults() {
545      return Document.defaults[this.version] || Document.defaults[this.options.version] || {};
546    }
547  
548    get(key, keepScalar) {
549      return this.contents instanceof resolveSeq.Collection ? this.contents.get(key, keepScalar) : undefined;
550    }
551  
552    getIn(path, keepScalar) {
553      if (resolveSeq.isEmptyPath(path)) return !keepScalar && this.contents instanceof resolveSeq.Scalar ? this.contents.value : this.contents;
554      return this.contents instanceof resolveSeq.Collection ? this.contents.getIn(path, keepScalar) : undefined;
555    }
556  
557    has(key) {
558      return this.contents instanceof resolveSeq.Collection ? this.contents.has(key) : false;
559    }
560  
561    hasIn(path) {
562      if (resolveSeq.isEmptyPath(path)) return this.contents !== undefined;
563      return this.contents instanceof resolveSeq.Collection ? this.contents.hasIn(path) : false;
564    }
565  
566    set(key, value) {
567      assertCollection(this.contents);
568      this.contents.set(key, value);
569    }
570  
571    setIn(path, value) {
572      if (resolveSeq.isEmptyPath(path)) this.contents = value;else {
573        assertCollection(this.contents);
574        this.contents.setIn(path, value);
575      }
576    }
577  
578    setSchema(id, customTags) {
579      if (!id && !customTags && this.schema) return;
580      if (typeof id === 'number') id = id.toFixed(1);
581  
582      if (id === '1.0' || id === '1.1' || id === '1.2') {
583        if (this.version) this.version = id;else this.options.version = id;
584        delete this.options.schema;
585      } else if (id && typeof id === 'string') {
586        this.options.schema = id;
587      }
588  
589      if (Array.isArray(customTags)) this.options.customTags = customTags;
590      const opt = Object.assign({}, this.getDefaults(), this.options);
591      this.schema = new Schema.Schema(opt);
592    }
593  
594    parse(node, prevDoc) {
595      if (this.options.keepCstNodes) this.cstNode = node;
596      if (this.options.keepNodeTypes) this.type = 'DOCUMENT';
597      const {
598        directives = [],
599        contents = [],
600        directivesEndMarker,
601        error,
602        valueRange
603      } = node;
604  
605      if (error) {
606        if (!error.source) error.source = this;
607        this.errors.push(error);
608      }
609  
610      parseDirectives(this, directives, prevDoc);
611      if (directivesEndMarker) this.directivesEndMarker = true;
612      this.range = valueRange ? [valueRange.start, valueRange.end] : null;
613      this.setSchema();
614      this.anchors._cstAliases = [];
615      parseContents(this, contents);
616      this.anchors.resolveNodes();
617  
618      if (this.options.prettyErrors) {
619        for (const error of this.errors) if (error instanceof PlainValue.YAMLError) error.makePretty();
620  
621        for (const warn of this.warnings) if (warn instanceof PlainValue.YAMLError) warn.makePretty();
622      }
623  
624      return this;
625    }
626  
627    listNonDefaultTags() {
628      return listTagNames(this.contents).filter(t => t.indexOf(Schema.Schema.defaultPrefix) !== 0);
629    }
630  
631    setTagPrefix(handle, prefix) {
632      if (handle[0] !== '!' || handle[handle.length - 1] !== '!') throw new Error('Handle must start and end with !');
633  
634      if (prefix) {
635        const prev = this.tagPrefixes.find(p => p.handle === handle);
636        if (prev) prev.prefix = prefix;else this.tagPrefixes.push({
637          handle,
638          prefix
639        });
640      } else {
641        this.tagPrefixes = this.tagPrefixes.filter(p => p.handle !== handle);
642      }
643    }
644  
645    toJSON(arg, onAnchor) {
646      const {
647        keepBlobsInJSON,
648        mapAsMap,
649        maxAliasCount
650      } = this.options;
651      const keep = keepBlobsInJSON && (typeof arg !== 'string' || !(this.contents instanceof resolveSeq.Scalar));
652      const ctx = {
653        doc: this,
654        indentStep: '  ',
655        keep,
656        mapAsMap: keep && !!mapAsMap,
657        maxAliasCount,
658        stringify // Requiring directly in Pair would create circular dependencies
659  
660      };
661      const anchorNames = Object.keys(this.anchors.map);
662      if (anchorNames.length > 0) ctx.anchors = new Map(anchorNames.map(name => [this.anchors.map[name], {
663        alias: [],
664        aliasCount: 0,
665        count: 1
666      }]));
667      const res = resolveSeq.toJSON(this.contents, arg, ctx);
668      if (typeof onAnchor === 'function' && ctx.anchors) for (const {
669        count,
670        res
671      } of ctx.anchors.values()) onAnchor(res, count);
672      return res;
673    }
674  
675    toString() {
676      if (this.errors.length > 0) throw new Error('Document with errors cannot be stringified');
677      const indentSize = this.options.indent;
678  
679      if (!Number.isInteger(indentSize) || indentSize <= 0) {
680        const s = JSON.stringify(indentSize);
681        throw new Error(`"indent" option must be a positive integer, not ${s}`);
682      }
683  
684      this.setSchema();
685      const lines = [];
686      let hasDirectives = false;
687  
688      if (this.version) {
689        let vd = '%YAML 1.2';
690  
691        if (this.schema.name === 'yaml-1.1') {
692          if (this.version === '1.0') vd = '%YAML:1.0';else if (this.version === '1.1') vd = '%YAML 1.1';
693        }
694  
695        lines.push(vd);
696        hasDirectives = true;
697      }
698  
699      const tagNames = this.listNonDefaultTags();
700      this.tagPrefixes.forEach(({
701        handle,
702        prefix
703      }) => {
704        if (tagNames.some(t => t.indexOf(prefix) === 0)) {
705          lines.push(`%TAG ${handle} ${prefix}`);
706          hasDirectives = true;
707        }
708      });
709      if (hasDirectives || this.directivesEndMarker) lines.push('---');
710  
711      if (this.commentBefore) {
712        if (hasDirectives || !this.directivesEndMarker) lines.unshift('');
713        lines.unshift(this.commentBefore.replace(/^/gm, '#'));
714      }
715  
716      const ctx = {
717        anchors: Object.create(null),
718        doc: this,
719        indent: '',
720        indentStep: ' '.repeat(indentSize),
721        stringify // Requiring directly in nodes would create circular dependencies
722  
723      };
724      let chompKeep = false;
725      let contentComment = null;
726  
727      if (this.contents) {
728        if (this.contents instanceof resolveSeq.Node) {
729          if (this.contents.spaceBefore && (hasDirectives || this.directivesEndMarker)) lines.push('');
730          if (this.contents.commentBefore) lines.push(this.contents.commentBefore.replace(/^/gm, '#')); // top-level block scalars need to be indented if followed by a comment
731  
732          ctx.forceBlockIndent = !!this.comment;
733          contentComment = this.contents.comment;
734        }
735  
736        const onChompKeep = contentComment ? null : () => chompKeep = true;
737        const body = stringify(this.contents, ctx, () => contentComment = null, onChompKeep);
738        lines.push(resolveSeq.addComment(body, '', contentComment));
739      } else if (this.contents !== undefined) {
740        lines.push(stringify(this.contents, ctx));
741      }
742  
743      if (this.comment) {
744        if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '') lines.push('');
745        lines.push(this.comment.replace(/^/gm, '#'));
746      }
747  
748      return lines.join('\n') + '\n';
749    }
750  
751  }
752  
753  PlainValue._defineProperty(Document, "defaults", documentOptions);
754  
755  exports.Document = Document;
756  exports.defaultOptions = defaultOptions;
757  exports.scalarOptions = scalarOptions;