seendiff 0.0.2
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 +124 -0
- package/bin/seendiff.js +9 -0
- package/package.json +38 -0
- package/src/cli.js +216 -0
- package/src/git.js +615 -0
- package/src/highlight.js +220 -0
- package/src/server.js +580 -0
- package/src/store.js +128 -0
- package/src/theme-base.css +482 -0
- package/src/theme.js +14 -0
- package/src/walkthrough.js +338 -0
- package/static/fonts/JetBrainsMono.woff2 +0 -0
- package/static/fonts/LICENCE-UbuntuSansMono.txt +96 -0
- package/static/fonts/LICENSE-JetBrainsMono.txt +93 -0
- package/static/fonts/README.md +29 -0
- package/static/fonts/UbuntuSansMono.woff2 +0 -0
- package/static/index.html +3175 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync, statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export const MAX_STEPS = 40;
|
|
6
|
+
export const MAX_ALSO = 3;
|
|
7
|
+
export const MAX_DEPTH = 1;
|
|
8
|
+
|
|
9
|
+
export class WalkthroughError extends Error {
|
|
10
|
+
constructor(problems) {
|
|
11
|
+
super(problems.join("; "));
|
|
12
|
+
this.problems = problems;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function refToJson(ref) {
|
|
17
|
+
const d = { path: ref.path, lines: [ref.lines[0], ref.lines[1]] };
|
|
18
|
+
if (ref.hunkId) d.hunk_id = ref.hunkId;
|
|
19
|
+
if (ref.note) d.note = ref.note;
|
|
20
|
+
return d;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stepToJson(s) {
|
|
24
|
+
return {
|
|
25
|
+
id: s.id,
|
|
26
|
+
act: s.act,
|
|
27
|
+
depth: s.depth,
|
|
28
|
+
title: s.title,
|
|
29
|
+
text: s.text,
|
|
30
|
+
ref: refToJson(s.ref),
|
|
31
|
+
also: s.also.map(refToJson),
|
|
32
|
+
forward: s.forward,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function walkthroughPaths(wt) {
|
|
37
|
+
const out = new Set();
|
|
38
|
+
for (const s of wt.steps) {
|
|
39
|
+
out.add(s.ref.path);
|
|
40
|
+
for (const r of s.also) out.add(r.path);
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function walkthroughToJson(wt) {
|
|
46
|
+
return {
|
|
47
|
+
title: wt.title,
|
|
48
|
+
intro: wt.intro,
|
|
49
|
+
acts: wt.acts.map((a) => ({ id: a.id, title: a.title })),
|
|
50
|
+
steps: wt.steps.map(stepToJson),
|
|
51
|
+
digest: wt.digest,
|
|
52
|
+
mtime: wt.mtime,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function fileLineCount(repo, filePath) {
|
|
57
|
+
let raw;
|
|
58
|
+
try {
|
|
59
|
+
raw = readFileSync(path.join(repo, filePath));
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (raw.subarray(0, 8192).includes(0)) return null;
|
|
64
|
+
const lines = raw.toString("utf8").split("\n");
|
|
65
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
66
|
+
return lines.length;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function checkRef(ref, repo, where, hunkIds) {
|
|
70
|
+
const probs = [];
|
|
71
|
+
const p = ref.path;
|
|
72
|
+
if (typeof p !== "string" || !p) {
|
|
73
|
+
return [null, [`${where}: ref needs a string 'path'`]];
|
|
74
|
+
}
|
|
75
|
+
const root = path.resolve(repo);
|
|
76
|
+
const resolved = path.resolve(root, p);
|
|
77
|
+
const rel = path.relative(root, resolved);
|
|
78
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
79
|
+
return [null, [`${where}: ${p} resolves outside the repository`]];
|
|
80
|
+
}
|
|
81
|
+
let st;
|
|
82
|
+
try {
|
|
83
|
+
st = statSync(resolved);
|
|
84
|
+
} catch {
|
|
85
|
+
return [null, [`${where}: ${p} does not exist`]];
|
|
86
|
+
}
|
|
87
|
+
if (!st.isFile()) {
|
|
88
|
+
return [null, [`${where}: ${p} does not exist`]];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const lines = ref.lines;
|
|
92
|
+
const validLines =
|
|
93
|
+
Array.isArray(lines) &&
|
|
94
|
+
lines.length === 2 &&
|
|
95
|
+
lines.every((n) => Number.isInteger(n));
|
|
96
|
+
if (!validLines) {
|
|
97
|
+
return [null, [`${where}: 'lines' must be [start, end] integers`]];
|
|
98
|
+
}
|
|
99
|
+
const [a, b] = lines;
|
|
100
|
+
const n = fileLineCount(repo, p);
|
|
101
|
+
if (n === null) {
|
|
102
|
+
probs.push(`${where}: ${p} is binary or unreadable`);
|
|
103
|
+
} else if (!(1 <= a && a <= b && b <= n)) {
|
|
104
|
+
probs.push(
|
|
105
|
+
a <= b
|
|
106
|
+
? `${where}: ${p}:${a}-${b} out of range (file has ${n} lines)`
|
|
107
|
+
: `${where}: ${p}:${a}-${b} is inverted`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const hunkId = ref.hunk_id;
|
|
112
|
+
let outHunkId = null;
|
|
113
|
+
if (hunkId !== undefined && hunkId !== null) {
|
|
114
|
+
if (typeof hunkId !== "string") {
|
|
115
|
+
probs.push(`${where}: 'hunk_id' must be a string`);
|
|
116
|
+
} else {
|
|
117
|
+
outHunkId = hunkId;
|
|
118
|
+
if (hunkIds != null) {
|
|
119
|
+
const set = hunkIds[p];
|
|
120
|
+
if (!set || !set.has(hunkId)) {
|
|
121
|
+
probs.push(`${where}: hunk_id '${hunkId}' not in the diff of ${p}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let note = ref.note;
|
|
128
|
+
if (note !== undefined && note !== null && typeof note !== "string") {
|
|
129
|
+
probs.push(`${where}: 'note' must be a string`);
|
|
130
|
+
note = null;
|
|
131
|
+
}
|
|
132
|
+
if (probs.length) return [null, probs];
|
|
133
|
+
return [{ path: p, lines: [a, b], hunkId: outHunkId, note: note ?? null }, []];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseWalkthrough(data, repo, hunkIds) {
|
|
137
|
+
const probs = [];
|
|
138
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
139
|
+
return [null, ["top level must be a JSON object"]];
|
|
140
|
+
}
|
|
141
|
+
if (data.version !== 1) {
|
|
142
|
+
probs.push(`version must be 1 (got ${JSON.stringify(data.version)})`);
|
|
143
|
+
}
|
|
144
|
+
let title = data.title;
|
|
145
|
+
if (typeof title !== "string" || !title) {
|
|
146
|
+
probs.push("'title' is required");
|
|
147
|
+
title = "";
|
|
148
|
+
}
|
|
149
|
+
const intro = data.intro || "";
|
|
150
|
+
|
|
151
|
+
const acts = [];
|
|
152
|
+
const actIds = new Map();
|
|
153
|
+
let rawActs = data.acts;
|
|
154
|
+
if (!Array.isArray(rawActs) || !rawActs.length) {
|
|
155
|
+
probs.push("'acts' must be a non-empty list");
|
|
156
|
+
rawActs = [];
|
|
157
|
+
}
|
|
158
|
+
rawActs.forEach((a, i) => {
|
|
159
|
+
if (typeof a !== "object" || a === null || typeof a.id !== "string" || typeof a.title !== "string") {
|
|
160
|
+
probs.push(`acts[${i}]: needs string 'id' and 'title'`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (actIds.has(a.id)) {
|
|
164
|
+
probs.push(`acts[${i}]: duplicate act id '${a.id}'`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
actIds.set(a.id, acts.length);
|
|
168
|
+
acts.push({ id: a.id, title: a.title });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
let rawSteps = data.steps;
|
|
172
|
+
if (!Array.isArray(rawSteps) || !rawSteps.length) {
|
|
173
|
+
probs.push("'steps' must be a non-empty list");
|
|
174
|
+
rawSteps = [];
|
|
175
|
+
}
|
|
176
|
+
if (rawSteps.length > MAX_STEPS) {
|
|
177
|
+
probs.push(`${rawSteps.length} steps — cap is ${MAX_STEPS}; the scope was too big`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const steps = [];
|
|
181
|
+
const stepIndex = new Map();
|
|
182
|
+
let lastAct = -1;
|
|
183
|
+
rawSteps.forEach((s, i) => {
|
|
184
|
+
const w = `steps[${i}]`;
|
|
185
|
+
if (typeof s !== "object" || s === null || Array.isArray(s)) {
|
|
186
|
+
probs.push(`${w}: must be an object`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
let sid = s.id;
|
|
190
|
+
if (typeof sid !== "string" || !sid) {
|
|
191
|
+
probs.push(`${w}: needs a string 'id'`);
|
|
192
|
+
sid = `#${i}`;
|
|
193
|
+
} else if (stepIndex.has(sid)) {
|
|
194
|
+
probs.push(`${w}: duplicate step id '${sid}'`);
|
|
195
|
+
}
|
|
196
|
+
const act = s.act;
|
|
197
|
+
if (!actIds.has(act)) {
|
|
198
|
+
probs.push(`${w}: unknown act ${JSON.stringify(act)}`);
|
|
199
|
+
} else {
|
|
200
|
+
const ai = actIds.get(act);
|
|
201
|
+
if (ai < lastAct) {
|
|
202
|
+
probs.push(`${w}: act '${act}' interleaved — steps must be grouped in act order`);
|
|
203
|
+
}
|
|
204
|
+
lastAct = Math.max(lastAct, ai);
|
|
205
|
+
}
|
|
206
|
+
let depth = s.depth ?? 0;
|
|
207
|
+
if (!Number.isInteger(depth) || !(depth >= 0 && depth <= MAX_DEPTH)) {
|
|
208
|
+
probs.push(`${w}: depth must be 0 or 1 (got ${JSON.stringify(depth)}); three levels of nesting needs its own act`);
|
|
209
|
+
depth = 0;
|
|
210
|
+
}
|
|
211
|
+
let stTitle = s.title;
|
|
212
|
+
if (typeof stTitle !== "string" || !stTitle) {
|
|
213
|
+
probs.push(`${w}: needs a string 'title'`);
|
|
214
|
+
stTitle = "";
|
|
215
|
+
}
|
|
216
|
+
let text = s.text;
|
|
217
|
+
if (typeof text !== "string" || !text) {
|
|
218
|
+
probs.push(`${w}: needs a string 'text'`);
|
|
219
|
+
text = "";
|
|
220
|
+
}
|
|
221
|
+
if (typeof s.ref !== "object" || s.ref === null || Array.isArray(s.ref)) {
|
|
222
|
+
probs.push(`${w}: needs a 'ref' object`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const [ref, rp] = checkRef(s.ref, repo, `${w}.ref`, hunkIds);
|
|
226
|
+
probs.push(...rp);
|
|
227
|
+
const also = [];
|
|
228
|
+
let rawAlso = s.also || [];
|
|
229
|
+
if (!Array.isArray(rawAlso)) {
|
|
230
|
+
probs.push(`${w}: 'also' must be a list`);
|
|
231
|
+
rawAlso = [];
|
|
232
|
+
}
|
|
233
|
+
if (rawAlso.length > MAX_ALSO) {
|
|
234
|
+
probs.push(`${w}: ${rawAlso.length} also-refs — cap is ${MAX_ALSO}`);
|
|
235
|
+
}
|
|
236
|
+
rawAlso.forEach((ar, j) => {
|
|
237
|
+
const [aref, ap] = checkRef(
|
|
238
|
+
typeof ar === "object" && ar !== null ? ar : {},
|
|
239
|
+
repo,
|
|
240
|
+
`${w}.also[${j}]`,
|
|
241
|
+
hunkIds
|
|
242
|
+
);
|
|
243
|
+
probs.push(...ap);
|
|
244
|
+
if (aref) also.push(aref);
|
|
245
|
+
});
|
|
246
|
+
let forward = s.forward;
|
|
247
|
+
if (forward !== undefined && forward !== null && typeof forward !== "string") {
|
|
248
|
+
probs.push(`${w}: 'forward' must be a step id`);
|
|
249
|
+
forward = null;
|
|
250
|
+
}
|
|
251
|
+
if (!ref) return;
|
|
252
|
+
if (!stepIndex.has(sid)) stepIndex.set(sid, steps.length);
|
|
253
|
+
steps.push({
|
|
254
|
+
id: sid,
|
|
255
|
+
act: actIds.has(act) ? act : "",
|
|
256
|
+
depth,
|
|
257
|
+
title: stTitle,
|
|
258
|
+
text,
|
|
259
|
+
ref,
|
|
260
|
+
also,
|
|
261
|
+
forward: forward ?? null,
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
steps.forEach((st, i) => {
|
|
266
|
+
if (st.forward == null) return;
|
|
267
|
+
const j = stepIndex.get(st.forward);
|
|
268
|
+
if (j === undefined) {
|
|
269
|
+
probs.push(`steps[${i}]: forward '${st.forward}' names no step`);
|
|
270
|
+
} else if (j <= i) {
|
|
271
|
+
probs.push(`steps[${i}]: forward '${st.forward}' must point to a later step`);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (probs.length) return [null, probs];
|
|
276
|
+
return [
|
|
277
|
+
{ title, intro, acts, steps, path: "", mtime: 0.0, digest: "" },
|
|
278
|
+
[],
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function isEmpty(raw) {
|
|
283
|
+
if (raw === null || !raw.toString("utf8").trim()) return true;
|
|
284
|
+
let data;
|
|
285
|
+
try {
|
|
286
|
+
data = JSON.parse(raw.toString("utf8"));
|
|
287
|
+
} catch {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
if (data === null) return true;
|
|
291
|
+
if (typeof data === "object" && !Array.isArray(data) && Object.keys(data).length === 0) return true;
|
|
292
|
+
return typeof data === "object" && data !== null && !Array.isArray(data) && !data.steps;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function check(filePath, repo, hunkIds = null) {
|
|
296
|
+
const name = path.basename(filePath);
|
|
297
|
+
let raw;
|
|
298
|
+
try {
|
|
299
|
+
raw = readFileSync(filePath);
|
|
300
|
+
} catch (e) {
|
|
301
|
+
if (e.code === "ENOENT") return [];
|
|
302
|
+
return [`${name}: cannot read: ${e.message}`];
|
|
303
|
+
}
|
|
304
|
+
if (isEmpty(raw)) return [];
|
|
305
|
+
let data;
|
|
306
|
+
try {
|
|
307
|
+
data = JSON.parse(raw.toString("utf8"));
|
|
308
|
+
} catch (e) {
|
|
309
|
+
return [`${name}: invalid JSON: ${e.message}`];
|
|
310
|
+
}
|
|
311
|
+
const [, probs] = parseWalkthrough(data, repo, hunkIds);
|
|
312
|
+
return probs.map((p) => `${name}: ${p}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function load(filePath, repo, hunkIds = null) {
|
|
316
|
+
let raw;
|
|
317
|
+
let mtime;
|
|
318
|
+
try {
|
|
319
|
+
raw = readFileSync(filePath);
|
|
320
|
+
mtime = statSync(filePath).mtimeMs / 1000;
|
|
321
|
+
} catch (e) {
|
|
322
|
+
if (e.code === "ENOENT") return null;
|
|
323
|
+
throw new WalkthroughError([`cannot read ${filePath}: ${e.message}`]);
|
|
324
|
+
}
|
|
325
|
+
if (isEmpty(raw)) return null;
|
|
326
|
+
let data;
|
|
327
|
+
try {
|
|
328
|
+
data = JSON.parse(raw.toString("utf8"));
|
|
329
|
+
} catch (e) {
|
|
330
|
+
throw new WalkthroughError([`invalid JSON: ${e.message}`]);
|
|
331
|
+
}
|
|
332
|
+
const [wt, probs] = parseWalkthrough(data, repo, hunkIds);
|
|
333
|
+
if (!wt) throw new WalkthroughError(probs);
|
|
334
|
+
wt.path = filePath;
|
|
335
|
+
wt.mtime = mtime;
|
|
336
|
+
wt.digest = createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
337
|
+
return wt;
|
|
338
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
-------------------------------
|
|
2
|
+
UBUNTU FONT LICENCE Version 1.0
|
|
3
|
+
-------------------------------
|
|
4
|
+
|
|
5
|
+
PREAMBLE
|
|
6
|
+
This licence allows the licensed fonts to be used, studied, modified and
|
|
7
|
+
redistributed freely. The fonts, including any derivative works, can be
|
|
8
|
+
bundled, embedded, and redistributed provided the terms of this licence
|
|
9
|
+
are met. The fonts and derivatives, however, cannot be released under
|
|
10
|
+
any other licence. The requirement for fonts to remain under this
|
|
11
|
+
licence does not require any document created using the fonts or their
|
|
12
|
+
derivatives to be published under this licence, as long as the primary
|
|
13
|
+
purpose of the document is not to be a vehicle for the distribution of
|
|
14
|
+
the fonts.
|
|
15
|
+
|
|
16
|
+
DEFINITIONS
|
|
17
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
18
|
+
Holder(s) under this licence and clearly marked as such. This may
|
|
19
|
+
include source files, build scripts and documentation.
|
|
20
|
+
|
|
21
|
+
"Original Version" refers to the collection of Font Software components
|
|
22
|
+
as received under this licence.
|
|
23
|
+
|
|
24
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
25
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
26
|
+
Original Version, by changing formats or by porting the Font Software to
|
|
27
|
+
a new environment.
|
|
28
|
+
|
|
29
|
+
"Copyright Holder(s)" refers to all individuals and companies who have a
|
|
30
|
+
copyright ownership of the Font Software.
|
|
31
|
+
|
|
32
|
+
"Substantially Changed" refers to Modified Versions which can be easily
|
|
33
|
+
identified as dissimilar to the Font Software by users of the Font
|
|
34
|
+
Software comparing the Original Version with the Modified Version.
|
|
35
|
+
|
|
36
|
+
To "Propagate" a work means to do anything with it that, without
|
|
37
|
+
permission, would make you directly or secondarily liable for
|
|
38
|
+
infringement under applicable copyright law, except executing it on a
|
|
39
|
+
computer or modifying a private copy. Propagation includes copying,
|
|
40
|
+
distribution (with or without modification and with or without charging
|
|
41
|
+
a redistribution fee), making available to the public, and in some
|
|
42
|
+
countries other activities as well.
|
|
43
|
+
|
|
44
|
+
PERMISSION & CONDITIONS
|
|
45
|
+
This licence does not grant any rights under trademark law and all such
|
|
46
|
+
rights are reserved.
|
|
47
|
+
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
49
|
+
copy of the Font Software, to propagate the Font Software, subject to
|
|
50
|
+
the below conditions:
|
|
51
|
+
|
|
52
|
+
1) Each copy of the Font Software must contain the above copyright
|
|
53
|
+
notice and this licence. These can be included either as stand-alone
|
|
54
|
+
text files, human-readable headers or in the appropriate machine-
|
|
55
|
+
readable metadata fields within text or binary files as long as those
|
|
56
|
+
fields can be easily viewed by the user.
|
|
57
|
+
|
|
58
|
+
2) The font name complies with the following:
|
|
59
|
+
(a) The Original Version must retain its name, unmodified.
|
|
60
|
+
(b) Modified Versions which are Substantially Changed must be renamed to
|
|
61
|
+
avoid use of the name of the Original Version or similar names entirely.
|
|
62
|
+
(c) Modified Versions which are not Substantially Changed must be
|
|
63
|
+
renamed to both (i) retain the name of the Original Version and (ii) add
|
|
64
|
+
additional naming elements to distinguish the Modified Version from the
|
|
65
|
+
Original Version. The name of such Modified Versions must be the name of
|
|
66
|
+
the Original Version, with "derivative X" where X represents the name of
|
|
67
|
+
the new work, appended to that name.
|
|
68
|
+
|
|
69
|
+
3) The name(s) of the Copyright Holder(s) and any contributor to the
|
|
70
|
+
Font Software shall not be used to promote, endorse or advertise any
|
|
71
|
+
Modified Version, except (i) as required by this licence, (ii) to
|
|
72
|
+
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
|
|
73
|
+
their explicit written permission.
|
|
74
|
+
|
|
75
|
+
4) The Font Software, modified or unmodified, in part or in whole, must
|
|
76
|
+
be distributed entirely under this licence, and must not be distributed
|
|
77
|
+
under any other licence. The requirement for fonts to remain under this
|
|
78
|
+
licence does not affect any document created using the Font Software,
|
|
79
|
+
except any version of the Font Software extracted from a document
|
|
80
|
+
created using the Font Software may only be distributed under this
|
|
81
|
+
licence.
|
|
82
|
+
|
|
83
|
+
TERMINATION
|
|
84
|
+
This licence becomes null and void if any of the above conditions are
|
|
85
|
+
not met.
|
|
86
|
+
|
|
87
|
+
DISCLAIMER
|
|
88
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
89
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
90
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
91
|
+
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
92
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
93
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
94
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
95
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
|
96
|
+
DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
https://openfontlicense.org
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Bundled fonts
|
|
2
|
+
|
|
3
|
+
seendiff ships two monospace webfonts so the UI renders identically without a
|
|
4
|
+
network fetch. Both are subsetted and converted to woff2; neither is the
|
|
5
|
+
upstream file byte-for-byte.
|
|
6
|
+
|
|
7
|
+
| File | Font | Version | Licence |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `JetBrainsMono.woff2` | JetBrains Mono Regular | 2.211 | SIL Open Font License 1.1 — [`LICENSE-JetBrainsMono.txt`](LICENSE-JetBrainsMono.txt) |
|
|
10
|
+
| `UbuntuSansMono.woff2` | Ubuntu Sans Mono Regular | 1.006 | Ubuntu Font Licence 1.0 — [`LICENCE-UbuntuSansMono.txt`](LICENCE-UbuntuSansMono.txt) |
|
|
11
|
+
|
|
12
|
+
Upstream sources:
|
|
13
|
+
|
|
14
|
+
- JetBrains Mono — <https://github.com/JetBrains/JetBrainsMono>
|
|
15
|
+
- Ubuntu Sans Mono — <https://github.com/canonical/Ubuntu-Sans-fonts>
|
|
16
|
+
|
|
17
|
+
## Modifications
|
|
18
|
+
|
|
19
|
+
Both files were reduced to the glyphs seendiff actually renders and converted
|
|
20
|
+
to woff2 (394 glyphs / 229 codepoints for JetBrains Mono, 259 / 227 for Ubuntu
|
|
21
|
+
Sans Mono). No outlines, metrics, or hinting were altered.
|
|
22
|
+
|
|
23
|
+
The JetBrains Mono copyright declares no Reserved Font Name, so the subset is
|
|
24
|
+
redistributable under the OFL as-is, provided this licence text travels with
|
|
25
|
+
it.
|
|
26
|
+
|
|
27
|
+
The Ubuntu Font Licence treats a format conversion as a Modified Version
|
|
28
|
+
(clause 2), which carries a naming requirement — see the licence text for the
|
|
29
|
+
exact terms.
|
|
Binary file
|