dsh-plugin-prompt-tool 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -222
- package/engine/anchor-match.mjs +117 -0
- package/engine/compaction-epoch.mjs +139 -0
- package/engine/compositions/library/bootstrap-filesystem.yml +19 -0
- package/engine/compositions/library/compaction.yml +52 -0
- package/engine/compositions/library/context-gate.yml +40 -0
- package/engine/compositions/library/custom-bash.yml +14 -0
- package/engine/compositions/library/delegation.yml +85 -0
- package/engine/compositions/library/official-agent-instructions.yml +17 -0
- package/engine/compositions/library/official-persistent-shell.yml +56 -0
- package/engine/compositions/library/official-skill-filesystem-cordis.yml +10 -0
- package/engine/compositions/library/official-tool-bash.yml +6 -0
- package/engine/compositions/library/official-tool-cordis.yml +13 -0
- package/engine/compositions/library/official-tool-presentation.yml +7 -0
- package/engine/compositions/library/official-tool-skill.yml +14 -0
- package/engine/compositions/library/persistent-shell.yml +36 -0
- package/engine/compositions/library/persona.yml +9 -0
- package/engine/compositions/library/planning.yml +41 -0
- package/engine/compositions/library/prompt-config-engine.yml +16 -0
- package/engine/compositions/library/run-code-env.yml +15 -0
- package/engine/compositions/library/skill-filesystem.yml +13 -0
- package/engine/compositions/library/skill-search.yml +14 -0
- package/engine/compositions/library/str-replace-editor.yml +10 -0
- package/engine/compositions/library/tool-ask-user.yml +8 -0
- package/engine/compositions/library/tool-bash.yml +16 -0
- package/engine/compositions/library/tool-bootstrap.yml +16 -0
- package/engine/compositions/library/tool-filter.yml +12 -0
- package/engine/compositions/library/tool-fs-search.yml +18 -0
- package/engine/compositions/library/tool-fs.yml +10 -0
- package/engine/compositions/library/tool-goal.yml +19 -0
- package/engine/compositions/library/tool-jobs.yml +23 -0
- package/engine/compositions/library/tool-pwsh.yml +12 -0
- package/engine/compositions/library/tool-todo.yml +11 -0
- package/engine/compositions/library/tool-web.yml +9 -0
- package/engine/compositions/source/local/context-gate.yml +37 -0
- package/engine/compositions/source/local/custom-bash.yml +11 -0
- package/engine/compositions/source/local/prompt-config-engine.yml +13 -0
- package/engine/compositions/source/local/run-code-env.yml +12 -0
- package/engine/compositions/source/local/skill-search.yml +11 -0
- package/engine/compositions/source/local/tool-bootstrap.yml +13 -0
- package/engine/context-gate.mjs +305 -0
- package/{preset → engine}/custom-bash.mjs +243 -243
- package/engine/executor.mjs +278 -0
- package/engine/fillers.mjs +273 -0
- package/engine/interpolate.mjs +66 -0
- package/engine/layers.mjs +224 -0
- package/engine/prompt-config-engine.mjs +49 -0
- package/engine/run-code-env.mjs +208 -0
- package/engine/schema.mjs +312 -0
- package/engine/session-vars.mjs +52 -0
- package/{preset → engine}/shared.mjs +35 -0
- package/engine/strategies.mjs +213 -0
- package/{preset → engine}/tool-bootstrap.mjs +350 -282
- package/engine/tool-filter.mjs +80 -0
- package/engine/vendor/yaml/LICENSE +13 -0
- package/engine/vendor/yaml/dist/compose/compose-collection.js +88 -0
- package/engine/vendor/yaml/dist/compose/compose-doc.js +43 -0
- package/engine/vendor/yaml/dist/compose/compose-node.js +109 -0
- package/engine/vendor/yaml/dist/compose/compose-scalar.js +86 -0
- package/engine/vendor/yaml/dist/compose/composer.js +219 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-map.js +115 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-scalar.js +198 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-seq.js +49 -0
- package/engine/vendor/yaml/dist/compose/resolve-end.js +37 -0
- package/engine/vendor/yaml/dist/compose/resolve-flow-collection.js +207 -0
- package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +225 -0
- package/engine/vendor/yaml/dist/compose/resolve-props.js +146 -0
- package/engine/vendor/yaml/dist/compose/util-contains-newline.js +34 -0
- package/engine/vendor/yaml/dist/compose/util-empty-scalar-position.js +26 -0
- package/engine/vendor/yaml/dist/compose/util-flow-indent-check.js +15 -0
- package/engine/vendor/yaml/dist/compose/util-map-includes.js +13 -0
- package/engine/vendor/yaml/dist/doc/Document.js +335 -0
- package/engine/vendor/yaml/dist/doc/anchors.js +71 -0
- package/engine/vendor/yaml/dist/doc/applyReviver.js +55 -0
- package/engine/vendor/yaml/dist/doc/createNode.js +88 -0
- package/engine/vendor/yaml/dist/doc/directives.js +176 -0
- package/engine/vendor/yaml/dist/errors.js +57 -0
- package/engine/vendor/yaml/dist/index.js +17 -0
- package/engine/vendor/yaml/dist/log.js +11 -0
- package/engine/vendor/yaml/dist/nodes/Alias.js +116 -0
- package/engine/vendor/yaml/dist/nodes/Collection.js +147 -0
- package/engine/vendor/yaml/dist/nodes/Node.js +38 -0
- package/engine/vendor/yaml/dist/nodes/Pair.js +36 -0
- package/engine/vendor/yaml/dist/nodes/Scalar.js +24 -0
- package/engine/vendor/yaml/dist/nodes/YAMLMap.js +144 -0
- package/engine/vendor/yaml/dist/nodes/YAMLSeq.js +113 -0
- package/engine/vendor/yaml/dist/nodes/addPairToJSMap.js +63 -0
- package/engine/vendor/yaml/dist/nodes/identity.js +36 -0
- package/engine/vendor/yaml/dist/nodes/toJS.js +37 -0
- package/engine/vendor/yaml/dist/parse/cst-scalar.js +214 -0
- package/engine/vendor/yaml/dist/parse/cst-stringify.js +61 -0
- package/engine/vendor/yaml/dist/parse/cst-visit.js +97 -0
- package/engine/vendor/yaml/dist/parse/cst.js +98 -0
- package/engine/vendor/yaml/dist/parse/lexer.js +721 -0
- package/engine/vendor/yaml/dist/parse/line-counter.js +39 -0
- package/engine/vendor/yaml/dist/parse/parser.js +975 -0
- package/engine/vendor/yaml/dist/public-api.js +102 -0
- package/engine/vendor/yaml/dist/schema/Schema.js +37 -0
- package/engine/vendor/yaml/dist/schema/common/map.js +17 -0
- package/engine/vendor/yaml/dist/schema/common/null.js +15 -0
- package/engine/vendor/yaml/dist/schema/common/seq.js +17 -0
- package/engine/vendor/yaml/dist/schema/common/string.js +14 -0
- package/engine/vendor/yaml/dist/schema/core/bool.js +19 -0
- package/engine/vendor/yaml/dist/schema/core/float.js +43 -0
- package/engine/vendor/yaml/dist/schema/core/int.js +38 -0
- package/engine/vendor/yaml/dist/schema/core/schema.js +23 -0
- package/engine/vendor/yaml/dist/schema/json/schema.js +62 -0
- package/engine/vendor/yaml/dist/schema/tags.js +96 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/binary.js +58 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/bool.js +26 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/float.js +46 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/int.js +71 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/merge.js +67 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/omap.js +74 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/pairs.js +78 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/schema.js +39 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/set.js +93 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/engine/vendor/yaml/dist/stringify/foldFlowLines.js +146 -0
- package/engine/vendor/yaml/dist/stringify/stringify.js +129 -0
- package/engine/vendor/yaml/dist/stringify/stringifyCollection.js +153 -0
- package/engine/vendor/yaml/dist/stringify/stringifyComment.js +20 -0
- package/engine/vendor/yaml/dist/stringify/stringifyDocument.js +85 -0
- package/engine/vendor/yaml/dist/stringify/stringifyNumber.js +25 -0
- package/engine/vendor/yaml/dist/stringify/stringifyPair.js +150 -0
- package/engine/vendor/yaml/dist/stringify/stringifyString.js +336 -0
- package/engine/vendor/yaml/dist/util.js +11 -0
- package/engine/vendor/yaml/dist/visit.js +233 -0
- package/engine/vendor/yaml/index.js +5 -0
- package/engine/vendor/yaml/package.json +11 -0
- package/lib/client.js +4724 -775
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +530 -51
- package/lib/index.mjs +4164 -740
- package/lib/preset-core.d.mts +7 -37
- package/lib/preset-core.mjs +43 -285
- package/lib/prompt-configs-B4vH09wx.d.mts +100 -0
- package/lib/prompt-configs-ThS4iXPg.mjs +771 -0
- package/package.json +36 -29
- package/preset/anchored/preset.yml +342 -0
- package/preset/creative/preset.yml +65 -0
- package/preset/creative/skills/cordis-plugin-development/SKILL.md +420 -0
- package/preset/creative/skills/editing-cordis-compositions/SKILL.md +165 -0
- package/preset/custom/preset.yml +13 -0
- package/preset/liangshen/preset.yml +72 -0
- package/preset/minimal/preset.yml +44 -0
- package/preset/ptc/preset.yml +56 -0
- package/preset/standard/preset.yml +55 -0
- package/skills/manifest.json +7 -0
- package/skills/sandboxmod/SKILL.md +49 -49
- package/skills/web ui/SKILL.md +42 -0
- package/templates/10-pre-step.yml +28 -0
- package/templates/11-merged-a.yml +11 -0
- package/templates/13-anchor.yml +19 -0
- package/templates/14-first-turn-anchor.yml +27 -0
- package/templates/15-guide-auto.yml +25 -0
- package/templates/16-custom-fallback.yml +21 -0
- package/templates/17-instruction-hint.yml +23 -0
- package/templates/18-placeholder-env-facts.yml +11 -0
- package/templates/19-placeholder-skill-catalog.yml +19 -0
- package/templates/20-system-section.yml +19 -0
- package/templates/30-runtime-context.yml +10 -0
- package/templates/31-runtime-context-placeholder.yml +18 -0
- package/templates/40-agent-request.yml +11 -0
- package/templates/50-llm-stream.yml +9 -0
- package/templates/60-tool-pipeline.yml +12 -0
- package/AGENTS.md +0 -4
- package/plan.md +0 -312
- package/preset/agent.cordis.yml +0 -443
- package/preset/compaction-epoch.mjs +0 -81
- package/preset/context-gate.mjs +0 -165
- package/preset/instruction-hint.mjs +0 -217
- package/preset/near-anchor.mjs +0 -101
- package/preset/preset.yml +0 -3
- package/preset/prompt-injector.mjs +0 -112
- package/preset/router-first-turn.mjs +0 -73
- package/preset/router-guide.mjs +0 -79
- package/preset.md +0 -115
- package/upstream/dsh-anchored-standard/LICENSE +0 -22
- package/upstream/dsh-anchored-standard/NOTICE +0 -19
- package/upstream/dsh-anchored-standard/REVISION +0 -1
- package/upstream/dsh-anchored-standard/preset/agent.cordis.yml +0 -440
- package/upstream/dsh-anchored-standard/preset/compaction-epoch.mjs +0 -81
- package/upstream/dsh-anchored-standard/preset/context-gate.mjs +0 -202
- package/upstream/dsh-anchored-standard/preset/custom-bash.mjs +0 -219
- package/upstream/dsh-anchored-standard/preset/dev-tool-search.mjs +0 -131
- package/upstream/dsh-anchored-standard/preset/instruction-hint.mjs +0 -231
- package/upstream/dsh-anchored-standard/preset/preset.yml +0 -3
- package/upstream/dsh-anchored-standard/preset/skill-search.mjs +0 -142
- package/upstream/dsh-anchored-standard/preset/tool-bootstrap.mjs +0 -301
- /package/{preset → engine}/skill-search.mjs +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main-session tool filter — apply a persistent allow/deny mask to the
|
|
3
|
+
* assembled tool catalog, covering EVERY registered tool (including tools
|
|
4
|
+
* contributed by arbitrary third-party plugins).
|
|
5
|
+
*
|
|
6
|
+
* Unlike `tool-bootstrap` (phase-1 bootstrap narrowing that restores the full
|
|
7
|
+
* catalog after promotion), this filter is CONSTANT: it runs on every
|
|
8
|
+
* `system-prompt/assemble` and keeps the mask applied before and after
|
|
9
|
+
* promotion, so main-session tool restrictions never disappear.
|
|
10
|
+
*
|
|
11
|
+
* Config:
|
|
12
|
+
* - `allow`: non-empty keep-set. When present, ONLY tools whose name is in
|
|
13
|
+
* this list survive (white-list); `deny` is then subtracted from it.
|
|
14
|
+
* - `deny`: non-empty remove-set. Tools whose name is in this list are
|
|
15
|
+
* dropped regardless of `allow`.
|
|
16
|
+
* - `includeSubagents`: true = the same mask applies to delegated
|
|
17
|
+
* sub-agents; false (default) = main session only, sub-agents keep the
|
|
18
|
+
* assembled catalog (they already have their own toolFilter{allow,deny}
|
|
19
|
+
* in the tool-subagent row).
|
|
20
|
+
*
|
|
21
|
+
* Both lists empty = no filtering (official default), zero overhead.
|
|
22
|
+
* Failure degrades to the full catalog — a filter bug must never brick a
|
|
23
|
+
* session (same discipline as tool-bootstrap).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { booleanOption, createWarnOnce, validateConfig } from './shared.mjs'
|
|
27
|
+
|
|
28
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
29
|
+
export const name = 'anchored-tool-filter'
|
|
30
|
+
|
|
31
|
+
export const inject = []
|
|
32
|
+
|
|
33
|
+
/** Every config key this plugin accepts — anything else is a typo. */
|
|
34
|
+
const ALLOWED_KEYS = new Set(['allow', 'deny', 'includeSubagents', 'enabled'])
|
|
35
|
+
|
|
36
|
+
/** Non-empty string list validator (returns a Set). */
|
|
37
|
+
function nameSet(value, field) {
|
|
38
|
+
if (value === undefined) return undefined
|
|
39
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.length === 0)) {
|
|
40
|
+
throw new TypeError(`${name}: ${field} must be an array of non-empty strings`)
|
|
41
|
+
}
|
|
42
|
+
return new Set(value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Apply the mask to one assembled catalog. */
|
|
46
|
+
function applyMask(assembled, allow, deny) {
|
|
47
|
+
if (allow === undefined && deny === undefined) return assembled
|
|
48
|
+
const tools = (Array.isArray(assembled.tools) ? assembled.tools : [])
|
|
49
|
+
.filter((tool) => {
|
|
50
|
+
const toolName = typeof tool?.name === 'string' ? tool.name : ''
|
|
51
|
+
if (toolName.length === 0) return true
|
|
52
|
+
if (deny !== undefined && deny.has(toolName)) return false
|
|
53
|
+
if (allow !== undefined && !allow.has(toolName)) return false
|
|
54
|
+
return true
|
|
55
|
+
})
|
|
56
|
+
return { ...assembled, tools }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Register the per-assembly main-session tool mask. */
|
|
60
|
+
export function apply(ctx, config) {
|
|
61
|
+
const source = validateConfig(name, config, ALLOWED_KEYS)
|
|
62
|
+
if (source.enabled === false) return
|
|
63
|
+
const allow = nameSet(source.allow, 'allow')
|
|
64
|
+
const deny = nameSet(source.deny, 'deny')
|
|
65
|
+
const includeSubagents = booleanOption(name, source.includeSubagents, 'includeSubagents', false)
|
|
66
|
+
const warnOnce = createWarnOnce(ctx, name)
|
|
67
|
+
|
|
68
|
+
ctx.on('system-prompt/assemble', async (assembly, context, next) => {
|
|
69
|
+
const assembled = await next()
|
|
70
|
+
try {
|
|
71
|
+
const agent = context?.agent
|
|
72
|
+
const depth = agent?.session?.header?.delegationDepth ?? 0
|
|
73
|
+
if (depth > 0 && !includeSubagents) return assembled
|
|
74
|
+
return applyMask(assembled, allow, deny)
|
|
75
|
+
} catch (error) {
|
|
76
|
+
warnOnce(`${name}: filter failed, exposing the full catalog: ${String((error && error.message) || error)}`)
|
|
77
|
+
return assembled
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright Eemeli Aro <eemeli@gmail.com>
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
|
4
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
|
5
|
+
and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
8
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
9
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
10
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
11
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
12
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
13
|
+
THIS SOFTWARE.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { isNode } from '../nodes/identity.js';
|
|
2
|
+
import { Scalar } from '../nodes/Scalar.js';
|
|
3
|
+
import { YAMLMap } from '../nodes/YAMLMap.js';
|
|
4
|
+
import { YAMLSeq } from '../nodes/YAMLSeq.js';
|
|
5
|
+
import { resolveBlockMap } from './resolve-block-map.js';
|
|
6
|
+
import { resolveBlockSeq } from './resolve-block-seq.js';
|
|
7
|
+
import { resolveFlowCollection } from './resolve-flow-collection.js';
|
|
8
|
+
|
|
9
|
+
function resolveCollection(CN, ctx, token, onError, tagName, tag) {
|
|
10
|
+
const coll = token.type === 'block-map'
|
|
11
|
+
? resolveBlockMap(CN, ctx, token, onError, tag)
|
|
12
|
+
: token.type === 'block-seq'
|
|
13
|
+
? resolveBlockSeq(CN, ctx, token, onError, tag)
|
|
14
|
+
: resolveFlowCollection(CN, ctx, token, onError, tag);
|
|
15
|
+
const Coll = coll.constructor;
|
|
16
|
+
// If we got a tagName matching the class, or the tag name is '!',
|
|
17
|
+
// then use the tagName from the node class used to create it.
|
|
18
|
+
if (tagName === '!' || tagName === Coll.tagName) {
|
|
19
|
+
coll.tag = Coll.tagName;
|
|
20
|
+
return coll;
|
|
21
|
+
}
|
|
22
|
+
if (tagName)
|
|
23
|
+
coll.tag = tagName;
|
|
24
|
+
return coll;
|
|
25
|
+
}
|
|
26
|
+
function composeCollection(CN, ctx, token, props, onError) {
|
|
27
|
+
const tagToken = props.tag;
|
|
28
|
+
const tagName = !tagToken
|
|
29
|
+
? null
|
|
30
|
+
: ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg));
|
|
31
|
+
if (token.type === 'block-seq') {
|
|
32
|
+
const { anchor, newlineAfterProp: nl } = props;
|
|
33
|
+
const lastProp = anchor && tagToken
|
|
34
|
+
? anchor.offset > tagToken.offset
|
|
35
|
+
? anchor
|
|
36
|
+
: tagToken
|
|
37
|
+
: (anchor ?? tagToken);
|
|
38
|
+
if (lastProp && (!nl || nl.offset < lastProp.offset)) {
|
|
39
|
+
const message = 'Missing newline after block sequence props';
|
|
40
|
+
onError(lastProp, 'MISSING_CHAR', message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const expType = token.type === 'block-map'
|
|
44
|
+
? 'map'
|
|
45
|
+
: token.type === 'block-seq'
|
|
46
|
+
? 'seq'
|
|
47
|
+
: token.start.source === '{'
|
|
48
|
+
? 'map'
|
|
49
|
+
: 'seq';
|
|
50
|
+
// shortcut: check if it's a generic YAMLMap or YAMLSeq
|
|
51
|
+
// before jumping into the custom tag logic.
|
|
52
|
+
if (!tagToken ||
|
|
53
|
+
!tagName ||
|
|
54
|
+
tagName === '!' ||
|
|
55
|
+
(tagName === YAMLMap.tagName && expType === 'map') ||
|
|
56
|
+
(tagName === YAMLSeq.tagName && expType === 'seq')) {
|
|
57
|
+
return resolveCollection(CN, ctx, token, onError, tagName);
|
|
58
|
+
}
|
|
59
|
+
let tag = ctx.schema.tags.find(t => t.tag === tagName && t.collection === expType);
|
|
60
|
+
if (!tag) {
|
|
61
|
+
const kt = ctx.schema.knownTags[tagName];
|
|
62
|
+
if (kt?.collection === expType) {
|
|
63
|
+
ctx.schema.tags.push(Object.assign({}, kt, { default: false }));
|
|
64
|
+
tag = kt;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (kt) {
|
|
68
|
+
onError(tagToken, 'BAD_COLLECTION_TYPE', `${kt.tag} used for ${expType} collection, but expects ${kt.collection ?? 'scalar'}`, true);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, true);
|
|
72
|
+
}
|
|
73
|
+
return resolveCollection(CN, ctx, token, onError, tagName);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const coll = resolveCollection(CN, ctx, token, onError, tagName, tag);
|
|
77
|
+
const res = tag.resolve?.(coll, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg), ctx.options) ?? coll;
|
|
78
|
+
const node = isNode(res)
|
|
79
|
+
? res
|
|
80
|
+
: new Scalar(res);
|
|
81
|
+
node.range = coll.range;
|
|
82
|
+
node.tag = tagName;
|
|
83
|
+
if (tag?.format)
|
|
84
|
+
node.format = tag.format;
|
|
85
|
+
return node;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { composeCollection };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Document } from '../doc/Document.js';
|
|
2
|
+
import { composeNode, composeEmptyNode } from './compose-node.js';
|
|
3
|
+
import { resolveEnd } from './resolve-end.js';
|
|
4
|
+
import { resolveProps } from './resolve-props.js';
|
|
5
|
+
|
|
6
|
+
function composeDoc(options, directives, { offset, start, value, end }, onError) {
|
|
7
|
+
const opts = Object.assign({ _directives: directives }, options);
|
|
8
|
+
const doc = new Document(undefined, opts);
|
|
9
|
+
const ctx = {
|
|
10
|
+
atKey: false,
|
|
11
|
+
atRoot: true,
|
|
12
|
+
directives: doc.directives,
|
|
13
|
+
options: doc.options,
|
|
14
|
+
schema: doc.schema
|
|
15
|
+
};
|
|
16
|
+
const props = resolveProps(start, {
|
|
17
|
+
indicator: 'doc-start',
|
|
18
|
+
next: value ?? end?.[0],
|
|
19
|
+
offset,
|
|
20
|
+
onError,
|
|
21
|
+
parentIndent: 0,
|
|
22
|
+
startOnNewline: true
|
|
23
|
+
});
|
|
24
|
+
if (props.found) {
|
|
25
|
+
doc.directives.docStart = true;
|
|
26
|
+
if (value &&
|
|
27
|
+
(value.type === 'block-map' || value.type === 'block-seq') &&
|
|
28
|
+
!props.hasNewline)
|
|
29
|
+
onError(props.end, 'MISSING_CHAR', 'Block collection cannot start on same line with directives-end marker');
|
|
30
|
+
}
|
|
31
|
+
// @ts-expect-error If Contents is set, let's trust the user
|
|
32
|
+
doc.contents = value
|
|
33
|
+
? composeNode(ctx, value, props, onError)
|
|
34
|
+
: composeEmptyNode(ctx, props.end, start, null, props, onError);
|
|
35
|
+
const contentEnd = doc.contents.range[2];
|
|
36
|
+
const re = resolveEnd(end, contentEnd, false, onError);
|
|
37
|
+
if (re.comment)
|
|
38
|
+
doc.comment = re.comment;
|
|
39
|
+
doc.range = [offset, contentEnd, re.offset];
|
|
40
|
+
return doc;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { composeDoc };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Alias } from '../nodes/Alias.js';
|
|
2
|
+
import { isScalar } from '../nodes/identity.js';
|
|
3
|
+
import { composeCollection } from './compose-collection.js';
|
|
4
|
+
import { composeScalar } from './compose-scalar.js';
|
|
5
|
+
import { resolveEnd } from './resolve-end.js';
|
|
6
|
+
import { emptyScalarPosition } from './util-empty-scalar-position.js';
|
|
7
|
+
|
|
8
|
+
const CN = { composeNode, composeEmptyNode };
|
|
9
|
+
function composeNode(ctx, token, props, onError) {
|
|
10
|
+
const atKey = ctx.atKey;
|
|
11
|
+
const { spaceBefore, comment, anchor, tag } = props;
|
|
12
|
+
let node;
|
|
13
|
+
let isSrcToken = true;
|
|
14
|
+
switch (token.type) {
|
|
15
|
+
case 'alias':
|
|
16
|
+
node = composeAlias(ctx, token, onError);
|
|
17
|
+
if (anchor || tag)
|
|
18
|
+
onError(token, 'ALIAS_PROPS', 'An alias node must not specify any properties');
|
|
19
|
+
break;
|
|
20
|
+
case 'scalar':
|
|
21
|
+
case 'single-quoted-scalar':
|
|
22
|
+
case 'double-quoted-scalar':
|
|
23
|
+
case 'block-scalar':
|
|
24
|
+
node = composeScalar(ctx, token, tag, onError);
|
|
25
|
+
if (anchor)
|
|
26
|
+
node.anchor = anchor.source.substring(1);
|
|
27
|
+
break;
|
|
28
|
+
case 'block-map':
|
|
29
|
+
case 'block-seq':
|
|
30
|
+
case 'flow-collection':
|
|
31
|
+
try {
|
|
32
|
+
node = composeCollection(CN, ctx, token, props, onError);
|
|
33
|
+
if (anchor)
|
|
34
|
+
node.anchor = anchor.source.substring(1);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// Almost certainly here due to a stack overflow
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
onError(token, 'RESOURCE_EXHAUSTION', message);
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
default: {
|
|
43
|
+
const message = token.type === 'error'
|
|
44
|
+
? token.message
|
|
45
|
+
: `Unsupported token (type: ${token.type})`;
|
|
46
|
+
onError(token, 'UNEXPECTED_TOKEN', message);
|
|
47
|
+
isSrcToken = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
|
|
51
|
+
if (anchor && node.anchor === '')
|
|
52
|
+
onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
|
|
53
|
+
if (atKey &&
|
|
54
|
+
ctx.options.stringKeys &&
|
|
55
|
+
(!isScalar(node) ||
|
|
56
|
+
typeof node.value !== 'string' ||
|
|
57
|
+
(node.tag && node.tag !== 'tag:yaml.org,2002:str'))) {
|
|
58
|
+
const msg = 'With stringKeys, all keys must be strings';
|
|
59
|
+
onError(tag ?? token, 'NON_STRING_KEY', msg);
|
|
60
|
+
}
|
|
61
|
+
if (spaceBefore)
|
|
62
|
+
node.spaceBefore = true;
|
|
63
|
+
if (comment) {
|
|
64
|
+
if (token.type === 'scalar' && token.source === '')
|
|
65
|
+
node.comment = comment;
|
|
66
|
+
else
|
|
67
|
+
node.commentBefore = comment;
|
|
68
|
+
}
|
|
69
|
+
// @ts-expect-error Type checking misses meaning of isSrcToken
|
|
70
|
+
if (ctx.options.keepSourceTokens && isSrcToken)
|
|
71
|
+
node.srcToken = token;
|
|
72
|
+
return node;
|
|
73
|
+
}
|
|
74
|
+
function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) {
|
|
75
|
+
const token = {
|
|
76
|
+
type: 'scalar',
|
|
77
|
+
offset: emptyScalarPosition(offset, before, pos),
|
|
78
|
+
indent: -1,
|
|
79
|
+
source: ''
|
|
80
|
+
};
|
|
81
|
+
const node = composeScalar(ctx, token, tag, onError);
|
|
82
|
+
if (anchor) {
|
|
83
|
+
node.anchor = anchor.source.substring(1);
|
|
84
|
+
if (node.anchor === '')
|
|
85
|
+
onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
|
|
86
|
+
}
|
|
87
|
+
if (spaceBefore)
|
|
88
|
+
node.spaceBefore = true;
|
|
89
|
+
if (comment) {
|
|
90
|
+
node.comment = comment;
|
|
91
|
+
node.range[2] = end;
|
|
92
|
+
}
|
|
93
|
+
return node;
|
|
94
|
+
}
|
|
95
|
+
function composeAlias({ options }, { offset, source, end }, onError) {
|
|
96
|
+
const alias = new Alias(source.substring(1));
|
|
97
|
+
if (alias.source === '')
|
|
98
|
+
onError(offset, 'BAD_ALIAS', 'Alias cannot be an empty string');
|
|
99
|
+
if (alias.source.endsWith(':'))
|
|
100
|
+
onError(offset + source.length - 1, 'BAD_ALIAS', 'Alias ending in : is ambiguous', true);
|
|
101
|
+
const valueEnd = offset + source.length;
|
|
102
|
+
const re = resolveEnd(end, valueEnd, options.strict, onError);
|
|
103
|
+
alias.range = [offset, valueEnd, re.offset];
|
|
104
|
+
if (re.comment)
|
|
105
|
+
alias.comment = re.comment;
|
|
106
|
+
return alias;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { composeEmptyNode, composeNode };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isScalar, SCALAR } from '../nodes/identity.js';
|
|
2
|
+
import { Scalar } from '../nodes/Scalar.js';
|
|
3
|
+
import { resolveBlockScalar } from './resolve-block-scalar.js';
|
|
4
|
+
import { resolveFlowScalar } from './resolve-flow-scalar.js';
|
|
5
|
+
|
|
6
|
+
function composeScalar(ctx, token, tagToken, onError) {
|
|
7
|
+
const { value, type, comment, range } = token.type === 'block-scalar'
|
|
8
|
+
? resolveBlockScalar(ctx, token, onError)
|
|
9
|
+
: resolveFlowScalar(token, ctx.options.strict, onError);
|
|
10
|
+
const tagName = tagToken
|
|
11
|
+
? ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg))
|
|
12
|
+
: null;
|
|
13
|
+
let tag;
|
|
14
|
+
if (ctx.options.stringKeys && ctx.atKey) {
|
|
15
|
+
tag = ctx.schema[SCALAR];
|
|
16
|
+
}
|
|
17
|
+
else if (tagName)
|
|
18
|
+
tag = findScalarTagByName(ctx.schema, value, tagName, tagToken, onError);
|
|
19
|
+
else if (token.type === 'scalar')
|
|
20
|
+
tag = findScalarTagByTest(ctx, value, token, onError);
|
|
21
|
+
else
|
|
22
|
+
tag = ctx.schema[SCALAR];
|
|
23
|
+
let scalar;
|
|
24
|
+
try {
|
|
25
|
+
const res = tag.resolve(value, msg => onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg), ctx.options);
|
|
26
|
+
scalar = isScalar(res) ? res : new Scalar(res);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
30
|
+
onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg);
|
|
31
|
+
scalar = new Scalar(value);
|
|
32
|
+
}
|
|
33
|
+
scalar.range = range;
|
|
34
|
+
scalar.source = value;
|
|
35
|
+
if (type)
|
|
36
|
+
scalar.type = type;
|
|
37
|
+
if (tagName)
|
|
38
|
+
scalar.tag = tagName;
|
|
39
|
+
if (tag.format)
|
|
40
|
+
scalar.format = tag.format;
|
|
41
|
+
if (comment)
|
|
42
|
+
scalar.comment = comment;
|
|
43
|
+
return scalar;
|
|
44
|
+
}
|
|
45
|
+
function findScalarTagByName(schema, value, tagName, tagToken, onError) {
|
|
46
|
+
if (tagName === '!')
|
|
47
|
+
return schema[SCALAR]; // non-specific tag
|
|
48
|
+
const matchWithTest = [];
|
|
49
|
+
for (const tag of schema.tags) {
|
|
50
|
+
if (!tag.collection && tag.tag === tagName) {
|
|
51
|
+
if (tag.default && tag.test)
|
|
52
|
+
matchWithTest.push(tag);
|
|
53
|
+
else
|
|
54
|
+
return tag;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const tag of matchWithTest)
|
|
58
|
+
if (tag.test?.test(value))
|
|
59
|
+
return tag;
|
|
60
|
+
const kt = schema.knownTags[tagName];
|
|
61
|
+
if (kt && !kt.collection) {
|
|
62
|
+
// Ensure that the known tag is available for stringifying,
|
|
63
|
+
// but does not get used by default.
|
|
64
|
+
schema.tags.push(Object.assign({}, kt, { default: false, test: undefined }));
|
|
65
|
+
return kt;
|
|
66
|
+
}
|
|
67
|
+
onError(tagToken, 'TAG_RESOLVE_FAILED', `Unresolved tag: ${tagName}`, tagName !== 'tag:yaml.org,2002:str');
|
|
68
|
+
return schema[SCALAR];
|
|
69
|
+
}
|
|
70
|
+
function findScalarTagByTest({ atKey, directives, schema }, value, token, onError) {
|
|
71
|
+
const tag = schema.tags.find(tag => (tag.default === true || (atKey && tag.default === 'key')) &&
|
|
72
|
+
tag.test?.test(value)) || schema[SCALAR];
|
|
73
|
+
if (schema.compat) {
|
|
74
|
+
const compat = schema.compat.find(tag => tag.default && tag.test?.test(value)) ??
|
|
75
|
+
schema[SCALAR];
|
|
76
|
+
if (tag.tag !== compat.tag) {
|
|
77
|
+
const ts = directives.tagString(tag.tag);
|
|
78
|
+
const cs = directives.tagString(compat.tag);
|
|
79
|
+
const msg = `Value may be parsed as either ${ts} or ${cs}`;
|
|
80
|
+
onError(token, 'TAG_RESOLVE_FAILED', msg, true);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return tag;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { composeScalar };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Directives } from '../doc/directives.js';
|
|
2
|
+
import { Document } from '../doc/Document.js';
|
|
3
|
+
import { YAMLWarning, YAMLParseError } from '../errors.js';
|
|
4
|
+
import { isCollection, isPair } from '../nodes/identity.js';
|
|
5
|
+
import { composeDoc } from './compose-doc.js';
|
|
6
|
+
import { resolveEnd } from './resolve-end.js';
|
|
7
|
+
|
|
8
|
+
function getErrorPos(src) {
|
|
9
|
+
if (typeof src === 'number')
|
|
10
|
+
return [src, src + 1];
|
|
11
|
+
if (Array.isArray(src))
|
|
12
|
+
return src.length === 2 ? src : [src[0], src[1]];
|
|
13
|
+
const { offset, source } = src;
|
|
14
|
+
return [offset, offset + (typeof source === 'string' ? source.length : 1)];
|
|
15
|
+
}
|
|
16
|
+
function parsePrelude(prelude) {
|
|
17
|
+
let comment = '';
|
|
18
|
+
let atComment = false;
|
|
19
|
+
let afterEmptyLine = false;
|
|
20
|
+
for (let i = 0; i < prelude.length; ++i) {
|
|
21
|
+
const source = prelude[i];
|
|
22
|
+
switch (source[0]) {
|
|
23
|
+
case '#':
|
|
24
|
+
comment +=
|
|
25
|
+
(comment === '' ? '' : afterEmptyLine ? '\n\n' : '\n') +
|
|
26
|
+
(source.substring(1) || ' ');
|
|
27
|
+
atComment = true;
|
|
28
|
+
afterEmptyLine = false;
|
|
29
|
+
break;
|
|
30
|
+
case '%':
|
|
31
|
+
if (prelude[i + 1]?.[0] !== '#')
|
|
32
|
+
i += 1;
|
|
33
|
+
atComment = false;
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
// This may be wrong after doc-end, but in that case it doesn't matter
|
|
37
|
+
if (!atComment)
|
|
38
|
+
afterEmptyLine = true;
|
|
39
|
+
atComment = false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { comment, afterEmptyLine };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Compose a stream of CST nodes into a stream of YAML Documents.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { Composer, Parser } from 'yaml'
|
|
49
|
+
*
|
|
50
|
+
* const src: string = ...
|
|
51
|
+
* const tokens = new Parser().parse(src)
|
|
52
|
+
* const docs = new Composer().compose(tokens)
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
class Composer {
|
|
56
|
+
constructor(options = {}) {
|
|
57
|
+
this.doc = null;
|
|
58
|
+
this.atDirectives = false;
|
|
59
|
+
this.prelude = [];
|
|
60
|
+
this.errors = [];
|
|
61
|
+
this.warnings = [];
|
|
62
|
+
this.onError = (source, code, message, warning) => {
|
|
63
|
+
const pos = getErrorPos(source);
|
|
64
|
+
if (warning)
|
|
65
|
+
this.warnings.push(new YAMLWarning(pos, code, message));
|
|
66
|
+
else
|
|
67
|
+
this.errors.push(new YAMLParseError(pos, code, message));
|
|
68
|
+
};
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
70
|
+
this.directives = new Directives({ version: options.version || '1.2' });
|
|
71
|
+
this.options = options;
|
|
72
|
+
}
|
|
73
|
+
decorate(doc, afterDoc) {
|
|
74
|
+
const { comment, afterEmptyLine } = parsePrelude(this.prelude);
|
|
75
|
+
//console.log({ dc: doc.comment, prelude, comment })
|
|
76
|
+
if (comment) {
|
|
77
|
+
const dc = doc.contents;
|
|
78
|
+
if (afterDoc) {
|
|
79
|
+
doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment;
|
|
80
|
+
}
|
|
81
|
+
else if (afterEmptyLine || doc.directives.docStart || !dc) {
|
|
82
|
+
doc.commentBefore = comment;
|
|
83
|
+
}
|
|
84
|
+
else if (isCollection(dc) && !dc.flow && dc.items.length > 0) {
|
|
85
|
+
let it = dc.items[0];
|
|
86
|
+
if (isPair(it))
|
|
87
|
+
it = it.key;
|
|
88
|
+
const cb = it.commentBefore;
|
|
89
|
+
it.commentBefore = cb ? `${comment}\n${cb}` : comment;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const cb = dc.commentBefore;
|
|
93
|
+
dc.commentBefore = cb ? `${comment}\n${cb}` : comment;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (afterDoc) {
|
|
97
|
+
for (let i = 0; i < this.errors.length; ++i)
|
|
98
|
+
doc.errors.push(this.errors[i]);
|
|
99
|
+
for (let i = 0; i < this.warnings.length; ++i)
|
|
100
|
+
doc.warnings.push(this.warnings[i]);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
doc.errors = this.errors;
|
|
104
|
+
doc.warnings = this.warnings;
|
|
105
|
+
}
|
|
106
|
+
this.prelude = [];
|
|
107
|
+
this.errors = [];
|
|
108
|
+
this.warnings = [];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Current stream status information.
|
|
112
|
+
*
|
|
113
|
+
* Mostly useful at the end of input for an empty stream.
|
|
114
|
+
*/
|
|
115
|
+
streamInfo() {
|
|
116
|
+
return {
|
|
117
|
+
comment: parsePrelude(this.prelude).comment,
|
|
118
|
+
directives: this.directives,
|
|
119
|
+
errors: this.errors,
|
|
120
|
+
warnings: this.warnings
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Compose tokens into documents.
|
|
125
|
+
*
|
|
126
|
+
* @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document.
|
|
127
|
+
* @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly.
|
|
128
|
+
*/
|
|
129
|
+
*compose(tokens, forceDoc = false, endOffset = -1) {
|
|
130
|
+
for (const token of tokens)
|
|
131
|
+
yield* this.next(token);
|
|
132
|
+
yield* this.end(forceDoc, endOffset);
|
|
133
|
+
}
|
|
134
|
+
/** Advance the composer by one CST token. */
|
|
135
|
+
*next(token) {
|
|
136
|
+
switch (token.type) {
|
|
137
|
+
case 'directive':
|
|
138
|
+
this.directives.add(token.source, (offset, message, warning) => {
|
|
139
|
+
const pos = getErrorPos(token);
|
|
140
|
+
pos[0] += offset;
|
|
141
|
+
this.onError(pos, 'BAD_DIRECTIVE', message, warning);
|
|
142
|
+
});
|
|
143
|
+
this.prelude.push(token.source);
|
|
144
|
+
this.atDirectives = true;
|
|
145
|
+
break;
|
|
146
|
+
case 'document': {
|
|
147
|
+
const doc = composeDoc(this.options, this.directives, token, this.onError);
|
|
148
|
+
if (this.atDirectives && !doc.directives.docStart)
|
|
149
|
+
this.onError(token, 'MISSING_CHAR', 'Missing directives-end/doc-start indicator line');
|
|
150
|
+
this.decorate(doc, false);
|
|
151
|
+
if (this.doc)
|
|
152
|
+
yield this.doc;
|
|
153
|
+
this.doc = doc;
|
|
154
|
+
this.atDirectives = false;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'byte-order-mark':
|
|
158
|
+
case 'space':
|
|
159
|
+
break;
|
|
160
|
+
case 'comment':
|
|
161
|
+
case 'newline':
|
|
162
|
+
this.prelude.push(token.source);
|
|
163
|
+
break;
|
|
164
|
+
case 'error': {
|
|
165
|
+
const msg = token.source
|
|
166
|
+
? `${token.message}: ${JSON.stringify(token.source)}`
|
|
167
|
+
: token.message;
|
|
168
|
+
const error = new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg);
|
|
169
|
+
if (this.atDirectives || !this.doc)
|
|
170
|
+
this.errors.push(error);
|
|
171
|
+
else
|
|
172
|
+
this.doc.errors.push(error);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'doc-end': {
|
|
176
|
+
if (!this.doc) {
|
|
177
|
+
const msg = 'Unexpected doc-end without preceding document';
|
|
178
|
+
this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg));
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
this.doc.directives.docEnd = true;
|
|
182
|
+
const end = resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError);
|
|
183
|
+
this.decorate(this.doc, true);
|
|
184
|
+
if (end.comment) {
|
|
185
|
+
const dc = this.doc.comment;
|
|
186
|
+
this.doc.comment = dc ? `${dc}\n${end.comment}` : end.comment;
|
|
187
|
+
}
|
|
188
|
+
this.doc.range[2] = end.offset;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
default:
|
|
192
|
+
this.errors.push(new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', `Unsupported token ${token.type}`));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Call at end of input to yield any remaining document.
|
|
197
|
+
*
|
|
198
|
+
* @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document.
|
|
199
|
+
* @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly.
|
|
200
|
+
*/
|
|
201
|
+
*end(forceDoc = false, endOffset = -1) {
|
|
202
|
+
if (this.doc) {
|
|
203
|
+
this.decorate(this.doc, true);
|
|
204
|
+
yield this.doc;
|
|
205
|
+
this.doc = null;
|
|
206
|
+
}
|
|
207
|
+
else if (forceDoc) {
|
|
208
|
+
const opts = Object.assign({ _directives: this.directives }, this.options);
|
|
209
|
+
const doc = new Document(undefined, opts);
|
|
210
|
+
if (this.atDirectives)
|
|
211
|
+
this.onError(endOffset, 'MISSING_CHAR', 'Missing directives-end indicator line');
|
|
212
|
+
doc.range = [0, endOffset, endOffset];
|
|
213
|
+
this.decorate(doc, false);
|
|
214
|
+
yield doc;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export { Composer };
|