@anja.codes/vitakit 0.2.0 → 0.3.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 +4 -3
- package/dist/check.mjs +14 -12
- package/dist/chunks/authoring.mjs +50 -45
- package/dist/{shared/vitakit.By5VWQ1n.mjs → chunks/bibliography.mjs} +73 -170
- package/dist/chunks/build.mjs +19 -17
- package/dist/chunks/documents.mjs +15 -6
- package/dist/chunks/draft.mjs +3 -3
- package/dist/chunks/eject.mjs +2 -2
- package/dist/chunks/emit.mjs +38 -22
- package/dist/chunks/fields.mjs +2 -2
- package/dist/chunks/inventory.mjs +29 -27
- package/dist/chunks/migrations.mjs +218 -0
- package/dist/chunks/plan.mjs +36 -8
- package/dist/chunks/publication-lists.mjs +202 -0
- package/dist/chunks/schemas.mjs +2 -2
- package/dist/chunks/sections.mjs +3 -3
- package/dist/chunks/validate.mjs +25 -16
- package/dist/chunks/vue.mjs +89 -52
- package/dist/chunks/yaml.mjs +62 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +12 -10
- package/dist/nuxt.mjs +19 -11
- package/dist/runtime/components/VitaPublications.vue +21 -22
- package/dist/runtime/composables/useVitakit.ts +14 -3
- package/dist/schemas/publications.ts +9 -7
- package/dist/shared/index.d.mts +2 -2
- package/dist/shared/{vitakit.BfpxDtz1.d.mts → vitakit.BfR_mUHm.d.mts} +17 -5
- package/dist/shared/{vitakit.BZZIsWlY.mjs → vitakit.CCTDg7pz.mjs} +21 -1
- package/dist/shared/{vitakit.DbSeENVS.mjs → vitakit.DCK90zq4.mjs} +10 -1
- package/dist/shared/vitakit.DteA9vLy.mjs +135 -0
- package/dist/shared/{vitakit.BerUaxtF.mjs → vitakit.DvABWuUc.mjs} +1 -1
- package/dist/shared/{vitakit.Do5HC_MD.mjs → vitakit.DvvUYUTS.mjs} +61 -3
- package/dist/shared/vitakit.UfoXdL7U.mjs +22 -0
- package/dist/shared/{vitakit.CoWX2Z4R.mjs → vitakit.zwr1ssO5.mjs} +353 -57
- package/dist/tex/templates/publications.eta +4 -2
- package/dist/tex/vitakit-cv.cls +15 -4
- package/dist/tex.d.mts +29 -8
- package/dist/tex.mjs +13 -10
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@ import { readdir, readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { basename, join } from 'node:path';
|
|
3
3
|
import { parse } from 'yaml';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { f as fail, m as messageOf, a as formatZodIssues } from '../shared/vitakit.
|
|
5
|
+
import { f as fail, m as messageOf, a as formatZodIssues } from '../shared/vitakit.DCK90zq4.mjs';
|
|
6
|
+
import { c as findPdfPublicationPlacements, f as findPdfPlacements } from '../shared/vitakit.DvvUYUTS.mjs';
|
|
6
7
|
import { a as atMost } from '../shared/vitakit.TSMTC5Jl.mjs';
|
|
7
8
|
import { f as fieldValue, a as filterWhere } from '../shared/vitakit.D-DdJaDZ.mjs';
|
|
8
9
|
import 'node:url';
|
|
@@ -37,10 +38,7 @@ const documentSchema = z.strictObject({
|
|
|
37
38
|
* build (see `vitakit verify`), and the website renders CV data directly.
|
|
38
39
|
*/
|
|
39
40
|
visibility: z.enum(["public", "cv", "private"]).default("cv"),
|
|
40
|
-
sections: z.record(z.string(), sectionOptions).default({})
|
|
41
|
-
publications: z.strictObject({
|
|
42
|
-
groups: z.array(z.string()).optional()
|
|
43
|
-
}).default({})
|
|
41
|
+
sections: z.record(z.string(), sectionOptions).default({})
|
|
44
42
|
});
|
|
45
43
|
const DEFAULTS = documentSchema.parse({});
|
|
46
44
|
async function findDocuments(cvDir) {
|
|
@@ -62,6 +60,10 @@ async function findDocuments(cvDir) {
|
|
|
62
60
|
problems.push({ where, message: messageOf(error) });
|
|
63
61
|
continue;
|
|
64
62
|
}
|
|
63
|
+
if (typeof data === "object" && data !== null && Object.hasOwn(data, "publications")) {
|
|
64
|
+
problems.push({ where, message: "publications: which publications a PDF shows is now a list in content/publication-lists.yml, placed with \\vitapublications[Heading]{list}; `vitakit migrate` converts it" });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
65
67
|
const parsed = documentSchema.safeParse(data ?? {});
|
|
66
68
|
if (!parsed.success) {
|
|
67
69
|
problems.push({ where, message: `invalid
|
|
@@ -70,7 +72,14 @@ ${formatZodIssues(parsed.error)}` });
|
|
|
70
72
|
}
|
|
71
73
|
options = parsed.data;
|
|
72
74
|
}
|
|
73
|
-
|
|
75
|
+
const tex = `cv-tex/${file}`;
|
|
76
|
+
documents.push({
|
|
77
|
+
name,
|
|
78
|
+
texFile: join(cvDir, file),
|
|
79
|
+
options,
|
|
80
|
+
placements: findPdfPlacements(name, tex, source),
|
|
81
|
+
publications: findPdfPublicationPlacements(name, tex, source)
|
|
82
|
+
});
|
|
74
83
|
}
|
|
75
84
|
return { documents, problems };
|
|
76
85
|
}
|
package/dist/chunks/draft.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { s as section, t as text, d as dateRange, l as linked, u as url, a as richText } from '../shared/vitakit.
|
|
3
|
-
import { p as propertyKey } from '../shared/vitakit.
|
|
2
|
+
import { s as section, t as text, d as dateRange, l as linked, u as url, a as richText } from '../shared/vitakit.DvABWuUc.mjs';
|
|
3
|
+
import { p as propertyKey } from '../shared/vitakit.CCTDg7pz.mjs';
|
|
4
4
|
import { i as isSitePath } from '../shared/vitakit.D10HyDhF.mjs';
|
|
5
|
-
import '../shared/vitakit.
|
|
5
|
+
import '../shared/vitakit.DCK90zq4.mjs';
|
|
6
6
|
import 'node:url';
|
|
7
7
|
import '@babel/parser';
|
|
8
8
|
|
package/dist/chunks/eject.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises';
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
4
|
import { r as resolveAsset } from '../shared/vitakit.DQ6PmGoL.mjs';
|
|
5
|
-
import { C as CommandError } from '../shared/vitakit.
|
|
5
|
+
import { C as CommandError } from '../shared/vitakit.zwr1ssO5.mjs';
|
|
6
6
|
import 'node:url';
|
|
7
7
|
import 'node:process';
|
|
8
8
|
import 'node:util';
|
|
@@ -15,7 +15,7 @@ const SCOPES = {
|
|
|
15
15
|
VitaLinks: "the profile links",
|
|
16
16
|
VitaMarkdown: "every Markdown page body",
|
|
17
17
|
VitaIssues: "the development error banner",
|
|
18
|
-
VitaPublications: "
|
|
18
|
+
VitaPublications: "how a publication list is laid out: its headings and groups",
|
|
19
19
|
VitaPublication: "how each reference looks on the website"
|
|
20
20
|
};
|
|
21
21
|
async function ejectableAssets() {
|
package/dist/chunks/emit.mjs
CHANGED
|
@@ -5,12 +5,13 @@ import { Eta } from 'eta';
|
|
|
5
5
|
import { r as resolveAsset } from '../shared/vitakit.DQ6PmGoL.mjs';
|
|
6
6
|
import { b as escapeUrl, a as escapeTex, e as escapeRichTex } from '../shared/vitakit.DcQOdM4I.mjs';
|
|
7
7
|
import { f as formatDateRange, e as entrySecondaryParts, b as entryTitleField } from '../shared/vitakit.CfB3hMHl.mjs';
|
|
8
|
-
import { f as fail } from '../shared/vitakit.
|
|
8
|
+
import { f as fail } from '../shared/vitakit.DCK90zq4.mjs';
|
|
9
9
|
import { i as isSitePath } from '../shared/vitakit.D10HyDhF.mjs';
|
|
10
10
|
import { selectEntries } from './documents.mjs';
|
|
11
11
|
import 'node:url';
|
|
12
12
|
import 'yaml';
|
|
13
13
|
import 'zod';
|
|
14
|
+
import '../shared/vitakit.DvvUYUTS.mjs';
|
|
14
15
|
import '../shared/vitakit.TSMTC5Jl.mjs';
|
|
15
16
|
import '../shared/vitakit.D-DdJaDZ.mjs';
|
|
16
17
|
|
|
@@ -114,13 +115,24 @@ function genericHelpers(h) {
|
|
|
114
115
|
}
|
|
115
116
|
};
|
|
116
117
|
}
|
|
117
|
-
function
|
|
118
|
-
const
|
|
119
|
-
const bad = [group.id, ...group.entries.map((entry) => entry.key)].find((value) => !value || unsafe.test(value));
|
|
118
|
+
function texSafe(values, where) {
|
|
119
|
+
const bad = values.find((value) => !value || /[\s%#\\{},~]/.test(value));
|
|
120
120
|
if (bad !== void 0) {
|
|
121
121
|
fail(where, `"${bad}" cannot be used as a citation key or group id in LaTeX; use letters, digits and - _ : . / +`);
|
|
122
122
|
}
|
|
123
|
-
|
|
123
|
+
}
|
|
124
|
+
function bibHelpers(list, where) {
|
|
125
|
+
return {
|
|
126
|
+
cite() {
|
|
127
|
+
const keys = list.groups.flatMap((group) => group.entries.map((entry) => entry.key));
|
|
128
|
+
texSafe(keys, where);
|
|
129
|
+
return fragment(`\\nocite{${keys.join(",")}}`);
|
|
130
|
+
},
|
|
131
|
+
group(group) {
|
|
132
|
+
texSafe([group.id, ...group.entries.map((entry) => entry.key)], where);
|
|
133
|
+
return fragment(`\\vitabibgroup{${group.id}}{${group.entries.map((entry) => entry.key).join(",")}}`);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
124
136
|
}
|
|
125
137
|
async function resolveTemplate(id, userDir, { exclude } = {}) {
|
|
126
138
|
const candidates = [
|
|
@@ -159,17 +171,16 @@ async function renderSection(id, entries, options, templateDir) {
|
|
|
159
171
|
throw new Error(`template ${id}.eta: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
|
-
async function renderPublications(
|
|
163
|
-
const groups = options.publications.groups ? options.publications.groups.map((id) => publications.find((group) => group.id === id)).filter((group) => group !== void 0) : publications;
|
|
174
|
+
async function renderPublications(list, templateDir, siteUrl) {
|
|
164
175
|
if (!siteUrl) {
|
|
165
|
-
const local = groups.flatMap((group) => group.entries).find((entry) => entry.titleUrl && isSitePath(entry.titleUrl));
|
|
176
|
+
const local = list.groups.flatMap((group) => group.entries).find((entry) => entry.titleUrl && isSitePath(entry.titleUrl));
|
|
166
177
|
if (local) fail("content/publications.bib", `${local.key}: url ${local.titleUrl} ${SITE_PATH_HELP}`);
|
|
167
178
|
}
|
|
168
179
|
const { path: templatePath, source } = await resolveTemplate("publications", templateDir);
|
|
169
180
|
return tidy(eta.renderString(source, {
|
|
170
|
-
groups,
|
|
181
|
+
groups: list.groups,
|
|
171
182
|
...createTemplateHelpers(templatePath, "content/publications.bib"),
|
|
172
|
-
bib:
|
|
183
|
+
bib: bibHelpers(list, templatePath)
|
|
173
184
|
}));
|
|
174
185
|
}
|
|
175
186
|
async function emitDocument(emit) {
|
|
@@ -177,22 +188,27 @@ async function emitDocument(emit) {
|
|
|
177
188
|
await assertTemplatesAreUsed(emit.templateDir, Object.keys(emit.sections));
|
|
178
189
|
const written = [];
|
|
179
190
|
for (const [id, entries] of Object.entries(emit.sections)) {
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
await writeFile(
|
|
183
|
-
${
|
|
191
|
+
const body = await renderSection(id, entries, emit.options, emit.templateDir);
|
|
192
|
+
const file = join(emit.outDir, `${id}.tex`);
|
|
193
|
+
await writeFile(file, `% Generated by vitakit. Do not edit; edit content/${id}.yml instead.
|
|
194
|
+
${body.trim()}
|
|
184
195
|
`);
|
|
185
|
-
written.push(
|
|
196
|
+
written.push(file);
|
|
197
|
+
}
|
|
198
|
+
const current = /* @__PURE__ */ new Set(["profile.tex", ...written.map((file) => basename(file))]);
|
|
199
|
+
for (const file of await readdir(emit.outDir)) {
|
|
200
|
+
if (file.endsWith(".tex") && !current.has(file)) await rm(join(emit.outDir, file));
|
|
186
201
|
}
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
await
|
|
202
|
+
const listDir = join(emit.outDir, "publications");
|
|
203
|
+
await rm(listDir, { recursive: true, force: true });
|
|
204
|
+
await mkdir(listDir);
|
|
205
|
+
for (const [name, list] of Object.entries(emit.publications)) {
|
|
206
|
+
const body = await renderPublications(list, emit.templateDir, emit.siteUrl);
|
|
207
|
+
const file = join(listDir, `${name}.tex`);
|
|
208
|
+
await writeFile(file, `% Generated by vitakit. Do not edit; edit content/publications.bib or content/publication-lists.yml instead.
|
|
190
209
|
${body.trim()}
|
|
191
210
|
`);
|
|
192
|
-
|
|
193
|
-
const current = /* @__PURE__ */ new Set(["profile.tex", ...written.map((file2) => basename(file2))]);
|
|
194
|
-
for (const file2 of await readdir(emit.outDir)) {
|
|
195
|
-
if (file2.endsWith(".tex") && !current.has(file2)) await rm(join(emit.outDir, file2));
|
|
211
|
+
written.push(file);
|
|
196
212
|
}
|
|
197
213
|
return written;
|
|
198
214
|
}
|
package/dist/chunks/fields.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { v as visibility, t as text, a as richText, d as dateRange, u as url, b as absoluteUrl, l as linked, r as requiredLink } from '../shared/vitakit.
|
|
3
|
-
import '../shared/vitakit.
|
|
2
|
+
import { v as visibility, t as text, a as richText, d as dateRange, u as url, b as absoluteUrl, l as linked, r as requiredLink } from '../shared/vitakit.DvABWuUc.mjs';
|
|
3
|
+
import '../shared/vitakit.DCK90zq4.mjs';
|
|
4
4
|
import 'node:url';
|
|
5
5
|
import '../shared/vitakit.D10HyDhF.mjs';
|
|
6
6
|
|
|
@@ -3,30 +3,32 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { join, relative, sep } from 'node:path';
|
|
4
4
|
import matter from 'gray-matter';
|
|
5
5
|
import { parse } from 'yaml';
|
|
6
|
-
import { l as loadContent } from '../shared/vitakit.
|
|
6
|
+
import { l as loadContent } from '../shared/vitakit.DteA9vLy.mjs';
|
|
7
7
|
import { describeSection } from './fields.mjs';
|
|
8
8
|
import { findSectionFiles, resolveSectionSchema } from './sections.mjs';
|
|
9
9
|
import { schemas } from './schemas.mjs';
|
|
10
10
|
import { findDocuments, documentSectionProblems } from './documents.mjs';
|
|
11
|
-
import {
|
|
12
|
-
import { m as messageOf, w as whereOf } from '../shared/vitakit.
|
|
11
|
+
import { a as pageRoute, d as documentDirective, p as publicationPlacementProblems, b as findWebPlacements } from '../shared/vitakit.DvvUYUTS.mjs';
|
|
12
|
+
import { m as messageOf, w as whereOf } from '../shared/vitakit.DCK90zq4.mjs';
|
|
13
|
+
import './bibliography.mjs';
|
|
14
|
+
import '../shared/vitakit.DQ6PmGoL.mjs';
|
|
15
|
+
import 'node:url';
|
|
16
|
+
import '@behackl/citation-js-extras';
|
|
13
17
|
import 'unified';
|
|
18
|
+
import 'rehype-parse';
|
|
19
|
+
import 'rehype-sanitize';
|
|
20
|
+
import 'rehype-stringify';
|
|
14
21
|
import 'remark-parse';
|
|
15
22
|
import 'remark-gfm';
|
|
16
23
|
import 'remark-math';
|
|
17
24
|
import 'remark-rehype';
|
|
18
25
|
import 'rehype-katex';
|
|
19
26
|
import 'rehype-raw';
|
|
20
|
-
import 'rehype-stringify';
|
|
21
27
|
import 'unist-util-visit';
|
|
22
|
-
import '../shared/vitakit.D10HyDhF.mjs';
|
|
23
|
-
import '../shared/vitakit.DQ6PmGoL.mjs';
|
|
24
|
-
import 'node:url';
|
|
25
|
-
import '@behackl/citation-js-extras';
|
|
26
|
-
import 'rehype-parse';
|
|
27
|
-
import 'rehype-sanitize';
|
|
28
28
|
import 'zod';
|
|
29
|
-
import '
|
|
29
|
+
import './publication-lists.mjs';
|
|
30
|
+
import '../shared/vitakit.D10HyDhF.mjs';
|
|
31
|
+
import '../shared/vitakit.DvABWuUc.mjs';
|
|
30
32
|
import '../shared/vitakit.TSMTC5Jl.mjs';
|
|
31
33
|
import '../shared/vitakit.D-DdJaDZ.mjs';
|
|
32
34
|
|
|
@@ -128,17 +130,18 @@ async function inventory(rootDir, options = {}) {
|
|
|
128
130
|
}
|
|
129
131
|
page(pageRoute(below)).vue = posix(relative(rootDir, path));
|
|
130
132
|
}
|
|
131
|
-
const bib = join(rootDir, "content", "publications.bib");
|
|
132
|
-
const publications =
|
|
133
|
+
const bib = await readFile(join(rootDir, "content", "publications.bib"), "utf8").catch(() => void 0);
|
|
134
|
+
const publications = bib === void 0 ? void 0 : {
|
|
133
135
|
file: "content/publications.bib",
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
// Counted, not parsed: the lists were parsed above, and may be broken.
|
|
137
|
+
entries: bib.match(/^\s*@(?!comment\b|string\b|preamble\b)\w+\s*[{(]/gim)?.length ?? 0,
|
|
138
|
+
counts: Object.fromEntries(Object.entries(content.publications).map(([name, list]) => [name, list.count]))
|
|
139
|
+
};
|
|
137
140
|
const cvDir = join(rootDir, "cv-tex");
|
|
138
141
|
const { documents: found, problems: documentProblems } = await findDocuments(cvDir);
|
|
139
142
|
problems.push(...documentProblems);
|
|
140
|
-
const known = /* @__PURE__ */ new Set([...sections.map((section) => section.id), "
|
|
141
|
-
const
|
|
143
|
+
const known = /* @__PURE__ */ new Set([...sections.map((section) => section.id), "profile"]);
|
|
144
|
+
const lists = Object.keys(content.publications);
|
|
142
145
|
const documents = [];
|
|
143
146
|
for (const document of found) {
|
|
144
147
|
const tex = `cv-tex/${document.name}.tex`;
|
|
@@ -151,17 +154,13 @@ async function inventory(rootDir, options = {}) {
|
|
|
151
154
|
else if (directive.name !== document.name) {
|
|
152
155
|
own.push({ where: `${tex}:${directive.line}`, message: `\\vitadocument{${directive.name}} does not match the file name; expected \\vitadocument{${document.name}}` });
|
|
153
156
|
}
|
|
154
|
-
const placements =
|
|
157
|
+
const { placements } = document;
|
|
155
158
|
for (const placement of placements) {
|
|
156
|
-
if (!known.has(placement.section)) {
|
|
159
|
+
if (!known.has(placement.section) && placement.section !== "publications") {
|
|
157
160
|
own.push({ where: `${tex}:${placement.line}`, message: `no section called "${placement.section}"` });
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
|
-
|
|
161
|
-
if (!groupIds.has(group) && publications) {
|
|
162
|
-
own.push({ where: `cv-tex/${document.name}.yml`, message: `publications.groups: no publication group "${group}"` });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
163
|
+
own.push(...publicationPlacementProblems([], [...placements, ...document.publications], lists));
|
|
165
164
|
problems.push(...own);
|
|
166
165
|
documents.push({
|
|
167
166
|
name: document.name,
|
|
@@ -169,6 +168,7 @@ async function inventory(rootDir, options = {}) {
|
|
|
169
168
|
...existsSync(join(cvDir, `${document.name}.yml`)) ? { yml: `cv-tex/${document.name}.yml` } : {},
|
|
170
169
|
options: document.options,
|
|
171
170
|
placements,
|
|
171
|
+
publications: document.publications,
|
|
172
172
|
...pdf ? { pdf: { file: `cv-tex/${document.name}.pdf`, mtimeMs: pdf.mtimeMs } } : {},
|
|
173
173
|
problems: [...documentProblems.filter((problem) => problem.where === `cv-tex/${document.name}.yml`), ...own]
|
|
174
174
|
});
|
|
@@ -178,10 +178,12 @@ async function inventory(rootDir, options = {}) {
|
|
|
178
178
|
...await files(join(rootDir, "schemas"), () => true),
|
|
179
179
|
...await files(cvDir, (name) => /\.(tex|yml|yaml|eta|cls)$/.test(name)).then((list) => list.filter((path) => !relative(cvDir, path).startsWith("generated")))
|
|
180
180
|
]);
|
|
181
|
-
const
|
|
181
|
+
const none = () => ({ placements: [], publications: [], dynamic: [], components: /* @__PURE__ */ new Map() });
|
|
182
|
+
const web = options.web === false ? none() : await findWebPlacements(rootDir).catch((error) => {
|
|
182
183
|
problems.push({ where: whereOf(error, "app/"), message: messageOf(error) });
|
|
183
|
-
return
|
|
184
|
+
return none();
|
|
184
185
|
});
|
|
186
|
+
problems.push(...publicationPlacementProblems(web.publications, [], lists));
|
|
185
187
|
return {
|
|
186
188
|
root: rootDir,
|
|
187
189
|
sections,
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, sep } from 'node:path';
|
|
3
|
+
import { parseExpression } from '@babel/parser';
|
|
4
|
+
import { parse, parseDocument, isMap, isScalar } from 'yaml';
|
|
5
|
+
import { l as loadPublicationsSchema, d as defaultPublications } from './bibliography.mjs';
|
|
6
|
+
import { i as importProjectModule } from '../shared/vitakit.DCK90zq4.mjs';
|
|
7
|
+
import { addPublicationList, publicationListYaml } from './yaml.mjs';
|
|
8
|
+
import { r as renamePublicationsSettings } from '../shared/vitakit.CCTDg7pz.mjs';
|
|
9
|
+
import { publicationsElements } from './vue.mjs';
|
|
10
|
+
import { f as findPdfPlacements } from '../shared/vitakit.DvvUYUTS.mjs';
|
|
11
|
+
import { LISTS_FILE, LISTS_HEADER } from './publication-lists.mjs';
|
|
12
|
+
import 'node:fs';
|
|
13
|
+
import '../shared/vitakit.DQ6PmGoL.mjs';
|
|
14
|
+
import 'node:url';
|
|
15
|
+
import '@behackl/citation-js-extras';
|
|
16
|
+
import 'unified';
|
|
17
|
+
import 'rehype-parse';
|
|
18
|
+
import 'rehype-sanitize';
|
|
19
|
+
import 'rehype-stringify';
|
|
20
|
+
import 'remark-parse';
|
|
21
|
+
import 'remark-gfm';
|
|
22
|
+
import 'remark-math';
|
|
23
|
+
import 'remark-rehype';
|
|
24
|
+
import 'rehype-katex';
|
|
25
|
+
import 'rehype-raw';
|
|
26
|
+
import 'unist-util-visit';
|
|
27
|
+
import 'zod';
|
|
28
|
+
import '../shared/vitakit.UfoXdL7U.mjs';
|
|
29
|
+
import '@vue/compiler-sfc';
|
|
30
|
+
|
|
31
|
+
class ListPlan {
|
|
32
|
+
constructor(existing) {
|
|
33
|
+
this.existing = existing;
|
|
34
|
+
}
|
|
35
|
+
added = /* @__PURE__ */ new Map();
|
|
36
|
+
/** The name of a list with these settings: an existing one, or a new one. */
|
|
37
|
+
name(wanted, settings) {
|
|
38
|
+
const same = (other) => JSON.stringify(other ?? {}) === JSON.stringify(settings);
|
|
39
|
+
for (const [name2, other] of [...Object.entries(this.existing), ...this.added]) if (same(other)) return name2;
|
|
40
|
+
let name = wanted;
|
|
41
|
+
for (let index = 2; Object.hasOwn(this.existing, name) || this.added.has(name); index++) name = `${wanted}-${index}`;
|
|
42
|
+
this.added.set(name, settings);
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function statusList(statuses, grouped = true) {
|
|
47
|
+
return {
|
|
48
|
+
wanted: `${statuses?.join("-") ?? "all"}${grouped ? "" : "-ungrouped"}`,
|
|
49
|
+
settings: {
|
|
50
|
+
...statuses ? { where: { status: statuses.length === 1 ? statuses[0] : statuses } } : {},
|
|
51
|
+
...grouped ? { groupBy: "status" } : {}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function literalStrings(expression) {
|
|
56
|
+
try {
|
|
57
|
+
const node = parseExpression(expression);
|
|
58
|
+
if (node.type !== "ArrayExpression") return void 0;
|
|
59
|
+
const values = node.elements.map((element) => element?.type === "StringLiteral" ? element.value : void 0);
|
|
60
|
+
return values.every((value) => value !== void 0) ? values : void 0;
|
|
61
|
+
} catch {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function legacyList(element, statuses) {
|
|
66
|
+
const groupsProp = element.props.find((prop) => prop.name === "groups");
|
|
67
|
+
const headingsProp = element.props.find((prop) => prop.name === "headings");
|
|
68
|
+
const groups = groupsProp?.expression !== void 0 ? literalStrings(groupsProp.expression) : void 0;
|
|
69
|
+
if (groupsProp && !groups) return { reason: "its groups are not a literal list such as ['preprint']" };
|
|
70
|
+
const headings = !headingsProp || headingsProp.expression === "true" || headingsProp.expression === void 0 && headingsProp.value === void 0;
|
|
71
|
+
if (headingsProp && !headings && headingsProp.expression !== "false") return { reason: "its headings are not literally true or false" };
|
|
72
|
+
const list = statusList(groups, headings);
|
|
73
|
+
const reordered = headings && groups && groups.join() !== statuses.filter((status) => groups.includes(status)).join();
|
|
74
|
+
const merged = !headings && (groups?.length ?? 2) > 1;
|
|
75
|
+
const note = reordered ? "its groups now appear in the order of the statuses in schemas/publications.ts" : merged ? "it is now one list, newest first, rather than one status after the other" : void 0;
|
|
76
|
+
return { ...list, ...note ? { note } : {} };
|
|
77
|
+
}
|
|
78
|
+
async function statusOrder(root) {
|
|
79
|
+
const ids = (statuses) => statuses.map((status) => status.id);
|
|
80
|
+
try {
|
|
81
|
+
return ids((await loadPublicationsSchema(root)).statuses);
|
|
82
|
+
} catch {
|
|
83
|
+
const path = join(root, "schemas", "publications.ts");
|
|
84
|
+
const old = await stat(path).then((info) => importProjectModule(path, info.mtimeMs, "schemas/publications.ts")).then((module) => module.default?.groups, () => void 0);
|
|
85
|
+
return Array.isArray(old) ? ids(old) : ids(defaultPublications.statuses);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function vueFiles(root, dir = join(root, "app")) {
|
|
89
|
+
const files = [];
|
|
90
|
+
for (const entry of await readdir(dir, { withFileTypes: true }).catch(() => [])) {
|
|
91
|
+
const path = join(dir, entry.name);
|
|
92
|
+
if (entry.isDirectory()) files.push(...await vueFiles(root, path));
|
|
93
|
+
else if (entry.name.endsWith(".vue")) files.push(relative(root, path).split(sep).join("/"));
|
|
94
|
+
}
|
|
95
|
+
return files.sort();
|
|
96
|
+
}
|
|
97
|
+
async function migrateSchema(ws, notes) {
|
|
98
|
+
const path = "schemas/publications.ts";
|
|
99
|
+
const source = await ws.read(path);
|
|
100
|
+
if (source === void 0 || !/\b(groups|defaultGroup)\s*:/.test(source)) return;
|
|
101
|
+
const renamed = renamePublicationsSettings(source, { groups: "statuses", defaultGroup: "defaultStatus" });
|
|
102
|
+
if (!renamed.ok) notes.after.push(`By hand: in ${path}, rename groups to statuses and defaultGroup to defaultStatus (${renamed.reason}).`);
|
|
103
|
+
else if (renamed.value !== source) await ws.edit(path, renamed.value, "groups is now statuses, defaultGroup defaultStatus");
|
|
104
|
+
}
|
|
105
|
+
async function migratePages(root, ws, lists, notes) {
|
|
106
|
+
const statuses = await statusOrder(root);
|
|
107
|
+
for (const path of await vueFiles(root)) {
|
|
108
|
+
const source = await ws.read(path);
|
|
109
|
+
if (/(^|\/)VitaPublications\.vue$/.test(path)) {
|
|
110
|
+
notes.after.push(`By hand: ${path} is your copy of VitaPublications. Its props are now list and title, and it reads usePublicationList(list); \`vitakit eject VitaPublications\` shows vitakit's.`);
|
|
111
|
+
}
|
|
112
|
+
if (/\busePublications\s*\(/.test(source)) {
|
|
113
|
+
notes.after.push(`By hand: ${path} calls usePublications(); use usePublicationList('<list>') with a list from ${LISTS_FILE}.`);
|
|
114
|
+
}
|
|
115
|
+
const found = publicationsElements(source);
|
|
116
|
+
if (!found.ok) {
|
|
117
|
+
if (source.includes("VitaPublications")) notes.after.push(`By hand: ${path} ${found.reason}; check its VitaPublications.`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const edits = [];
|
|
121
|
+
const shown = [];
|
|
122
|
+
for (const element of found.value) {
|
|
123
|
+
if (element.props.some((prop) => prop.name === "list")) continue;
|
|
124
|
+
const old = element.props.filter((prop) => prop.name === "groups" || prop.name === "headings");
|
|
125
|
+
const legacy = old.length ? legacyList(element, statuses) : statusList();
|
|
126
|
+
if ("reason" in legacy) {
|
|
127
|
+
notes.after.push(`By hand: ${path}:${element.line}: ${legacy.reason}. Declare the list in ${LISTS_FILE} and write <VitaPublications list="<name>" />.`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const name = lists.name(legacy.wanted, legacy.settings);
|
|
131
|
+
for (const prop of old) {
|
|
132
|
+
let start = prop.start;
|
|
133
|
+
while (start > 0 && /\s/.test(source[start - 1])) start--;
|
|
134
|
+
edits.push({ start, end: prop.end, text: "" });
|
|
135
|
+
}
|
|
136
|
+
const afterTag = element.start + 1 + element.tag.length;
|
|
137
|
+
edits.push({ start: afterTag, end: afterTag, text: ` list="${name}"` });
|
|
138
|
+
shown.push(name);
|
|
139
|
+
if ("note" in legacy && legacy.note) notes.before.push(`${path}:${element.line} shows the list ${name}: ${legacy.note}.`);
|
|
140
|
+
}
|
|
141
|
+
if (!edits.length) continue;
|
|
142
|
+
let result = source;
|
|
143
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
144
|
+
await ws.edit(path, result, `VitaPublications shows ${[...new Set(shown)].join(", ")}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function migrateDocuments(root, ws, lists, notes) {
|
|
148
|
+
const files = (await readdir(join(root, "cv-tex")).catch(() => [])).filter((name) => name.endsWith(".tex")).sort();
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
const tex = `cv-tex/${file}`;
|
|
151
|
+
const yml = tex.replace(/\.tex$/, ".yml");
|
|
152
|
+
let list = statusList();
|
|
153
|
+
const options = await ws.read(yml);
|
|
154
|
+
const document = options === void 0 ? void 0 : parseDocument(options);
|
|
155
|
+
const pair = document && isMap(document.contents) ? document.contents.items.find((item) => isScalar(item.key) && item.key.value === "publications") : void 0;
|
|
156
|
+
if (options !== void 0 && pair) {
|
|
157
|
+
const groups = document.toJS().publications?.groups;
|
|
158
|
+
if (Array.isArray(groups) && groups.every((group) => typeof group === "string")) list = statusList(groups);
|
|
159
|
+
const start = pair.key.range?.[0];
|
|
160
|
+
const end = pair.value?.range?.[1];
|
|
161
|
+
if (start === void 0 || end === void 0) {
|
|
162
|
+
notes.after.push(`By hand: remove publications from ${yml}; which publications the PDF shows is now a list.`);
|
|
163
|
+
} else {
|
|
164
|
+
const lines2 = options.split("\n");
|
|
165
|
+
const lineOf = (offset) => options.slice(0, offset).split("\n").length;
|
|
166
|
+
lines2.splice(lineOf(start) - 1, lineOf(Math.max(start, end - 1)) - lineOf(start) + 1);
|
|
167
|
+
await ws.edit(yml, lines2.join("\n"), `publications are now a list in ${LISTS_FILE}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const source = await ws.read(tex);
|
|
171
|
+
const inputs = findPdfPlacements("", tex, source).filter((placement) => placement.macro === "vitainput" && placement.section === "publications");
|
|
172
|
+
if (!inputs.length) continue;
|
|
173
|
+
const name = lists.name(list.wanted, list.settings);
|
|
174
|
+
const lines = source.split("\n");
|
|
175
|
+
for (const input of [...inputs].reverse()) {
|
|
176
|
+
const index = input.line - 1;
|
|
177
|
+
const heading = index > 0 ? /^\s*\\section\{([^{}]*)\}\s*$/.exec(lines[index - 1])?.[1] : void 0;
|
|
178
|
+
lines[index] = lines[index].replace("\\vitainput{publications}", `\\vitapublications${heading !== void 0 ? `[${heading}]` : ""}{${name}}`);
|
|
179
|
+
if (heading !== void 0) lines.splice(index - 1, 1);
|
|
180
|
+
}
|
|
181
|
+
await ws.edit(tex, lines.join("\n"), `shows the publication list ${name}`);
|
|
182
|
+
notes.before.push(`${tex} shows the list ${name}: in the PDF too, publications now come in the list's order, as on the website (biblatex sorted them by year and author before), and each group is numbered from 1.`);
|
|
183
|
+
}
|
|
184
|
+
if (await ws.read("cv-tex/templates/publications.eta") !== void 0) {
|
|
185
|
+
notes.after.push("By hand: cv-tex/templates/publications.eta is your copy of the PDF template. vitakit's now cites a list in its order, with `<%~ it.bib.cite() %>` instead of `\\nocite{*}`, inside `\\begin{refcontext}[sorting=none]`.");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function migratePublicationLists(root, ws, notes) {
|
|
189
|
+
await migrateSchema(ws, notes);
|
|
190
|
+
const source = await ws.read(LISTS_FILE);
|
|
191
|
+
let existing;
|
|
192
|
+
try {
|
|
193
|
+
existing = source === void 0 ? {} : parse(source) ?? {};
|
|
194
|
+
} catch {
|
|
195
|
+
notes.after.push(`By hand: ${LISTS_FILE} cannot be read; fix it, then run \`vitakit migrate\` again.`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const lists = new ListPlan(typeof existing === "object" && !Array.isArray(existing) ? existing : {});
|
|
199
|
+
await migratePages(root, ws, lists, notes);
|
|
200
|
+
await migrateDocuments(root, ws, lists, notes);
|
|
201
|
+
if (!lists.added.size) return;
|
|
202
|
+
let lines = source ?? LISTS_HEADER;
|
|
203
|
+
for (const [name, settings] of lists.added) {
|
|
204
|
+
const added = addPublicationList(lines, name, settings);
|
|
205
|
+
if (!added.ok) {
|
|
206
|
+
notes.after.push(`By hand: add to ${LISTS_FILE} (${added.reason}):
|
|
207
|
+
${[...lists.added].map(([other, value]) => publicationListYaml(other, value)).join("\n")}`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
lines = added.value;
|
|
211
|
+
}
|
|
212
|
+
notes.after.push(`The new lists are named after what they show. To rename one, change its name in ${LISTS_FILE} and where it is shown.`);
|
|
213
|
+
const reason = `declares ${[...lists.added.keys()].join(", ")}`;
|
|
214
|
+
if (source === void 0) ws.create(LISTS_FILE, lines, reason, { show: true });
|
|
215
|
+
else await ws.edit(LISTS_FILE, lines, reason);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export { migratePublicationLists };
|
package/dist/chunks/plan.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readFile, mkdir, writeFile, rename } from 'node:fs/promises';
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
|
-
import { C as CommandError } from '../shared/vitakit.
|
|
4
|
+
import { C as CommandError } from '../shared/vitakit.zwr1ssO5.mjs';
|
|
5
5
|
import '../shared/vitakit.DQ6PmGoL.mjs';
|
|
6
6
|
import 'node:url';
|
|
7
7
|
import 'node:process';
|
|
@@ -20,13 +20,41 @@ function diff(before, after, context = 2) {
|
|
|
20
20
|
endA--;
|
|
21
21
|
endB--;
|
|
22
22
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
...
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
const midA = a.slice(start, endA);
|
|
24
|
+
const midB = b.slice(start, endB);
|
|
25
|
+
const ops = a.slice(0, start).map((line) => ({ kind: " ", line }));
|
|
26
|
+
if (midA.length * midB.length > 1e6) {
|
|
27
|
+
ops.push(...midA.map((line) => ({ kind: "-", line })), ...midB.map((line) => ({ kind: "+", line })));
|
|
28
|
+
} else {
|
|
29
|
+
const lengths = Array.from({ length: midA.length + 1 }, () => new Array(midB.length + 1).fill(0));
|
|
30
|
+
for (let i2 = midA.length - 1; i2 >= 0; i2--) {
|
|
31
|
+
for (let j2 = midB.length - 1; j2 >= 0; j2--) {
|
|
32
|
+
lengths[i2][j2] = midA[i2] === midB[j2] ? lengths[i2 + 1][j2 + 1] + 1 : Math.max(lengths[i2 + 1][j2], lengths[i2][j2 + 1]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
let i = 0;
|
|
36
|
+
let j = 0;
|
|
37
|
+
while (i < midA.length || j < midB.length) {
|
|
38
|
+
if (i < midA.length && j < midB.length && midA[i] === midB[j]) {
|
|
39
|
+
ops.push({ kind: " ", line: midA[i] });
|
|
40
|
+
i++;
|
|
41
|
+
j++;
|
|
42
|
+
} else if (i < midA.length && (j === midB.length || lengths[i + 1][j] >= lengths[i][j + 1])) {
|
|
43
|
+
ops.push({ kind: "-", line: midA[i++] });
|
|
44
|
+
} else {
|
|
45
|
+
ops.push({ kind: "+", line: midB[j++] });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
ops.push(...a.slice(endA).map((line) => ({ kind: " ", line })));
|
|
50
|
+
const shown = ops.map((op, index) => op.kind !== " " || ops.slice(Math.max(0, index - context), index + context + 1).some((near) => near.kind !== " "));
|
|
51
|
+
const lines = [];
|
|
52
|
+
ops.forEach((op, index) => {
|
|
53
|
+
if (!shown[index]) return;
|
|
54
|
+
if (lines.length && !shown[index - 1]) lines.push(" \u2026");
|
|
55
|
+
lines.push(` ${op.kind === " " ? " " : op.kind} ${op.line}`);
|
|
56
|
+
});
|
|
57
|
+
return lines;
|
|
30
58
|
}
|
|
31
59
|
function printPlan(io, style, plan) {
|
|
32
60
|
const lines = [];
|