@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.
@@ -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 };
@@ -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.6.5",
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.22",
60
+ "publint": "0.3.24",
53
61
  "typescript": "7.0.2",
54
- "unplugin-unused": "0.5.7",
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": {