Skip to content

Commit

Permalink
Remove YAML.createNode()
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Recursively creating nodes now requires a document
instance.
  • Loading branch information
eemeli committed Aug 5, 2020
1 parent 5d2b627 commit b101145
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 194 deletions.
2 changes: 1 addition & 1 deletion docs/01_intro.md
Expand Up @@ -36,11 +36,11 @@ const YAML = require('yaml')

<h3>Documents</h3>

- [`YAML.createNode(value, wrapScalars, tag): Node`](#creating-nodes)
- [`YAML.defaultOptions`](#options)
- [`YAML.Document`](#documents)
- [`constructor(value, options)`](#creating-documents)
- [`defaults`](#options)
- [`#createNode(value, options): Node`](#creating-nodes)
- [`#anchors`](#working-with-anchors)
- [`#contents`](#content-nodes)
- [`#errors`](#errors)
Expand Down
45 changes: 23 additions & 22 deletions docs/04_documents.md
Expand Up @@ -98,16 +98,16 @@ During stringification, a document with a true-ish `version` value will include

## Document Methods

| Method | Returns | Description |
| ------------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| createNode(value,&nbsp;options?) | `Node` | Recursively turn objects into [collections](#collections). Generic objects as well as `Map` and its descendants become mappings, while arrays and other iterable objects result in sequences. If `options.wrapScalars` is undefined or `true`, it also wraps plain values in `Scalar` objects. To specify the collection type, set `options.tag` to its identifying string, e.g. `"!!omap"`. |
| createPair(key,&nbsp;value,&nbsp;options?) | `Pair` | Recursively wrap `key` and `value` into a `Pair` object. See `createNode()` for the available options. |
| listNonDefaultTags() | `string[]` | List the tags used in the document that are not in the default `tag:yaml.org,2002:` namespace. |
| parse(cst) | `Document` | Parse a CST into this document. Mostly an internal method, modifying the document according to the contents of the parsed `cst`. Calling this multiple times on a Document is not recommended. |
| setSchema(id?, customTags?) | `void` | Set the schema used by the document. `id` may either be a YAML version, or the identifier of a YAML 1.2 schema; if set, `customTags` should have the same shape as the similarly-named option. |
| setTagPrefix(handle, prefix) | `void` | Set `handle` as a shorthand string for the `prefix` tag namespace. |
| toJSON() | `any` | A plain JavaScript representation of the document `contents`. |
| toString() | `string` | A YAML representation of the document. |
| Method | Returns | Description |
| ------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| createNode(value,&nbsp;options?) | `Node` | Recursively wrap any input with appropriate `Node` containers. See [Creating Nodes](#creating-nodes) for more information. |
| createPair(key,&nbsp;value,&nbsp;options?) | `Pair` | Recursively wrap `key` and `value` into a `Pair` object. See [Creating Nodes](#creating-nodes) for more information. |
| listNonDefaultTags() | `string[]` | List the tags used in the document that are not in the default `tag:yaml.org,2002:` namespace. |
| parse(cst) | `Document` | Parse a CST into this document. Mostly an internal method, modifying the document according to the contents of the parsed `cst`. Calling this multiple times on a Document is not recommended. |
| setSchema(id?, customTags?) | `void` | Set the schema used by the document. `id` may either be a YAML version, or the identifier of a YAML 1.2 schema; if set, `customTags` should have the same shape as the similarly-named option. |
| setTagPrefix(handle, prefix) | `void` | Set `handle` as a shorthand string for the `prefix` tag namespace. |
| toJSON() | `any` | A plain JavaScript representation of the document `contents`. |
| toString() | `string` | A YAML representation of the document. |

```js
const doc = YAML.parseDocument('a: 1\nb: [2, 3]\n')
Expand Down Expand Up @@ -163,33 +163,34 @@ A description of [alias and merge nodes](#alias-nodes) is included in the next s
```js
const src = '[{ a: A }, { b: B }]'
const doc = YAML.parseDocument(src)
const { anchors, contents } = doc
const [a, b] = contents.items
anchors.setAnchor(a.items[0].value) // 'a1'
anchors.setAnchor(b.items[0].value) // 'a2'
anchors.setAnchor(null, 'a1') // 'a1'
anchors.getName(a) // undefined
anchors.getNode('a2')
doc.anchors.setAnchor(doc.getIn([0, 'a'], true)) // 'a1'
doc.anchors.setAnchor(doc.getIn([1, 'b'], true)) // 'a2'
doc.anchors.setAnchor(null, 'a1') // 'a1'
doc.anchors.getNode('a2')
// { value: 'B', range: [ 16, 18 ], type: 'PLAIN' }
String(doc)
// [ { a: A }, { b: &a2 B } ]

const alias = anchors.createAlias(a, 'AA')
contents.items.push(alias)
const alias = doc.anchors.createAlias(doc.get(0, true), 'AA')
// Alias { source: YAMLMap { items: [ [Pair] ] } }
doc.add(alias)
doc.toJSON()
// [ { a: 'A' }, { b: 'B' }, { a: 'A' } ]
String(doc)
// [ &AA { a: A }, { b: &a2 B }, *AA ]

const merge = anchors.createMergePair(alias)
b.items.push(merge)
const merge = doc.anchors.createMergePair(alias)
// Merge {
// key: Scalar { value: '<<' },
// value: YAMLSeq { items: [ [Alias] ] } }
doc.addIn([1], merge)
doc.toJSON()
// [ { a: 'A' }, { b: 'B', a: 'A' }, { a: 'A' } ]
String(doc)
// [ &AA { a: A }, { b: &a2 B, <<: *AA }, *AA ]

// This creates a circular reference
merge.value.items.push(anchors.createAlias(b))
merge.value.add(doc.anchors.createAlias(doc.get(1, true)))
doc.toJSON() // [RangeError: Maximum call stack size exceeded]
String(doc)
// [
Expand Down
61 changes: 33 additions & 28 deletions docs/05_content_nodes.md
Expand Up @@ -32,7 +32,7 @@ class Scalar extends Node {
}
```

A parsed document's contents will have all of its non-object values wrapped in `Scalar` objects, which themselves may be in some hierarchy of `Map` and `Seq` collections. However, this is not a requirement for the document's stringification, which is rather tolerant regarding its input values, and will use [`YAML.createNode`](#yaml-createnode) when encountering an unwrapped value.
A parsed document's contents will have all of its non-object values wrapped in `Scalar` objects, which themselves may be in some hierarchy of `Map` and `Seq` collections. However, this is not a requirement for the document's stringification, which is rather tolerant regarding its input values, and will use [`doc.createNode()`](#creating-nodes) when encountering an unwrapped value.

When stringifying, the node `type` will be taken into account by `!!str` and `!!binary` values, and ignored by other scalars. On the other hand, `!!int` and `!!float` stringifiers will take `format` into account.

Expand Down Expand Up @@ -74,14 +74,15 @@ All of the collections provide the following accessor methods:

<!-- prettier-ignore -->
```js
const map = YAML.createNode({ a: 1, b: [2, 3] })
const doc = new YAML.Document()
const map = doc.createNode({ a: 1, b: [2, 3] })
map.add({ key: 'c', value: 4 })
// => map.get('c') === 4 && map.has('c') === true
map.addIn(['b'], 5) // -> map.getIn(['b', 2]) === 5
map.delete('c') // true
map.deleteIn(['c', 'f']) // false
map.get('a') // 1
map.get(YAML.createNode('a'), true) // Scalar { value: 1 }
map.get(doc.createNode('a'), true) // Scalar { value: 1 }
map.getIn(['b', 1]) // 3
map.has('c') // false
map.hasIn(['b', '0']) // true
Expand All @@ -92,7 +93,7 @@ map.setIn(['c', 'x'])
// Expected YAML collection at c. Remaining path: x
```

For all of these methods, the keys may be nodes or their wrapped scalar values (i.e. `42` will match `Scalar { value: 42 }`) . Keys for `!!seq` should be positive integers, or their string representations. `add()` and `set()` do not automatically call `createNode()` to wrap the value.
For all of these methods, the keys may be nodes or their wrapped scalar values (i.e. `42` will match `Scalar { value: 42 }`) . Keys for `!!seq` should be positive integers, or their string representations. `add()` and `set()` do not automatically call `doc.createNode()` to wrap the value.

Each of the methods also has a variant that requires an iterable as the first parameter, and allows fetching or modifying deeper collections: `addIn(path, value)`, `deleteIn(path)`, `getIn(path, keepScalar)`, `hasIn(path)`, `setIn(path, value)`. If any intermediate node in `path` is a scalar rather than a collection, an error will be thrown. If any of the intermediate collections is not found:

Expand Down Expand Up @@ -124,7 +125,7 @@ YAML.stringify(obj)

`Alias` nodes provide a way to include a single node in multiple places in a document; the `source` of an alias node must be a preceding node in the document. Circular references are fully supported, and where possible the JS representation of alias nodes will be the actual source object.

When directly stringifying JS structures with `YAML.stringify()`, multiple references to the same object will result in including an autogenerated anchor at its first instance, and alias nodes to that anchor at later references. Directly calling `YAML.createNode()` will not create anchors or alias nodes, allowing for greater manual control.
When nodes are constructed from JS structures (e.g. during `YAML.stringify()`), multiple references to the same object will result in including an autogenerated anchor at its first instance, and alias nodes to that anchor at later references.

```js
class Merge extends Pair {
Expand All @@ -141,48 +142,52 @@ To create and work with alias and merge nodes, you should use the [`YAML.Documen
## Creating Nodes

```js
const seq = YAML.createNode(['some', 'values', { balloons: 99 }])
// YAMLSeq {
const doc = new YAML.Document(['some', 'values'])
// Document {
// contents:
// YAMLSeq {
// items:
// [ Scalar { value: 'some' },
// Scalar { value: 'values' } ] } }

const map = doc.createNode({ balloons: 99 })
// YAMLMap {
// items:
// [ Scalar { value: 'some' },
// Scalar { value: 'values' },
// YAMLMap {
// items:
// [ Pair {
// key: Scalar { value: 'balloons' },
// value: Scalar { value: 99 } } ] } ] }
// [ Pair {
// key: Scalar { value: 'balloons' },
// value: Scalar { value: 99 } } ] }

const doc = new YAML.Document()
doc.contents = seq
seq.items[0].comment = ' A commented item'
doc.add(map)
doc.get(0, true).comment = ' A commented item'
String(doc)
// - some # A commented item
// - values
// - balloons: 99
```

#### `YAML.createNode(value, wrapScalars?, tag?): Node`
#### `YAML.Document#createNode(value, options?): Node`

`YAML.createNode` recursively turns objects into [collections](#collections). Generic objects as well as `Map` and its descendants become mappings, while arrays and other iterable objects result in sequences. If `wrapScalars` is undefined or `true`, it also wraps plain values in `Scalar` objects; if it is false and `value` is not an object, it will be returned directly.
To create a new node, use the `createNode(value, options?)` document method. This will recursively wrap any input with appropriate `Node` containers. Generic objects as well as `Map` and its descendants become mappings, while arrays and other iterable objects result in sequences.

To specify the collection type, set `tag` to its identifying string, e.g. `"!!omap"`. Note that this requires the corresponding tag to be available based on the default options. To use a specific document's schema, use the Document method `createNode(value, options?)`.
To specify the collection type, set `options.tag` to its identifying string, e.g. `"!!omap"`. Note that this requires the corresponding tag to be available in the document's schema. If `options.wrapScalars` is undefined or `true`, plain values are wrapped in `Scalar` objects.

The primary purpose of this function is to enable attaching comments or other metadata to a value, or to otherwise exert more fine-grained control over the stringified output. To that end, you'll need to assign its return value to the `contents` of a Document (or somewhere within said contents), as the document's schema is required for YAML string output.
As a possible side effect, this method may add entries to the document's [`anchors`](#working-with-anchors)

<h4 style="clear:both"><code>new Map(), new Seq(), new Pair(key, value)</code></h4>
The primary purpose of this method is to enable attaching comments or other metadata to a value, or to otherwise exert more fine-grained control over the stringified output. To that end, you'll need to assign its return value to the `contents` of a document (or somewhere within said contents), as the document's schema is required for YAML string output. If you're not interested in working with such metadata, document `contents` may also include non-`Node` values at any level.

<h4 style="clear:both"><code>new YAMLMap(), new YAMLSeq(), doc.createPair(key, value)</code></h4>

```js
import YAML from 'yaml'
import { Pair, YAMLSeq } from 'yaml/types'
import { YAMLSeq } from 'yaml/types'

const doc = new YAML.Document()
doc.contents = new YAMLSeq()
const doc = new YAML.Document(new YAMLSeq())
doc.contents.items = [
'some values',
42,
{ including: 'objects', 3: 'a string' }
]
doc.contents.items.push(new Pair(1, 'a number'))
doc.add(doc.createPair(1, 'a number'))

doc.toString()
// - some values
Expand All @@ -192,9 +197,9 @@ doc.toString()
// - 1: a number
```

To construct a `YAMLSeq` or `YAMLMap`, use [`YAML.createNode()`](#yaml-createnode) with array, object or iterable input, or create the collections directly by importing the classes from `yaml/types`.
To construct a `YAMLSeq` or `YAMLMap`, use `doc.createNode()` with array, object or iterable input, or create the collections directly by importing the classes from `yaml/types`.

Once created, normal array operations may be used to modify the `items` array. New `Pair` objects may created either by importing the class from `yaml/types` and using its `new Pair(key, value)` constructor, or by using the `doc.createPair(key, value)` method. The latter will recursively wrap the `key` and `value` as nodes.
Once created, normal array operations may be used to modify the `items` array. New `Pair` objects may created either by importing the class from `yaml/types` and using its `new Pair(key, value)` constructor, or by using the `doc.createPair(key, value, options?)` method. The latter will recursively wrap the `key` and `value` as nodes, and accepts the same options as `doc.createNode()`

## Comments

Expand Down

0 comments on commit b101145

Please sign in to comment.