@sushichan044/kg-core 0.6.5 → 0.8.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 +62 -13
- package/dist/composed-manuscript-QgIdae8p.d.mts +1740 -0
- package/dist/describe-cause-BV8mZ3Um.mjs +18 -0
- package/dist/index.d.mts +6481 -18298
- package/dist/index.mjs +50 -2475
- package/dist/lint.d.mts +461 -0
- package/dist/lint.mjs +812 -0
- package/dist/novel-composer-BCt9g172.d.mts +9781 -0
- package/dist/novel-composer-CROZLBqt.mjs +1929 -0
- package/dist/plugin.d.mts +3 -0
- package/dist/plugin.mjs +3 -0
- package/dist/question-or-exclamation-spacing-B5o23ZLf.mjs +251 -0
- package/package.json +11 -4
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as ManuscriptResult, i as ParsedManuscript, n as ValidationIssue, r as NamespacedId } from "./composed-manuscript-QgIdae8p.mjs";
|
|
2
|
+
import { a as InlineMeasureRequest, c as logicalInlineMeasurer, f as ManuscriptComposer, m as Rejection, n as createNovelComposer, o as InlineMeasurement, p as ManuscriptParser, s as InlineMeasurer } from "./novel-composer-BCt9g172.mjs";
|
|
3
|
+
export { type InlineMeasureRequest, type InlineMeasurement, type InlineMeasurer, type ManuscriptComposer, type ManuscriptParser, ManuscriptResult, NamespacedId, ParsedManuscript, type Rejection, ValidationIssue, createNovelComposer, logicalInlineMeasurer };
|
package/dist/plugin.mjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { c as ManuscriptResult, d as NamespacedId, s as ValidationIssue } from "./question-or-exclamation-spacing-B5o23ZLf.mjs";
|
|
2
|
+
import { a as logicalInlineMeasurer, p as ParsedManuscript, t as createNovelComposer } from "./novel-composer-CROZLBqt.mjs";
|
|
3
|
+
export { ManuscriptResult, NamespacedId, ParsedManuscript, ValidationIssue, createNovelComposer, logicalInlineMeasurer };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
//#region src/internal/schema.ts
|
|
3
|
+
/**
|
|
4
|
+
* Every DTO in this package is a closed, frozen object: unknown keys are rejected, output is
|
|
5
|
+
* readonly.
|
|
6
|
+
*/
|
|
7
|
+
const readonlyObject = (entries) => v.pipe(v.strictObject(entries), v.readonly());
|
|
8
|
+
const readonlyArray = (item) => v.pipe(v.array(item), v.readonly());
|
|
9
|
+
const nonNegativeInteger = (maximum = Number.POSITIVE_INFINITY) => v.pipe(v.number(), v.finite(), v.integer(), v.minValue(0), v.maxValue(maximum));
|
|
10
|
+
const positiveInteger = () => v.pipe(v.number(), v.finite(), v.integer(), v.minValue(1));
|
|
11
|
+
const TextRange = { schema: v.pipe(readonlyObject({
|
|
12
|
+
start: nonNegativeInteger(),
|
|
13
|
+
end: nonNegativeInteger()
|
|
14
|
+
}), v.check(({ start, end }) => end >= start, "range end must not precede its start")) };
|
|
15
|
+
const DisplayRange = { schema: v.pipe(TextRange.schema, v.brand("DisplayRange")) };
|
|
16
|
+
const GraphemeRange = { schema: v.pipe(TextRange.schema, v.brand("GraphemeRange")) };
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/range/manuscript-range.ts
|
|
19
|
+
const ManuscriptRangeSchema = readonlyObject({
|
|
20
|
+
source: { schema: v.pipe(TextRange.schema, v.brand("SourceRange")) }.schema,
|
|
21
|
+
display: DisplayRange.schema,
|
|
22
|
+
graphemes: GraphemeRange.schema
|
|
23
|
+
});
|
|
24
|
+
const ManuscriptRange = {
|
|
25
|
+
schema: ManuscriptRangeSchema,
|
|
26
|
+
/**
|
|
27
|
+
* Brands a span core computed itself. Throws rather than returning a failure: the inputs come
|
|
28
|
+
* from core's own arithmetic, so a violated bound is a bug here, not bad data from a caller.
|
|
29
|
+
*/
|
|
30
|
+
of: (input) => v.parse(ManuscriptRangeSchema, input),
|
|
31
|
+
merge: (ranges) => {
|
|
32
|
+
const present = ranges.filter((range) => range !== null);
|
|
33
|
+
if (present.length === 0) return null;
|
|
34
|
+
return ManuscriptRange.of({
|
|
35
|
+
source: {
|
|
36
|
+
start: Math.min(...present.map(({ source }) => source.start)),
|
|
37
|
+
end: Math.max(...present.map(({ source }) => source.end))
|
|
38
|
+
},
|
|
39
|
+
display: {
|
|
40
|
+
start: Math.min(...present.map(({ display }) => display.start)),
|
|
41
|
+
end: Math.max(...present.map(({ display }) => display.end))
|
|
42
|
+
},
|
|
43
|
+
graphemes: {
|
|
44
|
+
start: Math.min(...present.map(({ graphemes }) => graphemes.start)),
|
|
45
|
+
end: Math.max(...present.map(({ graphemes }) => graphemes.end))
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
overlaps: (range, other) => range.graphemes.start < other.graphemes.end && range.graphemes.end > other.graphemes.start
|
|
50
|
+
};
|
|
51
|
+
const SourcePosition = {
|
|
52
|
+
schema: readonlyObject({
|
|
53
|
+
offset: nonNegativeInteger(),
|
|
54
|
+
line: positiveInteger(),
|
|
55
|
+
column: positiveInteger()
|
|
56
|
+
}),
|
|
57
|
+
/**
|
|
58
|
+
* Counts CRLF, CR and LF as one line break each; column is UTF-16 code units from line start.
|
|
59
|
+
*/
|
|
60
|
+
at: (source, offset) => {
|
|
61
|
+
let line = 1;
|
|
62
|
+
let lineStart = 0;
|
|
63
|
+
let index = 0;
|
|
64
|
+
while (index < offset) if (source[index] === "\r") {
|
|
65
|
+
index += source[index + 1] === "\n" ? 2 : 1;
|
|
66
|
+
line += 1;
|
|
67
|
+
lineStart = index;
|
|
68
|
+
} else if (source[index] === "\n") {
|
|
69
|
+
index += 1;
|
|
70
|
+
line += 1;
|
|
71
|
+
lineStart = index;
|
|
72
|
+
} else index += 1;
|
|
73
|
+
return {
|
|
74
|
+
offset,
|
|
75
|
+
line,
|
|
76
|
+
column: offset - lineStart + 1
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/namespaced-id.ts
|
|
82
|
+
const NamespacedIdSchema = v.pipe(v.string(), v.nonEmpty(), v.regex(/^[^/]+\/[^/]+$/u, "ID must use namespace/name"), v.brand("NamespacedId"));
|
|
83
|
+
const NamespacedId = {
|
|
84
|
+
schema: NamespacedIdSchema,
|
|
85
|
+
/**
|
|
86
|
+
* For IDs written as literals in code. Throws on a malformed literal, at import time.
|
|
87
|
+
*/
|
|
88
|
+
of: (literal) => v.parse(NamespacedIdSchema, literal),
|
|
89
|
+
/**
|
|
90
|
+
* For IDs supplied by a plugin, where a malformed value is bad input rather than a bug.
|
|
91
|
+
*/
|
|
92
|
+
parse: (raw) => {
|
|
93
|
+
const result = v.safeParse(NamespacedIdSchema, raw);
|
|
94
|
+
return result.success ? result.output : void 0;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const DiagnosticOrigin = { schema: v.variant("kind", [readonlyObject({
|
|
98
|
+
kind: v.literal("parser"),
|
|
99
|
+
id: NamespacedId.schema
|
|
100
|
+
}), readonlyObject({
|
|
101
|
+
kind: v.literal("rule"),
|
|
102
|
+
id: NamespacedId.schema
|
|
103
|
+
})]) };
|
|
104
|
+
const DiagnosticSeverity = { schema: v.picklist(["warning", "error"]) };
|
|
105
|
+
const ManuscriptDiagnostic = {
|
|
106
|
+
schema: readonlyObject({
|
|
107
|
+
id: v.string(),
|
|
108
|
+
origin: DiagnosticOrigin.schema,
|
|
109
|
+
severity: DiagnosticSeverity.schema,
|
|
110
|
+
message: v.string(),
|
|
111
|
+
range: ManuscriptRange.schema,
|
|
112
|
+
location: readonlyObject({
|
|
113
|
+
start: SourcePosition.schema,
|
|
114
|
+
end: SourcePosition.schema
|
|
115
|
+
})
|
|
116
|
+
}),
|
|
117
|
+
of: ({ source, origin, severity, code, message, range }) => ({
|
|
118
|
+
id: `${origin.kind}:${origin.id}:${code}:${range.source.start}:${range.source.end}`,
|
|
119
|
+
origin,
|
|
120
|
+
severity,
|
|
121
|
+
message,
|
|
122
|
+
range,
|
|
123
|
+
location: {
|
|
124
|
+
start: SourcePosition.at(source, range.source.start),
|
|
125
|
+
end: SourcePosition.at(source, range.source.end)
|
|
126
|
+
}
|
|
127
|
+
}),
|
|
128
|
+
/**
|
|
129
|
+
* Stable order for display: by source span, then by producing plugin.
|
|
130
|
+
*/
|
|
131
|
+
compare: (left, right) => left.range.source.start - right.range.source.start || left.range.source.end - right.range.source.end || left.origin.id.localeCompare(right.origin.id)
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/result/manuscript-result.ts
|
|
135
|
+
const ManuscriptResult = {
|
|
136
|
+
succeed: (value, warnings = []) => ({
|
|
137
|
+
ok: true,
|
|
138
|
+
value,
|
|
139
|
+
warnings
|
|
140
|
+
}),
|
|
141
|
+
fail: (error) => ({
|
|
142
|
+
ok: false,
|
|
143
|
+
error
|
|
144
|
+
}),
|
|
145
|
+
/**
|
|
146
|
+
* Validates a result that crossed a plugin boundary, including the diagnostics it carries.
|
|
147
|
+
*/
|
|
148
|
+
schema: (value, error) => v.variant("ok", [readonlyObject({
|
|
149
|
+
ok: v.literal(true),
|
|
150
|
+
value,
|
|
151
|
+
warnings: readonlyArray(ManuscriptDiagnostic.schema)
|
|
152
|
+
}), readonlyObject({
|
|
153
|
+
ok: v.literal(false),
|
|
154
|
+
error
|
|
155
|
+
})])
|
|
156
|
+
};
|
|
157
|
+
const ValidationIssue = {
|
|
158
|
+
schema: readonlyObject({
|
|
159
|
+
path: v.string(),
|
|
160
|
+
message: v.string()
|
|
161
|
+
}),
|
|
162
|
+
from: (issues) => issues.map((issue) => ({
|
|
163
|
+
path: (issue.path ?? []).map((item) => String(item.key)).join("."),
|
|
164
|
+
message: issue.message
|
|
165
|
+
}))
|
|
166
|
+
};
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/internal/segmenter.ts
|
|
169
|
+
/**
|
|
170
|
+
* Shared across parsing and proofreading so both agree on what counts as one grapheme.
|
|
171
|
+
*/
|
|
172
|
+
const graphemeSegmenter = new Intl.Segmenter("ja", { granularity: "grapheme" });
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/internal/japanese-brackets.ts
|
|
175
|
+
/**
|
|
176
|
+
* Brackets and quotes a Japanese paragraph may open with. Halfwidth forms are included because a
|
|
177
|
+
* manuscript that uses them is still opening a paragraph, not writing prose.
|
|
178
|
+
*/
|
|
179
|
+
const OPENING_BRACKETS = "「『〖〈《((“\"‘'[[〔{{<<";
|
|
180
|
+
/**
|
|
181
|
+
* The closing counterparts, in the same order.
|
|
182
|
+
*/
|
|
183
|
+
const CLOSING_BRACKETS = "」』〗〉》))”\"’']]〕}}>>";
|
|
184
|
+
/**
|
|
185
|
+
* Escapes the characters a regular expression character class would otherwise read as syntax, so a
|
|
186
|
+
* set declared above can be dropped into a pattern.
|
|
187
|
+
*/
|
|
188
|
+
function characterClass(characters) {
|
|
189
|
+
return characters.replaceAll(/[\\\]^-]/gu, "\\$&");
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/internal/japanese-spaces.ts
|
|
193
|
+
const LEADING_SPACES = /^[ \t]+/u;
|
|
194
|
+
/**
|
|
195
|
+
* The run of spaces the text begins with, or an empty string. Halfwidth spaces and tabs count: a
|
|
196
|
+
* rule has to see them to say they are the wrong space.
|
|
197
|
+
*/
|
|
198
|
+
function leadingSpaces(text) {
|
|
199
|
+
return LEADING_SPACES.exec(text)?.[0] ?? "";
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/internal/question-or-exclamation-spacing.ts
|
|
203
|
+
/**
|
|
204
|
+
* Runs, not single marks: `!?` is one mark and takes one gap, so the gap belongs after the run.
|
|
205
|
+
*/
|
|
206
|
+
const MARKS = /[?!]+/gu;
|
|
207
|
+
function analyzeMatch(line, marks) {
|
|
208
|
+
const start = marks.index;
|
|
209
|
+
if (start === void 0) return void 0;
|
|
210
|
+
const marksRange = {
|
|
211
|
+
start,
|
|
212
|
+
end: start + marks[0].length
|
|
213
|
+
};
|
|
214
|
+
const next = line[marksRange.end];
|
|
215
|
+
if (next === void 0 || "」』〗〉》))”\"’']]〕}}>>".includes(next)) return void 0;
|
|
216
|
+
const spaces = leadingSpaces(line.slice(marksRange.end));
|
|
217
|
+
if (spaces === "") return {
|
|
218
|
+
kind: "missing",
|
|
219
|
+
marks: marksRange
|
|
220
|
+
};
|
|
221
|
+
const gap = {
|
|
222
|
+
start: marksRange.end,
|
|
223
|
+
end: marksRange.end + spaces.length
|
|
224
|
+
};
|
|
225
|
+
const following = line[gap.end];
|
|
226
|
+
if (following !== void 0 && "」』〗〉》))”\"’']]〕}}>>".includes(following)) return {
|
|
227
|
+
kind: "before-closing-bracket",
|
|
228
|
+
marks: marksRange,
|
|
229
|
+
gap
|
|
230
|
+
};
|
|
231
|
+
if (spaces !== " ") return {
|
|
232
|
+
kind: "invalid-space",
|
|
233
|
+
marks: marksRange,
|
|
234
|
+
gap
|
|
235
|
+
};
|
|
236
|
+
return {
|
|
237
|
+
kind: "valid",
|
|
238
|
+
marks: marksRange,
|
|
239
|
+
gap
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Classifies the spacing after each run of fullwidth question or exclamation marks in one display
|
|
244
|
+
* line. Runs at the end of the line or immediately before a closing bracket need no gap and are
|
|
245
|
+
* omitted from the result.
|
|
246
|
+
*/
|
|
247
|
+
function questionOrExclamationSpacings(line) {
|
|
248
|
+
return [...line.matchAll(MARKS)].map((marks) => analyzeMatch(line, marks)).filter((spacing) => spacing !== void 0);
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
export { characterClass as a, ManuscriptResult as c, NamespacedId as d, ManuscriptRange as f, readonlyObject as h, OPENING_BRACKETS as i, ManuscriptDiagnostic as l, readonlyArray as m, leadingSpaces as n, graphemeSegmenter as o, nonNegativeInteger as p, CLOSING_BRACKETS as r, ValidationIssue as s, questionOrExclamationSpacings as t, DiagnosticSeverity as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sushichan044/kg-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Framework-independent parsing, composition, and proofreading for Japanese manuscripts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"japanese",
|
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
"types": "./dist/index.d.mts",
|
|
37
37
|
"import": "./dist/index.mjs"
|
|
38
38
|
},
|
|
39
|
+
"./lint": {
|
|
40
|
+
"types": "./dist/lint.d.mts",
|
|
41
|
+
"import": "./dist/lint.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./plugin": {
|
|
44
|
+
"types": "./dist/plugin.d.mts",
|
|
45
|
+
"import": "./dist/plugin.mjs"
|
|
46
|
+
},
|
|
39
47
|
"./package.json": "./package.json"
|
|
40
48
|
},
|
|
41
49
|
"publishConfig": {
|
|
@@ -49,10 +57,9 @@
|
|
|
49
57
|
},
|
|
50
58
|
"devDependencies": {
|
|
51
59
|
"@arethetypeswrong/core": "0.18.5",
|
|
52
|
-
"publint": "0.3.
|
|
60
|
+
"publint": "0.3.24",
|
|
53
61
|
"typescript": "7.0.2",
|
|
54
|
-
"unplugin-unused": "0.
|
|
55
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.7",
|
|
62
|
+
"unplugin-unused": "0.6.0",
|
|
56
63
|
"vite-plus": "0.3.0"
|
|
57
64
|
},
|
|
58
65
|
"engines": {
|