1 | 'use strict';
|
2 |
|
3 | var identity = require('../nodes/identity.js');
|
4 | var visit = require('../visit.js');
|
5 |
|
6 | const escapeChars = {
|
7 | '!': '%21',
|
8 | ',': '%2C',
|
9 | '[': '%5B',
|
10 | ']': '%5D',
|
11 | '{': '%7B',
|
12 | '}': '%7D'
|
13 | };
|
14 | const escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, ch => escapeChars[ch]);
|
15 | class Directives {
|
16 | constructor(yaml, tags) {
|
17 | |
18 |
|
19 |
|
20 |
|
21 | this.docStart = null;
|
22 |
|
23 | this.docEnd = false;
|
24 | this.yaml = Object.assign({}, Directives.defaultYaml, yaml);
|
25 | this.tags = Object.assign({}, Directives.defaultTags, tags);
|
26 | }
|
27 | clone() {
|
28 | const copy = new Directives(this.yaml, this.tags);
|
29 | copy.docStart = this.docStart;
|
30 | return copy;
|
31 | }
|
32 | |
33 |
|
34 |
|
35 |
|
36 | atDocument() {
|
37 | const res = new Directives(this.yaml, this.tags);
|
38 | switch (this.yaml.version) {
|
39 | case '1.1':
|
40 | this.atNextDocument = true;
|
41 | break;
|
42 | case '1.2':
|
43 | this.atNextDocument = false;
|
44 | this.yaml = {
|
45 | explicit: Directives.defaultYaml.explicit,
|
46 | version: '1.2'
|
47 | };
|
48 | this.tags = Object.assign({}, Directives.defaultTags);
|
49 | break;
|
50 | }
|
51 | return res;
|
52 | }
|
53 | |
54 |
|
55 |
|
56 |
|
57 | add(line, onError) {
|
58 | if (this.atNextDocument) {
|
59 | this.yaml = { explicit: Directives.defaultYaml.explicit, version: '1.1' };
|
60 | this.tags = Object.assign({}, Directives.defaultTags);
|
61 | this.atNextDocument = false;
|
62 | }
|
63 | const parts = line.trim().split(/[ \t]+/);
|
64 | const name = parts.shift();
|
65 | switch (name) {
|
66 | case '%TAG': {
|
67 | if (parts.length !== 2) {
|
68 | onError(0, '%TAG directive should contain exactly two parts');
|
69 | if (parts.length < 2)
|
70 | return false;
|
71 | }
|
72 | const [handle, prefix] = parts;
|
73 | this.tags[handle] = prefix;
|
74 | return true;
|
75 | }
|
76 | case '%YAML': {
|
77 | this.yaml.explicit = true;
|
78 | if (parts.length !== 1) {
|
79 | onError(0, '%YAML directive should contain exactly one part');
|
80 | return false;
|
81 | }
|
82 | const [version] = parts;
|
83 | if (version === '1.1' || version === '1.2') {
|
84 | this.yaml.version = version;
|
85 | return true;
|
86 | }
|
87 | else {
|
88 | const isValid = /^\d+\.\d+$/.test(version);
|
89 | onError(6, `Unsupported YAML version ${version}`, isValid);
|
90 | return false;
|
91 | }
|
92 | }
|
93 | default:
|
94 | onError(0, `Unknown directive ${name}`, true);
|
95 | return false;
|
96 | }
|
97 | }
|
98 | |
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | tagName(source, onError) {
|
105 | if (source === '!')
|
106 | return '!';
|
107 | if (source[0] !== '!') {
|
108 | onError(`Not a valid tag: ${source}`);
|
109 | return null;
|
110 | }
|
111 | if (source[1] === '<') {
|
112 | const verbatim = source.slice(2, -1);
|
113 | if (verbatim === '!' || verbatim === '!!') {
|
114 | onError(`Verbatim tags aren't resolved, so ${source} is invalid.`);
|
115 | return null;
|
116 | }
|
117 | if (source[source.length - 1] !== '>')
|
118 | onError('Verbatim tags must end with a >');
|
119 | return verbatim;
|
120 | }
|
121 | const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s);
|
122 | if (!suffix)
|
123 | onError(`The ${source} tag has no suffix`);
|
124 | const prefix = this.tags[handle];
|
125 | if (prefix) {
|
126 | try {
|
127 | return prefix + decodeURIComponent(suffix);
|
128 | }
|
129 | catch (error) {
|
130 | onError(String(error));
|
131 | return null;
|
132 | }
|
133 | }
|
134 | if (handle === '!')
|
135 | return source;
|
136 | onError(`Could not resolve tag: ${source}`);
|
137 | return null;
|
138 | }
|
139 | |
140 |
|
141 |
|
142 |
|
143 | tagString(tag) {
|
144 | for (const [handle, prefix] of Object.entries(this.tags)) {
|
145 | if (tag.startsWith(prefix))
|
146 | return handle + escapeTagName(tag.substring(prefix.length));
|
147 | }
|
148 | return tag[0] === '!' ? tag : `!<${tag}>`;
|
149 | }
|
150 | toString(doc) {
|
151 | const lines = this.yaml.explicit
|
152 | ? [`%YAML ${this.yaml.version || '1.2'}`]
|
153 | : [];
|
154 | const tagEntries = Object.entries(this.tags);
|
155 | let tagNames;
|
156 | if (doc && tagEntries.length > 0 && identity.isNode(doc.contents)) {
|
157 | const tags = {};
|
158 | visit.visit(doc.contents, (_key, node) => {
|
159 | if (identity.isNode(node) && node.tag)
|
160 | tags[node.tag] = true;
|
161 | });
|
162 | tagNames = Object.keys(tags);
|
163 | }
|
164 | else
|
165 | tagNames = [];
|
166 | for (const [handle, prefix] of tagEntries) {
|
167 | if (handle === '!!' && prefix === 'tag:yaml.org,2002:')
|
168 | continue;
|
169 | if (!doc || tagNames.some(tn => tn.startsWith(prefix)))
|
170 | lines.push(`%TAG ${handle} ${prefix}`);
|
171 | }
|
172 | return lines.join('\n');
|
173 | }
|
174 | }
|
175 | Directives.defaultYaml = { explicit: false, version: '1.2' };
|
176 | Directives.defaultTags = { '!!': 'tag:yaml.org,2002:' };
|
177 |
|
178 | exports.Directives = Directives;
|