@anja.codes/vitakit 0.1.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 +52 -131
- package/dist/check.mjs +20 -11
- package/dist/chunks/authoring.mjs +372 -0
- package/dist/{shared/vitakit.CypCF-4Q.mjs → chunks/bibliography.mjs} +74 -237
- package/dist/chunks/build.mjs +178 -0
- package/dist/chunks/documents.mjs +152 -0
- package/dist/chunks/draft.mjs +222 -0
- package/dist/chunks/eject.mjs +59 -0
- package/dist/chunks/emit.mjs +216 -0
- package/dist/chunks/eta.mjs +16 -0
- package/dist/chunks/fields.mjs +79 -0
- package/dist/chunks/inventory.mjs +201 -0
- package/dist/chunks/migrations.mjs +218 -0
- package/dist/chunks/plan.mjs +172 -0
- package/dist/chunks/prompt.mjs +33 -0
- package/dist/chunks/publication-lists.mjs +202 -0
- package/dist/chunks/schema-source.mjs +12 -0
- package/dist/chunks/schemas.mjs +109 -0
- package/dist/chunks/sections.mjs +109 -0
- package/dist/chunks/snippets.mjs +199 -0
- package/dist/chunks/validate.mjs +133 -0
- package/dist/chunks/vue.mjs +153 -0
- package/dist/chunks/yaml.mjs +62 -0
- package/dist/cli.d.mts +33 -40
- package/dist/cli.mjs +7 -224
- package/dist/index.d.mts +379 -49
- package/dist/index.mjs +15 -7
- package/dist/nuxt.mjs +48 -10
- package/dist/runtime/components/VitaPublications.vue +21 -22
- package/dist/runtime/components/VitaSection.vue +94 -38
- package/dist/runtime/composables/useVitakit.ts +14 -3
- package/dist/schemas/education.ts +4 -4
- package/dist/schemas/employment.ts +3 -3
- package/dist/schemas/publications.ts +10 -8
- package/dist/schemas/supervision.ts +3 -3
- package/dist/schemas/talks.ts +1 -1
- package/dist/schemas/teaching.ts +3 -3
- package/dist/shared/index.d.mts +60 -9
- package/dist/shared/index.mjs +3 -57
- package/dist/shared/{vitakit.D8WRg_Xx.d.mts → vitakit.BfR_mUHm.d.mts} +25 -12
- package/dist/shared/vitakit.CCTDg7pz.mjs +199 -0
- package/dist/shared/vitakit.CfB3hMHl.mjs +60 -0
- package/dist/shared/{vitakit.BlU-p01e.mjs → vitakit.CtChKO0I.mjs} +1 -1
- package/dist/shared/vitakit.D-DdJaDZ.mjs +20 -0
- package/dist/shared/vitakit.D10HyDhF.mjs +19 -0
- package/dist/shared/vitakit.DCK90zq4.mjs +67 -0
- package/dist/shared/vitakit.DQ6PmGoL.mjs +34 -0
- package/dist/shared/vitakit.DcQOdM4I.mjs +57 -0
- package/dist/shared/vitakit.DteA9vLy.mjs +135 -0
- package/dist/shared/vitakit.DvABWuUc.mjs +121 -0
- package/dist/shared/vitakit.DvvUYUTS.mjs +200 -0
- package/dist/shared/vitakit.TSMTC5Jl.mjs +6 -0
- package/dist/shared/vitakit.UfoXdL7U.mjs +22 -0
- package/dist/shared/vitakit.zwr1ssO5.mjs +1850 -0
- package/dist/tex/templates/_generic.eta +6 -4
- package/dist/tex/templates/education.eta +10 -5
- package/dist/tex/templates/employment.eta +12 -5
- package/dist/tex/templates/publications.eta +7 -3
- package/dist/tex/templates/supervision.eta +10 -2
- package/dist/tex/templates/talks.eta +5 -1
- package/dist/tex/templates/teaching.eta +6 -4
- package/dist/tex/vitakit-cv.cls +36 -10
- package/dist/tex.d.mts +80 -11
- package/dist/tex.mjs +27 -117
- package/package.json +10 -7
- package/src/bin/vitakit.mjs +3 -153
- package/dist/shared/vitakit.BRhjqpAt.mjs +0 -250
- package/dist/shared/vitakit.BxcnlFGP.mjs +0 -325
- package/src/bin/vitakit-build-cv.mjs +0 -50
- package/src/bin/vitakit-check.mjs +0 -57
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<% for (const item of it.entries) { %>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<%~ it.vita.entry({
|
|
3
|
+
date: it.date(item.date),
|
|
4
|
+
title: it.generic.title(item),
|
|
5
|
+
context: it.generic.context(item)
|
|
6
|
+
}) %>
|
|
7
|
+
<%~ it.vita.note(item.note) %>
|
|
6
8
|
<% } %>
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
<% for (const item of it.entries) { %>
|
|
2
|
-
|
|
2
|
+
<%~ it.vita.entry({
|
|
3
|
+
date: it.date(item.date),
|
|
4
|
+
title: item.degree,
|
|
5
|
+
context: it.linked(item.institution)
|
|
6
|
+
}) %>
|
|
3
7
|
<% if (item.thesis) { %>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
<%~ it.vita.note(it.join([
|
|
9
|
+
it.optional`${item.thesis.type ?? 'Thesis'}: \emph{${item.thesis.title}}`,
|
|
10
|
+
it.optional`, supervised by ${it.linked(item.thesis.supervisor)}`
|
|
11
|
+
], '')) %>
|
|
8
12
|
<% } %>
|
|
13
|
+
<%~ it.vita.note(item.note) %>
|
|
9
14
|
<% } %>
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
<% for (const item of it.entries) { %>
|
|
2
|
-
|
|
2
|
+
<%~ it.vita.entry({
|
|
3
|
+
date: it.date(item.date),
|
|
4
|
+
title: item.position,
|
|
5
|
+
context: it.linked(item.institution)
|
|
6
|
+
}) %>
|
|
3
7
|
<% if (item.project) { %>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
<%~ it.vita.note(it.join([
|
|
9
|
+
it.optional`Project \emph{${it.linked(item.project.title, item.project.url)}}`,
|
|
10
|
+
item.project.funder
|
|
11
|
+
? it.optional` (${it.join([item.project.funder, item.project.grant], it.optional`~`)})`
|
|
12
|
+
: ''
|
|
13
|
+
], '')) %>
|
|
8
14
|
<% } %>
|
|
15
|
+
<%~ it.vita.note(item.note) %>
|
|
9
16
|
<% } %>
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
<% if (it.groups.length) { %>
|
|
1
2
|
\begin{refsection}
|
|
2
|
-
\
|
|
3
|
+
\begin{refcontext}[sorting=none]
|
|
4
|
+
<%~ it.bib.cite() %>
|
|
3
5
|
<% for (const group of it.groups) { %>
|
|
4
|
-
<%~ it.
|
|
5
|
-
\printbibliography[check=vita-<%~ group.id %>, title={<%~ it.
|
|
6
|
+
<%~ it.bib.group(group) %>
|
|
7
|
+
\printbibliography[resetnumbers=true, check=vita-<%~ group.id %>, <% if (group.label !== undefined) { %>heading=subbibliography, title={<%~ it.text(group.label) %>}<% } else { %>heading=none<% } %>]
|
|
6
8
|
<% } %>
|
|
9
|
+
\end{refcontext}
|
|
7
10
|
\end{refsection}
|
|
11
|
+
<% } %>
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
<% for (const item of it.entries) { %>
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
<%~ it.vita.entry({
|
|
3
|
+
date: it.join([it.date(item.date), item.ongoing ? '--' : ''], ''),
|
|
4
|
+
title: item.student,
|
|
5
|
+
context: it.join([item.degree, it.linked(item.institution)])
|
|
6
|
+
}) %>
|
|
7
|
+
<%~ it.vita.note(it.join([
|
|
8
|
+
it.optional`\emph{${item.title}}`,
|
|
9
|
+
it.optional`, with ${item.coSupervisor}`,
|
|
10
|
+
item.ongoing ? ' (in progress)' : ''
|
|
11
|
+
], '')) %>
|
|
4
12
|
<% } %>
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
<% for (const item of it.entries) { %>
|
|
2
|
-
|
|
2
|
+
<%~ it.vita.entry({
|
|
3
|
+
date: it.date(item.date),
|
|
4
|
+
title: item.title,
|
|
5
|
+
context: it.join([item.event, item.location])
|
|
6
|
+
}) %>
|
|
3
7
|
<% } %>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<% for (const item of it.entries) { %>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<%~ it.vita.entry({
|
|
3
|
+
date: it.text(item.term) || it.date(item.date),
|
|
4
|
+
title: item.title,
|
|
5
|
+
context: it.join([item.role, it.linked(item.institution)])
|
|
6
|
+
}) %>
|
|
7
|
+
<%~ it.vita.note(item.note) %>
|
|
6
8
|
<% } %>
|
package/dist/tex/vitakit-cv.cls
CHANGED
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
\ProcessOptions\relax
|
|
14
14
|
\LoadClass[11pt,a4paper]{article}
|
|
15
15
|
|
|
16
|
+
%% pdfLaTeX's default font encoding (OT1) has no „, ‚, ł, ő and many other
|
|
17
|
+
%% characters of names, places and titles; T1 has them. LuaLaTeX and XeLaTeX
|
|
18
|
+
%% use Unicode fonts instead.
|
|
19
|
+
\RequirePackage{iftex}
|
|
20
|
+
\ifPDFTeX
|
|
21
|
+
\RequirePackage[T1]{fontenc}
|
|
22
|
+
\RequirePackage{lmodern}
|
|
23
|
+
\fi
|
|
24
|
+
|
|
16
25
|
\RequirePackage[margin=2.5cm]{geometry}
|
|
17
26
|
\RequirePackage{enumitem}
|
|
18
27
|
\RequirePackage{xcolor}
|
|
@@ -27,10 +36,10 @@
|
|
|
27
36
|
\RequirePackage[backend=biber, style=numeric, sorting=ydnt,
|
|
28
37
|
defernumbers=true, maxbibnames=99, giveninits=true]{biblatex}
|
|
29
38
|
%% \vitabibgroup{id}{key,key,...} defines the bibliography check `vita-id`,
|
|
30
|
-
%% which keeps exactly the listed entries.
|
|
31
|
-
%%
|
|
32
|
-
%%
|
|
33
|
-
%%
|
|
39
|
+
%% which keeps exactly the listed entries. A generated list cites its keys
|
|
40
|
+
%% in the list's order and prints one bibliography per group with
|
|
41
|
+
%% sorting=none, so the PDF lists what the website lists, in its order.
|
|
42
|
+
%% Keys are compared as strings.
|
|
34
43
|
\newcommand{\vitabibgroup}[2]{%
|
|
35
44
|
\defbibcheck{vita-#1}{%
|
|
36
45
|
\edef\vita@bibkey{\thefield{entrykey}}%
|
|
@@ -105,15 +114,32 @@
|
|
|
105
114
|
%% that it is safe after any preceding content, including none.
|
|
106
115
|
\newcommand{\vitanote}[1]{\par{\small\color{vitamuted}#1}}
|
|
107
116
|
|
|
108
|
-
%%
|
|
109
|
-
\newcommand{\
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
%% Generated profile.tex marks sections whose selection is empty.
|
|
118
|
+
\newcommand{\vitaemptysection}[1]{\expandafter\def\csname vita@empty@#1\endcsname{}}
|
|
119
|
+
|
|
120
|
+
%% \vitasection[Heading]{id} -- an optional heading for this invocation only.
|
|
121
|
+
%% Otherwise use the document/preamble label, falling back to the section id.
|
|
122
|
+
\NewDocumentCommand{\vitasection}{o m}{%
|
|
123
|
+
\ifcsname vita@empty@#2\endcsname\else
|
|
124
|
+
\IfNoValueTF{#1}{\section{\vita@titlefor{#2}}}{\section{#1}}%
|
|
125
|
+
\begin{vitaentries}%
|
|
126
|
+
\input{generated/\vita@document/#2.tex}%
|
|
127
|
+
\end{vitaentries}%
|
|
128
|
+
\fi}
|
|
114
129
|
|
|
115
130
|
%% \vitainput{id} -- the generated file without heading or list wrapper,
|
|
116
131
|
%% for sections that bring their own structure (publications).
|
|
117
132
|
\newcommand{\vitainput}[1]{\input{generated/\vita@document/#1.tex}}
|
|
118
133
|
|
|
134
|
+
%% Generated profile.tex marks publication lists without entries.
|
|
135
|
+
\newcommand{\vitaemptypublications}[1]{\expandafter\def\csname vita@emptylist@#1\endcsname{}}
|
|
136
|
+
|
|
137
|
+
%% \vitapublications[Heading]{list} -- a list from content/publication-lists.yml,
|
|
138
|
+
%% with an optional heading; neither appears when the list has no entries.
|
|
139
|
+
\NewDocumentCommand{\vitapublications}{o m}{%
|
|
140
|
+
\ifcsname vita@emptylist@#2\endcsname\else
|
|
141
|
+
\IfNoValueF{#1}{\section{#1}}%
|
|
142
|
+
\input{generated/\vita@document/publications/#2.tex}%
|
|
143
|
+
\fi}
|
|
144
|
+
|
|
119
145
|
\endinput
|
package/dist/tex.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { Entry,
|
|
2
|
+
import { Entry, PublicationList } from './index.mjs';
|
|
3
3
|
|
|
4
4
|
interface BuildOptions {
|
|
5
5
|
rootDir: string;
|
|
@@ -7,11 +7,52 @@ interface BuildOptions {
|
|
|
7
7
|
only?: string[];
|
|
8
8
|
/** Emit .tex but skip latexmk. */
|
|
9
9
|
noPdf?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* What happens to latexmk's output: shown as it runs (`inherit`, the
|
|
12
|
+
* default), or kept back and only its end shown on failure (`capture`).
|
|
13
|
+
*/
|
|
14
|
+
latexOutput?: 'inherit' | 'capture';
|
|
15
|
+
/** Told about each finished step, for concise progress output. */
|
|
16
|
+
onProgress?: (event: BuildEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
type BuildEvent = {
|
|
19
|
+
document: string;
|
|
20
|
+
step: 'tex';
|
|
21
|
+
dir: string;
|
|
22
|
+
files: number;
|
|
23
|
+
} | {
|
|
24
|
+
document: string;
|
|
25
|
+
step: 'pdf';
|
|
26
|
+
file: string;
|
|
27
|
+
};
|
|
28
|
+
/** A tool of the TeX distribution is not installed. */
|
|
29
|
+
declare class MissingToolError extends Error {
|
|
30
|
+
readonly tool: string;
|
|
31
|
+
constructor(tool: string);
|
|
10
32
|
}
|
|
11
33
|
declare function buildCv(options: BuildOptions): Promise<string[]>;
|
|
12
34
|
/** Read a generated file, for tests and diagnostics. */
|
|
13
35
|
declare function readGenerated(rootDir: string, document: string, section: string): Promise<string>;
|
|
14
36
|
|
|
37
|
+
interface PdfPlacement {
|
|
38
|
+
section: string;
|
|
39
|
+
document: string;
|
|
40
|
+
/** `cv-tex/cv.tex`. */
|
|
41
|
+
file: string;
|
|
42
|
+
line: number;
|
|
43
|
+
macro: 'vitasection' | 'vitainput';
|
|
44
|
+
/** The optional heading of `\vitasection[Heading]{id}`. */
|
|
45
|
+
heading?: string;
|
|
46
|
+
}
|
|
47
|
+
/** A `\vitapublications[Heading]{list}` line. */
|
|
48
|
+
interface PdfPublicationPlacement {
|
|
49
|
+
list: string;
|
|
50
|
+
document: string;
|
|
51
|
+
file: string;
|
|
52
|
+
line: number;
|
|
53
|
+
heading?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
15
56
|
declare const documentSchema: z.ZodObject<{
|
|
16
57
|
visibility: z.ZodDefault<z.ZodEnum<{
|
|
17
58
|
public: "public";
|
|
@@ -19,15 +60,12 @@ declare const documentSchema: z.ZodObject<{
|
|
|
19
60
|
private: "private";
|
|
20
61
|
}>>;
|
|
21
62
|
sections: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
22
|
-
where: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString
|
|
63
|
+
where: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>]>>>;
|
|
23
64
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
24
65
|
since: z.ZodOptional<z.ZodNumber>;
|
|
25
66
|
until: z.ZodOptional<z.ZodNumber>;
|
|
26
67
|
title: z.ZodOptional<z.ZodString>;
|
|
27
68
|
}, z.core.$strict>>>;
|
|
28
|
-
publications: z.ZodDefault<z.ZodObject<{
|
|
29
|
-
groups: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
30
|
-
}, z.core.$strict>>;
|
|
31
69
|
}, z.core.$strict>;
|
|
32
70
|
type DocumentOptions = z.infer<typeof documentSchema>;
|
|
33
71
|
interface DocumentSource {
|
|
@@ -35,17 +73,40 @@ interface DocumentSource {
|
|
|
35
73
|
name: string;
|
|
36
74
|
texFile: string;
|
|
37
75
|
options: DocumentOptions;
|
|
76
|
+
/** Its `\vitasection` and `\vitainput` lines. */
|
|
77
|
+
placements: PdfPlacement[];
|
|
78
|
+
/** Its `\vitapublications` lines. */
|
|
79
|
+
publications: PdfPublicationPlacement[];
|
|
38
80
|
}
|
|
39
|
-
/** Read every `cv-tex/*.tex` and its options. */
|
|
81
|
+
/** Read every `cv-tex/*.tex` and its options. Strict: the first problem throws. */
|
|
40
82
|
declare function discoverDocuments(cvDir: string): Promise<DocumentSource[]>;
|
|
41
83
|
/** Select the entries of one section for one document. */
|
|
42
84
|
declare function selectEntries(entries: Entry[], options: DocumentOptions, sectionId: string): Entry[];
|
|
43
85
|
|
|
44
86
|
/** `tex/` in the installed package: templates and the document class. */
|
|
45
87
|
declare const bundledTexDir: string;
|
|
88
|
+
/** Where a template came from, in the order they are tried. */
|
|
89
|
+
interface ResolvedTemplate {
|
|
90
|
+
path: string;
|
|
91
|
+
source: string;
|
|
92
|
+
/** `local` is in `cv-tex/templates/`, `bundled` is the package's own. */
|
|
93
|
+
origin: 'local' | 'bundled';
|
|
94
|
+
/** Whether this is `_generic.eta` rather than a template named for the section. */
|
|
95
|
+
generic: boolean;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The template that renders section `id`. A file in the repo always wins over
|
|
99
|
+
* the package default; `_generic.eta` is the fallback for sections without a
|
|
100
|
+
* dedicated template. `exclude` skips one path, so that replacing a local
|
|
101
|
+
* template can start from whatever would be used without it.
|
|
102
|
+
*/
|
|
103
|
+
declare function resolveTemplate(id: string, userDir: string, { exclude }?: {
|
|
104
|
+
exclude?: string;
|
|
105
|
+
}): Promise<ResolvedTemplate>;
|
|
46
106
|
interface EmitOptions {
|
|
47
107
|
sections: Record<string, Entry[]>;
|
|
48
|
-
|
|
108
|
+
/** The publication lists the document shows, by name. */
|
|
109
|
+
publications: Record<string, PublicationList>;
|
|
49
110
|
options: DocumentOptions;
|
|
50
111
|
/** `cv-tex/templates/`, where user overrides live. */
|
|
51
112
|
templateDir: string;
|
|
@@ -56,7 +117,14 @@ interface EmitOptions {
|
|
|
56
117
|
/** Path of the .bib file, relative to the generated directory. */
|
|
57
118
|
bibPath?: string;
|
|
58
119
|
}
|
|
59
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* Render one section with its effective template, without writing anything.
|
|
122
|
+
* `entries` are all loaded entries; the document's selection is applied here.
|
|
123
|
+
*/
|
|
124
|
+
declare function renderSection(id: string, entries: Entry[], options: DocumentOptions, templateDir: string): Promise<string>;
|
|
125
|
+
/** Render one publication list, without writing anything. */
|
|
126
|
+
declare function renderPublications(list: PublicationList, templateDir: string, siteUrl?: string): Promise<string>;
|
|
127
|
+
/** Write one `.tex` file per section, and one per publication list in `publications/`. */
|
|
60
128
|
declare function emitDocument(emit: EmitOptions): Promise<string[]>;
|
|
61
129
|
|
|
62
130
|
/**
|
|
@@ -84,10 +152,11 @@ declare function escapeTex(value: string, { typography, source }?: EscapeOptions
|
|
|
84
152
|
* Escape prose while preserving `$...$` and `$$...$$` spans verbatim.
|
|
85
153
|
*
|
|
86
154
|
* Used for `richText` fields, where a mathematician writes `Random $k$-ary
|
|
87
|
-
* trees` and expects the formula to reach LaTeX untouched.
|
|
155
|
+
* trees` and expects the formula to reach LaTeX untouched. The prose is
|
|
156
|
+
* Markdown, so `\$` is a dollar sign and does not start a formula.
|
|
88
157
|
*/
|
|
89
158
|
declare function escapeRichTex(value: string): string;
|
|
90
159
|
declare function escapeUrl(value: string): string;
|
|
91
160
|
|
|
92
|
-
export { buildCv, bundledTexDir, discoverDocuments, documentSchema, emitDocument, escapeRichTex, escapeTex, escapeUrl, readGenerated, selectEntries };
|
|
93
|
-
export type { BuildOptions, DocumentOptions, DocumentSource, EmitOptions, EscapeOptions };
|
|
161
|
+
export { MissingToolError, buildCv, bundledTexDir, discoverDocuments, documentSchema, emitDocument, escapeRichTex, escapeTex, escapeUrl, readGenerated, renderPublications, renderSection, resolveTemplate, selectEntries };
|
|
162
|
+
export type { BuildEvent, BuildOptions, DocumentOptions, DocumentSource, EmitOptions, EscapeOptions, ResolvedTemplate };
|
package/dist/tex.mjs
CHANGED
|
@@ -1,129 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
1
|
+
export { MissingToolError, buildCv, readGenerated } from './chunks/build.mjs';
|
|
2
|
+
export { bundledTexDir, emitDocument, renderPublications, renderSection, resolveTemplate } from './chunks/emit.mjs';
|
|
3
|
+
export { discoverDocuments, documentSchema, selectEntries } from './chunks/documents.mjs';
|
|
4
|
+
export { e as escapeRichTex, a as escapeTex, b as escapeUrl } from './shared/vitakit.DcQOdM4I.mjs';
|
|
5
|
+
import 'node:child_process';
|
|
6
|
+
import 'node:fs/promises';
|
|
7
|
+
import 'node:fs';
|
|
8
|
+
import 'node:process';
|
|
9
|
+
import 'node:path';
|
|
10
|
+
import './shared/vitakit.DteA9vLy.mjs';
|
|
10
11
|
import 'gray-matter';
|
|
11
12
|
import 'yaml';
|
|
13
|
+
import './chunks/bibliography.mjs';
|
|
14
|
+
import './shared/vitakit.DQ6PmGoL.mjs';
|
|
15
|
+
import 'node:url';
|
|
16
|
+
import '@behackl/citation-js-extras';
|
|
12
17
|
import 'unified';
|
|
18
|
+
import 'rehype-parse';
|
|
19
|
+
import 'rehype-sanitize';
|
|
20
|
+
import 'rehype-stringify';
|
|
13
21
|
import 'remark-parse';
|
|
14
22
|
import 'remark-gfm';
|
|
15
23
|
import 'remark-math';
|
|
16
24
|
import 'remark-rehype';
|
|
17
25
|
import 'rehype-katex';
|
|
18
26
|
import 'rehype-raw';
|
|
19
|
-
import 'rehype-stringify';
|
|
20
27
|
import 'unist-util-visit';
|
|
21
|
-
import '
|
|
22
|
-
import 'rehype-parse';
|
|
23
|
-
import 'rehype-sanitize';
|
|
28
|
+
import './shared/vitakit.DCK90zq4.mjs';
|
|
24
29
|
import 'zod';
|
|
25
|
-
import '
|
|
30
|
+
import './chunks/publication-lists.mjs';
|
|
31
|
+
import './shared/vitakit.D10HyDhF.mjs';
|
|
32
|
+
import './chunks/sections.mjs';
|
|
33
|
+
import './chunks/schemas.mjs';
|
|
34
|
+
import './shared/vitakit.DvABWuUc.mjs';
|
|
35
|
+
import './shared/vitakit.TSMTC5Jl.mjs';
|
|
36
|
+
import './shared/vitakit.DvvUYUTS.mjs';
|
|
26
37
|
import 'eta';
|
|
27
|
-
import './shared/
|
|
28
|
-
import './shared/vitakit.
|
|
29
|
-
|
|
30
|
-
const CLASS_FILE = "vitakit-cv.cls";
|
|
31
|
-
function run(command, args, cwd, env = {}) {
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
const child = spawn(command, args, { cwd, stdio: "inherit", env: { ...process.env, ...env } });
|
|
34
|
-
child.on("error", (error) => reject(
|
|
35
|
-
error.code === "ENOENT" ? new Error(`${command} not found. Install a TeX distribution, or use --no-pdf to only generate .tex files.`) : error
|
|
36
|
-
));
|
|
37
|
-
child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`${command} exited with code ${code}`)));
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
function profilePreamble(content) {
|
|
41
|
-
const { profile } = content;
|
|
42
|
-
const href = (url) => {
|
|
43
|
-
if (isSitePath(url)) fail("content/profile.yml", `links: ${url} ${SITE_PATH_HELP}`);
|
|
44
|
-
return escapeUrl(url);
|
|
45
|
-
};
|
|
46
|
-
const affiliation = profile.affiliation?.name ? escapeTex(profile.affiliation.name) : "";
|
|
47
|
-
const tagline = [profile.title && escapeTex(profile.title), affiliation].filter(Boolean).join(", ");
|
|
48
|
-
const contact = [
|
|
49
|
-
profile.email && `\\href{mailto:${escapeUrl(profile.email)}}{${escapeTex(profile.email)}}`,
|
|
50
|
-
...profile.links.map((link) => `\\href{${href(link.url)}}{${escapeTex(link.label)}}`)
|
|
51
|
-
].filter(Boolean).join(" \\quad ");
|
|
52
|
-
return [
|
|
53
|
-
"% Generated by vitakit. Do not edit; edit content/profile.yml instead.",
|
|
54
|
-
`\\vitaname{${escapeTex(profile.name)}}`,
|
|
55
|
-
`\\vitatagline{${tagline}}`,
|
|
56
|
-
`\\vitacontact{${contact}}`,
|
|
57
|
-
...profile.url ? siteUrlSourcemap(profile.url) : [],
|
|
58
|
-
""
|
|
59
|
-
].join("\n");
|
|
60
|
-
}
|
|
61
|
-
function siteUrlSourcemap(siteUrl) {
|
|
62
|
-
const base = siteUrl.replace(/\/?$/, "/");
|
|
63
|
-
if (!/^https?:\/\/[\w.:/-]+$/.test(base)) {
|
|
64
|
-
fail("content/profile.yml", `url: ${siteUrl} may only contain letters, digits and - . _ : /`);
|
|
65
|
-
}
|
|
66
|
-
return [
|
|
67
|
-
"\\ifdefined\\DeclareSourcemap",
|
|
68
|
-
` \\DeclareSourcemap{\\maps[datatype=bibtex]{\\map{\\step[fieldsource=url, match=\\regexp{\\A/}, replace={${base}}]}}}`,
|
|
69
|
-
"\\fi"
|
|
70
|
-
];
|
|
71
|
-
}
|
|
72
|
-
async function buildDocument(document, content, options, cvDir) {
|
|
73
|
-
const outDir = join(cvDir, "generated", document.name);
|
|
74
|
-
await emitDocument({
|
|
75
|
-
sections: content.sections,
|
|
76
|
-
publications: content.publications,
|
|
77
|
-
siteUrl: content.profile.url,
|
|
78
|
-
options: document.options,
|
|
79
|
-
templateDir: join(cvDir, "templates"),
|
|
80
|
-
outDir
|
|
81
|
-
});
|
|
82
|
-
await writeFile(join(outDir, "profile.tex"), profilePreamble(content));
|
|
83
|
-
const generatedClass = join(outDir, CLASS_FILE);
|
|
84
|
-
if (existsSync(join(cvDir, CLASS_FILE))) await rm(generatedClass, { force: true });
|
|
85
|
-
else await copyFile(join(bundledTexDir, CLASS_FILE), generatedClass);
|
|
86
|
-
if (options.noPdf) return;
|
|
87
|
-
const relativeOut = join("generated", document.name);
|
|
88
|
-
await run("latexmk", [
|
|
89
|
-
"-pdf",
|
|
90
|
-
"-halt-on-error",
|
|
91
|
-
"-interaction=nonstopmode",
|
|
92
|
-
`-outdir=${relativeOut}`,
|
|
93
|
-
`${document.name}.tex`
|
|
94
|
-
], cvDir, {
|
|
95
|
-
// A trailing delimiter keeps the standard search paths.
|
|
96
|
-
TEXINPUTS: `.${delimiter}${relativeOut}${delimiter}${process.env.TEXINPUTS ?? ""}`
|
|
97
|
-
});
|
|
98
|
-
await rename(join(outDir, `${document.name}.pdf`), join(cvDir, `${document.name}.pdf`));
|
|
99
|
-
}
|
|
100
|
-
async function buildCv(options) {
|
|
101
|
-
const cvDir = join(options.rootDir, "cv-tex");
|
|
102
|
-
if (!existsSync(cvDir)) {
|
|
103
|
-
throw new Error(`no cv-tex/ directory in ${options.rootDir}`);
|
|
104
|
-
}
|
|
105
|
-
const content = await loadContent(options.rootDir, { strict: true, maxVisibility: "private", target: "pdf" });
|
|
106
|
-
const documents = await discoverDocuments(cvDir);
|
|
107
|
-
const groups = (await loadPublicationsSchema(options.rootDir)).groups.map((group) => group.id);
|
|
108
|
-
for (const document of documents) {
|
|
109
|
-
const unknown = (document.options.publications.groups ?? []).filter((id) => !groups.includes(id));
|
|
110
|
-
if (unknown.length) {
|
|
111
|
-
fail(`cv-tex/${document.name}.yml`, `publications.groups: unknown group ${unknown.join(", ")}; the groups are ${groups.join(", ")} (schemas/publications.ts)`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const selected = options.only?.length ? documents.filter((document) => options.only.includes(document.name)) : documents;
|
|
115
|
-
if (!selected.length) {
|
|
116
|
-
const known = documents.map((document) => document.name).join(", ");
|
|
117
|
-
throw new Error(`no document matched ${options.only?.join(", ")}; available: ${known}`);
|
|
118
|
-
}
|
|
119
|
-
await mkdir(join(cvDir, "generated"), { recursive: true });
|
|
120
|
-
for (const document of selected) {
|
|
121
|
-
await buildDocument(document, content, options, cvDir);
|
|
122
|
-
}
|
|
123
|
-
return selected.map((document) => join(cvDir, `${document.name}.pdf`));
|
|
124
|
-
}
|
|
125
|
-
async function readGenerated(rootDir, document, section) {
|
|
126
|
-
return readFile(join(rootDir, "cv-tex", "generated", document, `${section}.tex`), "utf8");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export { buildCv, bundledTexDir, discoverDocuments, emitDocument, escapeTex, escapeUrl, readGenerated };
|
|
38
|
+
import './shared/vitakit.CfB3hMHl.mjs';
|
|
39
|
+
import './shared/vitakit.D-DdJaDZ.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anja.codes/vitakit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Structured CV data in, academic website and LaTeX CV out.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"katex",
|
|
14
14
|
"static-site"
|
|
15
15
|
],
|
|
16
|
-
"homepage": "https://
|
|
16
|
+
"homepage": "https://vitakit.anja.codes",
|
|
17
17
|
"bugs": {
|
|
18
18
|
"url": "https://github.com/anja-codes/vitakit/issues"
|
|
19
19
|
},
|
|
@@ -69,16 +69,20 @@
|
|
|
69
69
|
"test": "vitest run",
|
|
70
70
|
"test:watch": "vitest",
|
|
71
71
|
"typecheck": "tsc --noEmit",
|
|
72
|
-
"check": "pnpm typecheck && pnpm
|
|
72
|
+
"check": "pnpm typecheck && pnpm build && node scripts/check-dist.mjs && pnpm test && pnpm example:check",
|
|
73
73
|
"check:dist": "node scripts/check-dist.mjs",
|
|
74
74
|
"prepublishOnly": "pnpm check",
|
|
75
75
|
"example": "node scripts/generate-example.mjs",
|
|
76
76
|
"example:check": "node scripts/generate-example.mjs --check",
|
|
77
|
-
"check:deps": "node scripts/check-deps.mjs"
|
|
77
|
+
"check:deps": "node scripts/check-deps.mjs",
|
|
78
|
+
"docs:dev": "pnpm --filter ./docs dev",
|
|
79
|
+
"docs:build": "pnpm build && pnpm --filter ./docs build"
|
|
78
80
|
},
|
|
79
81
|
"dependencies": {
|
|
82
|
+
"@babel/parser": "^7.29.9",
|
|
80
83
|
"@behackl/citation-js-extras": "^0.4.0",
|
|
81
84
|
"@types/hast": "^3.0.5",
|
|
85
|
+
"@vue/compiler-sfc": "^3.5.43",
|
|
82
86
|
"citation-js": "^0.7.22",
|
|
83
87
|
"eta": "^4.6.0",
|
|
84
88
|
"gray-matter": "^4.0.3",
|
|
@@ -101,6 +105,7 @@
|
|
|
101
105
|
"nuxt": "^4.0.0"
|
|
102
106
|
},
|
|
103
107
|
"devDependencies": {
|
|
108
|
+
"@babel/types": "^7.29.8",
|
|
104
109
|
"@nuxt/kit": "^4.5.2",
|
|
105
110
|
"@nuxt/schema": "^4.5.2",
|
|
106
111
|
"@types/node": "^26.4.1",
|
|
@@ -114,8 +119,6 @@
|
|
|
114
119
|
"access": "public"
|
|
115
120
|
},
|
|
116
121
|
"bin": {
|
|
117
|
-
"vitakit": "src/bin/vitakit.mjs"
|
|
118
|
-
"vitakit-check": "src/bin/vitakit-check.mjs",
|
|
119
|
-
"vitakit-build-cv": "src/bin/vitakit-build-cv.mjs"
|
|
122
|
+
"vitakit": "src/bin/vitakit.mjs"
|
|
120
123
|
}
|
|
121
124
|
}
|