Sin descripción

Collection.js 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import { createNode } from '../doc/createNode.js';
  2. import { NodeBase, isNode, isPair, isCollection, isScalar } from './Node.js';
  3. function collectionFromPath(schema, path, value) {
  4. let v = value;
  5. for (let i = path.length - 1; i >= 0; --i) {
  6. const k = path[i];
  7. if (typeof k === 'number' && Number.isInteger(k) && k >= 0) {
  8. const a = [];
  9. a[k] = v;
  10. v = a;
  11. }
  12. else {
  13. v = new Map([[k, v]]);
  14. }
  15. }
  16. return createNode(v, undefined, {
  17. aliasDuplicateObjects: false,
  18. keepUndefined: false,
  19. onAnchor: () => {
  20. throw new Error('This should not happen, please report a bug.');
  21. },
  22. schema,
  23. sourceObjects: new Map()
  24. });
  25. }
  26. // Type guard is intentionally a little wrong so as to be more useful,
  27. // as it does not cover untypable empty non-string iterables (e.g. []).
  28. const isEmptyPath = (path) => path == null ||
  29. (typeof path === 'object' && !!path[Symbol.iterator]().next().done);
  30. class Collection extends NodeBase {
  31. constructor(type, schema) {
  32. super(type);
  33. Object.defineProperty(this, 'schema', {
  34. value: schema,
  35. configurable: true,
  36. enumerable: false,
  37. writable: true
  38. });
  39. }
  40. /**
  41. * Create a copy of this collection.
  42. *
  43. * @param schema - If defined, overwrites the original's schema
  44. */
  45. clone(schema) {
  46. const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this));
  47. if (schema)
  48. copy.schema = schema;
  49. copy.items = copy.items.map(it => isNode(it) || isPair(it) ? it.clone(schema) : it);
  50. if (this.range)
  51. copy.range = this.range.slice();
  52. return copy;
  53. }
  54. /**
  55. * Adds a value to the collection. For `!!map` and `!!omap` the value must
  56. * be a Pair instance or a `{ key, value }` object, which may not have a key
  57. * that already exists in the map.
  58. */
  59. addIn(path, value) {
  60. if (isEmptyPath(path))
  61. this.add(value);
  62. else {
  63. const [key, ...rest] = path;
  64. const node = this.get(key, true);
  65. if (isCollection(node))
  66. node.addIn(rest, value);
  67. else if (node === undefined && this.schema)
  68. this.set(key, collectionFromPath(this.schema, rest, value));
  69. else
  70. throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
  71. }
  72. }
  73. /**
  74. * Removes a value from the collection.
  75. * @returns `true` if the item was found and removed.
  76. */
  77. deleteIn(path) {
  78. const [key, ...rest] = path;
  79. if (rest.length === 0)
  80. return this.delete(key);
  81. const node = this.get(key, true);
  82. if (isCollection(node))
  83. return node.deleteIn(rest);
  84. else
  85. throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
  86. }
  87. /**
  88. * Returns item at `key`, or `undefined` if not found. By default unwraps
  89. * scalar values from their surrounding node; to disable set `keepScalar` to
  90. * `true` (collections are always returned intact).
  91. */
  92. getIn(path, keepScalar) {
  93. const [key, ...rest] = path;
  94. const node = this.get(key, true);
  95. if (rest.length === 0)
  96. return !keepScalar && isScalar(node) ? node.value : node;
  97. else
  98. return isCollection(node) ? node.getIn(rest, keepScalar) : undefined;
  99. }
  100. hasAllNullValues(allowScalar) {
  101. return this.items.every(node => {
  102. if (!isPair(node))
  103. return false;
  104. const n = node.value;
  105. return (n == null ||
  106. (allowScalar &&
  107. isScalar(n) &&
  108. n.value == null &&
  109. !n.commentBefore &&
  110. !n.comment &&
  111. !n.tag));
  112. });
  113. }
  114. /**
  115. * Checks if the collection includes a value with the key `key`.
  116. */
  117. hasIn(path) {
  118. const [key, ...rest] = path;
  119. if (rest.length === 0)
  120. return this.has(key);
  121. const node = this.get(key, true);
  122. return isCollection(node) ? node.hasIn(rest) : false;
  123. }
  124. /**
  125. * Sets a value in this collection. For `!!set`, `value` needs to be a
  126. * boolean to add/remove the item from the set.
  127. */
  128. setIn(path, value) {
  129. const [key, ...rest] = path;
  130. if (rest.length === 0) {
  131. this.set(key, value);
  132. }
  133. else {
  134. const node = this.get(key, true);
  135. if (isCollection(node))
  136. node.setIn(rest, value);
  137. else if (node === undefined && this.schema)
  138. this.set(key, collectionFromPath(this.schema, rest, value));
  139. else
  140. throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
  141. }
  142. }
  143. }
  144. Collection.maxFlowStringSingleLineLength = 60;
  145. export { Collection, collectionFromPath, isEmptyPath };