-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmod.js
119 lines (90 loc) · 3.23 KB
/
mod.js
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
import {jevkoFromString as parseJevkoWithHeredocs} from './deps.js'
import {fromJsonStr} from './fromJsonStr.js'
import {jevkoToPrettyString} from './jevkoToPrettyString.js'
export const prettyFromJsonStr = str => jevkoToPrettyString(parseJevkoWithHeredocs(fromJsonStr(str)))
export const jevkodata = (jevko, props) => {
// todo:
// if (props.version)
if (props.pretty === true || (Array.isArray(props.flags) && props.flags.includes('pretty'))) {
return JSON.stringify(convert(jevko), null, 2)
}
return JSON.stringify(convert(jevko))
}
export const fromString = (str) => convert(parseJevkoWithHeredocs(str))
export const convert = (jevko) => inner(prep(jevko))
export const prep = jevko => {
// todo: perhaps this should trim suffixes as well?
const {subjevkos, ...rest} = jevko
const subs = []
for (const {prefix, jevko} of subjevkos) {
const trimmed = prefix.trim()
let key
// note: support for ' keys '
if (trimmed.startsWith("'")) {
key = trimmed
} else {
// todo: support ' keys ' preceded by comment lines
// todo: configurable linebreak
const lines = prefix.split('\n')
// discard all lines but last:
key = lines.at(-1).trim()
// discard all pairs that have name starting with -
if (key.startsWith('-')) continue
}
subs.push({prefix: key, jevko: prep(jevko)})
}
return {subjevkos: subs, ...rest}
}
const inner = (jevko) => {
const {subjevkos, suffix} = jevko
if (subjevkos.length === 0) {
const {tag} = jevko
if (tag === 'json') return JSON.parse(suffix)
// note: other tags make raw heredoc strings -- untrimmed
else if (tag !== undefined) return suffix
const trimmed = suffix.trim()
if (trimmed.startsWith("'")) {
// note: allow unclosed string literals
if (trimmed.at(-1) === "'") return trimmed.slice(1, -1)
return trimmed.slice(1)
}
if (trimmed === '') return trimmed
if (trimmed === 'true') return true
if (trimmed === 'false') return false
if (trimmed === 'null' || trimmed === "nil") return null
if (trimmed === 'map') return Object.create(null)
if (trimmed === 'list' || trimmed === "seq") return []
if (trimmed === 'NaN') return NaN
const num = Number(trimmed)
if (Number.isNaN(num) === false) return num
return trimmed
}
if (suffix.trim() !== '') throw Error(`Expected blank suffix, was: ${suffix}`)
const sub0 = subjevkos[0]
if (sub0.prefix === '') return list(subjevkos)
return map(subjevkos)
}
export const list = subjevkos => {
const ret = []
for (const {prefix, jevko} of subjevkos) {
if (prefix !== '') throw Error(`Nonblank prefix in list: ${prefix}`)
ret.push(inner(jevko))
}
return ret
}
export const map = subjevkos => {
const ret = Object.create(null)
for (const {prefix, jevko} of subjevkos) {
if (prefix === '') throw Error(`Blank prefix in map: ${prefix}`)
let key
//?todo: extract & dedupe w/ inner
if (prefix.startsWith("'")) {
// note: allow unclosed string literals
if (prefix.at(-1) === "'") key = prefix.slice(1, -1)
else key = prefix.slice(1)
} else key = prefix
if (key in ret) throw Error(`Duplicate key '${key}'`)
ret[key] = inner(jevko)
}
return ret
}