Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking changes to Document & createNode APIs #186

Merged
merged 5 commits into from Aug 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions 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(options)`](#creating-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
50 changes: 27 additions & 23 deletions docs/04_documents.md
Expand Up @@ -61,7 +61,9 @@ The `contents` of a parsed document will always consist of `Scalar`, `Map`, `Seq

## Creating Documents

#### `new YAML.Document(options = {})`
#### `new YAML.Document(value, options = {})`

Creates a new document. If `value` is defined, the document `contents` are initialised with that value, wrapped recursively in appropriate [content nodes](#content-nodes). If `value` is `undefined`, the document's `contents` and `schema` are initialised as `null`. See [Options](#options) for more information on the second parameter.

| Member | Type | Description |
| ------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
Expand All @@ -77,10 +79,9 @@ The `contents` of a parsed document will always consist of `Scalar`, `Map`, `Seq
| warnings | `Error[]` | Warnings encountered during parsing. |

```js
const doc = new YAML.Document()
const doc = new YAML.Document(['some', 'values', { balloons: 99 }])
doc.version = true
doc.commentBefore = ' A commented document'
doc.contents = ['some', 'values', { balloons: 99 }]

String(doc)
// # A commented document
Expand All @@ -97,14 +98,16 @@ During stringification, a document with a true-ish `version` value will include

## Document Methods

| Method | Returns | Description |
| ---------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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` | When a document is created with `new YAML.Document()`, the schema object is not set as it may be influenced by parsed directives; call this with no arguments to set it manually, or with arguments to change 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 @@ -160,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 wrapped method `doc.schema.createNode(value, wrapScalars, tag)`.
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.schema.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