-
-
Notifications
You must be signed in to change notification settings - Fork 120
/
Copy pathYAMLMap.ts
172 lines (157 loc) · 5.53 KB
/
YAMLMap.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import type { BlockMap, FlowCollection } from '../parse/cst.js'
import type { Schema } from '../schema/Schema.js'
import type { StringifyContext } from '../stringify/stringify.js'
import { stringifyCollection } from '../stringify/stringifyCollection.js'
import { CreateNodeContext } from '../util.js'
import { addPairToJSMap } from './addPairToJSMap.js'
import { Collection } from './Collection.js'
import { isPair, isScalar, MAP } from './identity.js'
import type { ParsedNode, Range } from './Node.js'
import { createPair, Pair } from './Pair.js'
import { isScalarValue, Scalar } from './Scalar.js'
import type { ToJSContext } from './toJS.js'
export type MapLike =
| Map<unknown, unknown>
| Set<unknown>
| Record<string | number | symbol, unknown>
export function findPair<K = unknown, V = unknown>(
items: Iterable<Pair<K, V>>,
key: unknown
) {
const k = isScalar(key) ? key.value : key
for (const it of items) {
if (isPair(it)) {
if (it.key === key || it.key === k) return it
if (isScalar(it.key) && it.key.value === k) return it
}
}
return undefined
}
export declare namespace YAMLMap {
interface Parsed<
K extends ParsedNode = ParsedNode,
V extends ParsedNode | null = ParsedNode | null
> extends YAMLMap<K, V> {
items: Pair<K, V>[]
range: Range
srcToken?: BlockMap | FlowCollection
}
}
export class YAMLMap<K = unknown, V = unknown> extends Collection {
static get tagName(): 'tag:yaml.org,2002:map' {
return 'tag:yaml.org,2002:map'
}
items: Pair<K, V>[] = []
constructor(schema?: Schema) {
super(MAP, schema)
}
/**
* A generic collection parsing method that can be extended
* to other node classes that inherit from YAMLMap
*/
static from(schema: Schema, obj: unknown, ctx: CreateNodeContext) {
const { keepUndefined, replacer } = ctx
const map = new this(schema)
const add = (key: unknown, value: unknown) => {
if (typeof replacer === 'function') value = replacer.call(obj, key, value)
else if (Array.isArray(replacer) && !replacer.includes(key)) return
if (value !== undefined || keepUndefined)
map.items.push(createPair(key, value, ctx))
}
if (obj instanceof Map) {
for (const [key, value] of obj) add(key, value)
} else if (obj && typeof obj === 'object') {
for (const key of Object.keys(obj)) add(key, (obj as any)[key])
}
if (typeof schema.sortMapEntries === 'function') {
map.items.sort(schema.sortMapEntries)
}
return map
}
/**
* Adds a value to the collection.
*
* @param overwrite - If not set `true`, using a key that is already in the
* collection will throw. Otherwise, overwrites the previous value.
*/
add(pair: Pair<K, V> | { key: K; value: V }, overwrite?: boolean): void {
let _pair: Pair<K, V>
if (isPair(pair)) _pair = pair
else if (!pair || typeof pair !== 'object' || !('key' in pair)) {
// In TypeScript, this never happens.
_pair = new Pair<K, V>(pair as any, (pair as any)?.value)
} else _pair = new Pair(pair.key, pair.value)
const prev = findPair(this.items, _pair.key)
const sortEntries = this.schema?.sortMapEntries
if (prev) {
if (!overwrite) throw new Error(`Key ${_pair.key} already set`)
// For scalars, keep the old node & its comments and anchors
if (isScalar(prev.value) && isScalarValue(_pair.value))
prev.value.value = _pair.value
else prev.value = _pair.value
} else if (sortEntries) {
const i = this.items.findIndex(item => sortEntries(_pair, item) < 0)
if (i === -1) this.items.push(_pair)
else this.items.splice(i, 0, _pair)
} else {
this.items.push(_pair)
}
}
delete(key: unknown): boolean {
const it = findPair(this.items, key)
if (!it) return false
const del = this.items.splice(this.items.indexOf(it), 1)
return del.length > 0
}
get(key: unknown, keepScalar: true): Scalar<V> | undefined
get(key: unknown, keepScalar?: false): V | undefined
get(key: unknown, keepScalar?: boolean): V | Scalar<V> | undefined
get(key: unknown, keepScalar?: boolean): V | Scalar<V> | undefined {
const it = findPair(this.items, key)
const node = it?.value
return (!keepScalar && isScalar<V>(node) ? node.value : node) ?? undefined
}
has(key: unknown): boolean {
return !!findPair(this.items, key)
}
set(key: K, value: V): void {
this.add(new Pair(key, value), true)
}
/**
* @param ctx - Conversion context, originally set in Document#toJS()
* @param {Class} Type - If set, forces the returned collection type
* @returns Instance of Type, Map, or Object
*/
toJSON<T extends MapLike = Map<unknown, unknown>>(
_?: unknown,
ctx?: ToJSContext,
Type?: { new (): T }
): any {
const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {}
if (ctx?.onCreate) ctx.onCreate(map)
for (const item of this.items) addPairToJSMap(ctx, map, item)
return map
}
toString(
ctx?: StringifyContext,
onComment?: () => void,
onChompKeep?: () => void
): string {
if (!ctx) return JSON.stringify(this)
for (const item of this.items) {
if (!isPair(item))
throw new Error(
`Map items must all be pairs; found ${JSON.stringify(item)} instead`
)
}
if (!ctx.allNullValues && this.hasAllNullValues(false))
ctx = Object.assign({}, ctx, { allNullValues: true })
return stringifyCollection(this, ctx, {
blockItemPrefix: '',
flowChars: { start: '{', end: '}' },
itemIndent: ctx.indent || '',
onChompKeep,
onComment
})
}
}