myst-frontmatter
Advanced tools
Comparing version 1.1.5 to 1.1.6
import type { CreditRole } from 'credit-roles'; | ||
import type { Funding } from '../funding/types.js'; | ||
import type { Licenses } from '../licenses/types.js'; | ||
@@ -22,4 +23,12 @@ export interface Affiliation { | ||
} | ||
export type AuthorRoles = CreditRole | string; | ||
export interface Author { | ||
export type ContributorRole = CreditRole | string; | ||
export type Name = { | ||
literal?: string; | ||
given?: string; | ||
family?: string; | ||
dropping_particle?: string; | ||
non_dropping_particle?: string; | ||
suffix?: string; | ||
}; | ||
export interface Contributor { | ||
id?: string; | ||
@@ -33,3 +42,3 @@ name?: string; | ||
email?: string; | ||
roles?: AuthorRoles[]; | ||
roles?: ContributorRole[]; | ||
affiliations?: string[]; | ||
@@ -42,2 +51,3 @@ twitter?: string; | ||
fax?: string; | ||
nameParsed?: Name; | ||
} | ||
@@ -50,4 +60,9 @@ /** | ||
export type ReferenceStash = { | ||
affiliations?: Affiliation[]; | ||
authors?: Author[]; | ||
affiliations?: (Affiliation & { | ||
id: string; | ||
})[]; | ||
contributors?: (Contributor & { | ||
id: string; | ||
})[]; | ||
authorIds?: string[]; | ||
}; | ||
@@ -147,3 +162,3 @@ export type Biblio = { | ||
bannerOptimized?: string; | ||
authors?: Author[]; | ||
authors?: Contributor[]; | ||
affiliations?: Affiliation[]; | ||
@@ -153,2 +168,4 @@ venue?: Venue; | ||
keywords?: string[]; | ||
funding?: Funding[]; | ||
contributors?: Contributor[]; | ||
}; | ||
@@ -155,0 +172,0 @@ export type ProjectFrontmatter = SiteFrontmatter & { |
import type { ValidationOptions } from 'simple-validators'; | ||
import type { Author, Biblio, Export, Jupytext, KernelSpec, Numbering, PageFrontmatter, ProjectFrontmatter, SiteFrontmatter, Venue, Thebe, BinderHubOptions, JupyterServerOptions, JupyterLocalOptions, ReferenceStash, Affiliation } from './types.js'; | ||
import type { Contributor, Biblio, Export, Jupytext, KernelSpec, Numbering, PageFrontmatter, ProjectFrontmatter, SiteFrontmatter, Venue, Thebe, BinderHubOptions, JupyterServerOptions, JupyterLocalOptions, ReferenceStash, Affiliation, Name } from './types.js'; | ||
export declare const SITE_FRONTMATTER_KEYS: string[]; | ||
@@ -25,3 +25,3 @@ export declare const PROJECT_FRONTMATTER_KEYS: string[]; | ||
name?: string; | ||
}>(input: any, stash: ReferenceStash, kind: keyof ReferenceStash, validateFn: (v: any, o: ValidationOptions) => T | undefined, opts: ValidationOptions): string | undefined; | ||
}>(input: any, stash: ReferenceStash, kind: 'affiliations' | 'contributors', validateFn: (v: any, o: ValidationOptions) => T | undefined, opts: ValidationOptions): string | undefined; | ||
/** | ||
@@ -41,6 +41,10 @@ * Validate Venue object against the schema | ||
/** | ||
* Validate Author object against the schema | ||
* Validate Name object against the schema | ||
*/ | ||
export declare function validateAuthor(input: any, stash: ReferenceStash, opts: ValidationOptions): Author | undefined; | ||
export declare function validateName(input: any, opts: ValidationOptions): Name | undefined; | ||
/** | ||
* Validate Contributor object against the schema | ||
*/ | ||
export declare function validateContributor(input: any, stash: ReferenceStash, opts: ValidationOptions): Contributor | undefined; | ||
/** | ||
* Validate Biblio object | ||
@@ -47,0 +51,0 @@ * |
@@ -6,3 +6,5 @@ import { doi } from 'doi-utils'; | ||
import { validateLicenses } from '../licenses/validators.js'; | ||
import { formatName, parseName } from '../utils/parseName.js'; | ||
import { ExportFormats } from './types.js'; | ||
import { validateFunding } from '../funding/validators.js'; | ||
export const SITE_FRONTMATTER_KEYS = [ | ||
@@ -18,2 +20,3 @@ 'title', | ||
'authors', | ||
'contributors', | ||
'venue', | ||
@@ -23,2 +26,3 @@ 'github', | ||
'affiliations', | ||
'funding', | ||
]; | ||
@@ -62,2 +66,5 @@ export const PROJECT_FRONTMATTER_KEYS = [ | ||
'keywords', | ||
'funding', | ||
'authors', | ||
'affiliations', | ||
]; | ||
@@ -72,3 +79,2 @@ const AFFILIATION_KEYS = [ | ||
'name', | ||
'institution', | ||
'department', | ||
@@ -84,6 +90,7 @@ 'collaboration', | ||
]; | ||
const AUTHOR_KEYS = [ | ||
const CONTRIBUTOR_KEYS = [ | ||
'id', | ||
'userId', | ||
'name', | ||
'nameParsed', | ||
'orcid', | ||
@@ -104,3 +111,3 @@ 'corresponding', | ||
]; | ||
const AUTHOR_ALIASES = { | ||
const CONTRIBUTOR_ALIASES = { | ||
role: 'roles', | ||
@@ -110,2 +117,19 @@ affiliation: 'affiliations', | ||
}; | ||
const NAME_KEYS = [ | ||
'literal', | ||
'given', | ||
'family', | ||
'suffix', | ||
'non_dropping_particle', | ||
'dropping_particle', | ||
]; | ||
const NAME_ALIASES = { | ||
surname: 'family', | ||
last: 'family', | ||
forename: 'given', | ||
first: 'given', | ||
particle: 'non_dropping_particle', | ||
'non-dropping-particle': 'non_dropping_particle', | ||
'dropping-particle': 'dropping_particle', | ||
}; | ||
const AFFILIATION_ALIASES = { | ||
@@ -116,2 +140,3 @@ ref: 'id', | ||
website: 'url', | ||
institution: 'name', | ||
}; | ||
@@ -161,2 +186,3 @@ const BIBLIO_KEYS = ['volume', 'issue', 'first_page', 'last_page']; | ||
author: 'authors', | ||
contributor: 'contributors', | ||
affiliation: 'affiliations', | ||
@@ -317,5 +343,2 @@ export: 'exports', | ||
} | ||
if (defined(value.institution)) { | ||
output.institution = validateString(value.institution, incrementOptions('institution', opts)); | ||
} | ||
if (defined(value.department)) { | ||
@@ -373,4 +396,4 @@ output.department = validateString(value.department, incrementOptions('department', opts)); | ||
} | ||
else if (!output.name && !output.institution) { | ||
validationWarning('affiliation should include name or institution', opts); | ||
else if (!output.name) { | ||
validationWarning('affiliation should include name/institution', opts); | ||
} | ||
@@ -380,9 +403,66 @@ return output; | ||
/** | ||
* Validate Author object against the schema | ||
* Validate Name object against the schema | ||
*/ | ||
export function validateAuthor(input, stash, opts) { | ||
export function validateName(input, opts) { | ||
let output; | ||
if (typeof input === 'string') { | ||
output = parseName(input); | ||
} | ||
else { | ||
const value = validateObjectKeys(input, { optional: NAME_KEYS, alias: NAME_ALIASES }, opts); | ||
if (value === undefined) | ||
return undefined; | ||
output = {}; | ||
if (defined(value.literal)) { | ||
output.literal = validateString(value.literal, incrementOptions('literal', opts)); | ||
} | ||
if (defined(value.given)) { | ||
output.given = validateString(value.given, incrementOptions('given', opts)); | ||
} | ||
if (defined(value.non_dropping_particle)) { | ||
output.non_dropping_particle = validateString(value.non_dropping_particle, incrementOptions('non_dropping_particle', opts)); | ||
} | ||
if (defined(value.dropping_particle)) { | ||
output.dropping_particle = validateString(value.dropping_particle, incrementOptions('dropping_particle', opts)); | ||
} | ||
if (defined(value.family)) { | ||
output.family = validateString(value.family, incrementOptions('family', opts)); | ||
} | ||
if (defined(value.suffix)) { | ||
output.suffix = validateString(value.suffix, incrementOptions('suffix', opts)); | ||
} | ||
if (Object.keys(output).length === 1 && output.literal) { | ||
output = { ...output, ...parseName(output.literal) }; | ||
} | ||
else if (!output.literal) { | ||
output.literal = formatName(output); | ||
} | ||
} | ||
const warnOnComma = (part, o) => { | ||
if (part && part.includes(',')) { | ||
validationWarning(`unexpected comma in name part: ${part}`, o); | ||
} | ||
}; | ||
warnOnComma(output.given, incrementOptions('given', opts)); | ||
warnOnComma(output.family, incrementOptions('family', opts)); | ||
warnOnComma(output.non_dropping_particle, incrementOptions('non_dropping_particle', opts)); | ||
warnOnComma(output.dropping_particle, incrementOptions('dropping_particle', opts)); | ||
warnOnComma(output.suffix, incrementOptions('suffix', opts)); | ||
if (!output.family) { | ||
validationWarning(`No family name for name '${output.literal}'`, opts); | ||
} | ||
if (!output.given) { | ||
validationWarning(`No given name for name '${output.literal}'`, opts); | ||
} | ||
return output; | ||
} | ||
/** | ||
* Validate Contributor object against the schema | ||
*/ | ||
export function validateContributor(input, stash, opts) { | ||
var _a, _b, _c; | ||
if (typeof input === 'string') { | ||
input = { id: input, name: input }; | ||
} | ||
const value = validateObjectKeys(input, { optional: AUTHOR_KEYS, alias: AUTHOR_ALIASES }, opts); | ||
const value = validateObjectKeys(input, { optional: CONTRIBUTOR_KEYS, alias: CONTRIBUTOR_ALIASES }, opts); | ||
if (value === undefined) | ||
@@ -398,7 +478,19 @@ return undefined; | ||
} | ||
if (defined(value.name)) { | ||
output.name = validateString(value.name, incrementOptions('name', opts)); | ||
if (defined(value.nameParsed)) { | ||
// In general, nameParsed should not be included in frontmatter; | ||
// authors should provide string or parsed for "name" | ||
output.nameParsed = validateName(value.nameParsed, incrementOptions('nameParsed', opts)); | ||
output.name = value.name | ||
? validateString(value.name, incrementOptions('name', opts)) | ||
: (_a = output.nameParsed) === null || _a === void 0 ? void 0 : _a.literal; | ||
if (output.name !== ((_b = output.nameParsed) === null || _b === void 0 ? void 0 : _b.literal)) { | ||
validationWarning(`"name" and "parsedName.literal" should match`, opts); | ||
} | ||
} | ||
else if (defined(value.name)) { | ||
output.nameParsed = validateName(value.name, incrementOptions('name', opts)); | ||
output.name = (_c = output.nameParsed) === null || _c === void 0 ? void 0 : _c.literal; | ||
} | ||
else { | ||
validationWarning('author should include name', opts); | ||
validationWarning('contributor should include name', opts); | ||
} | ||
@@ -818,3 +910,3 @@ if (defined(value.orcid)) { | ||
export function validateSiteFrontmatterKeys(value, opts) { | ||
var _a, _b; | ||
var _a, _b, _c, _d, _e; | ||
const output = {}; | ||
@@ -865,11 +957,17 @@ if (defined(value.title)) { | ||
} | ||
output.authors = validateList(authors, incrementOptions('authors', opts), (author, index) => { | ||
return validateAuthor(author, stash, incrementOptions(`authors.${index}`, opts)); | ||
stash.authorIds = validateList(authors, incrementOptions('authors', opts), (author, index) => { | ||
return validateAndStashObject(author, stash, 'contributors', (v, o) => validateContributor(v, stash, o), incrementOptions(`authors.${index}`, opts)); | ||
}); | ||
// Ensure there is a corresponding author if an email is provided | ||
const corresponding = (_a = output.authors) === null || _a === void 0 ? void 0 : _a.find((a) => a.corresponding !== undefined); | ||
const email = (_b = output.authors) === null || _b === void 0 ? void 0 : _b.find((a) => a.email); | ||
if (!corresponding && email) { | ||
email.corresponding = true; | ||
} | ||
if (defined(value.contributors)) { | ||
// In addition to contributors defined here, additional contributors may be defined elsewhere | ||
// in the frontmatter (e.g. funding award investigator/recipient). These extra contributors | ||
// are combined with this list at the end of validation. | ||
let contributors = value.contributors; | ||
if (!Array.isArray(value.contributors)) { | ||
contributors = [contributors]; | ||
} | ||
validateList(contributors, incrementOptions('contributors', opts), (contributor, index) => { | ||
return validateAndStashObject(contributor, stash, 'contributors', (v, o) => validateContributor(v, stash, o), incrementOptions(`contributors.${index}`, opts)); | ||
}); | ||
} | ||
@@ -891,3 +989,23 @@ if (defined(value.venue)) { | ||
} | ||
if (stash.affiliations) { | ||
if (defined(value.funding)) { | ||
const funding = Array.isArray(value.funding) ? value.funding : [value.funding]; | ||
output.funding = validateList(funding, incrementOptions('funding', opts), (fund, index) => { | ||
return validateFunding(fund, stash, incrementOptions(`funding.${index}`, opts)); | ||
}); | ||
} | ||
const stashContribAuthors = (_a = stash.contributors) === null || _a === void 0 ? void 0 : _a.filter((contrib) => { var _a; return (_a = stash.authorIds) === null || _a === void 0 ? void 0 : _a.includes(contrib.id); }); | ||
const stashContribNonAuthors = (_b = stash.contributors) === null || _b === void 0 ? void 0 : _b.filter((contrib) => { var _a; return !((_a = stash.authorIds) === null || _a === void 0 ? void 0 : _a.includes(contrib.id)); }); | ||
if (stashContribAuthors === null || stashContribAuthors === void 0 ? void 0 : stashContribAuthors.length) { | ||
output.authors = stashContribAuthors; | ||
// Ensure there is a corresponding author if an email is provided | ||
const corresponding = (_c = output.authors) === null || _c === void 0 ? void 0 : _c.find((a) => a.corresponding !== undefined); | ||
const email = (_d = output.authors) === null || _d === void 0 ? void 0 : _d.find((a) => a.email); | ||
if (!corresponding && email) { | ||
email.corresponding = true; | ||
} | ||
} | ||
if (stashContribNonAuthors === null || stashContribNonAuthors === void 0 ? void 0 : stashContribNonAuthors.length) { | ||
output.contributors = stashContribNonAuthors; | ||
} | ||
if ((_e = stash.affiliations) === null || _e === void 0 ? void 0 : _e.length) { | ||
output.affiliations = stash.affiliations; | ||
@@ -1077,3 +1195,3 @@ } | ||
export function fillPageFrontmatter(pageFrontmatter, projectFrontmatter, opts) { | ||
var _a, _b, _c, _d; | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r; | ||
const frontmatter = fillMissingKeys(pageFrontmatter, projectFrontmatter, USE_PROJECT_FALLBACK); | ||
@@ -1097,36 +1215,87 @@ // If numbering is an object, combine page and project settings. | ||
} | ||
// Replace affiliation placeholders with extra affiliations available on the project/page | ||
let affiliations; | ||
let extraAffiliations; | ||
// Currently, affiliations are connected only to authors, so we look at | ||
// which frontmatter (project or page) has authors defined, and use the | ||
// affiliations from there. However, we still use the other affiliations | ||
// to fill out any placeholders where affiliations have id only. | ||
if (projectFrontmatter.authors && !pageFrontmatter.authors) { | ||
affiliations = projectFrontmatter.affiliations; | ||
extraAffiliations = pageFrontmatter.affiliations; | ||
} | ||
else { | ||
affiliations = pageFrontmatter.affiliations; | ||
extraAffiliations = projectFrontmatter.affiliations; | ||
} | ||
if (affiliations) { | ||
const projectAffLookup = {}; | ||
extraAffiliations === null || extraAffiliations === void 0 ? void 0 : extraAffiliations.forEach((aff) => { | ||
if (aff.id && !isStashPlaceholder(aff)) { | ||
projectAffLookup[aff.id] = aff; | ||
// Gather all contributors and affiliations from funding sources | ||
const contributorIds = new Set(); | ||
const affiliationIds = new Set(); | ||
(_e = frontmatter.funding) === null || _e === void 0 ? void 0 : _e.forEach((fund) => { | ||
var _a; | ||
(_a = fund.awards) === null || _a === void 0 ? void 0 : _a.forEach((award) => { | ||
var _a, _b, _c; | ||
(_a = award.investigators) === null || _a === void 0 ? void 0 : _a.forEach((inv) => { | ||
contributorIds.add(inv); | ||
}); | ||
(_b = award.recipients) === null || _b === void 0 ? void 0 : _b.forEach((rec) => { | ||
contributorIds.add(rec); | ||
}); | ||
(_c = award.sources) === null || _c === void 0 ? void 0 : _c.forEach((aff) => { | ||
affiliationIds.add(aff); | ||
}); | ||
}); | ||
}); | ||
if (((_f = frontmatter.authors) === null || _f === void 0 ? void 0 : _f.length) || contributorIds.size) { | ||
// Gather all people from page/project authors/contributors | ||
const people = [ | ||
...((_g = pageFrontmatter.authors) !== null && _g !== void 0 ? _g : []), | ||
...((_h = projectFrontmatter.authors) !== null && _h !== void 0 ? _h : []), | ||
...((_j = pageFrontmatter.contributors) !== null && _j !== void 0 ? _j : []), | ||
...((_k = projectFrontmatter.contributors) !== null && _k !== void 0 ? _k : []), | ||
]; | ||
const peopleLookup = {}; | ||
people.forEach((auth) => { | ||
if (!auth.id || isStashPlaceholder(auth)) | ||
return; | ||
if (!peopleLookup[auth.id]) { | ||
peopleLookup[auth.id] = auth; | ||
} | ||
else if (normalizedString(auth) !== normalizedString(peopleLookup[auth.id])) { | ||
validationWarning(`Duplicate contributor id within project: ${auth.id}`, incrementOptions('authors', opts)); | ||
} | ||
}); | ||
frontmatter.affiliations = affiliations.map((aff) => { | ||
if (!aff.id || !projectAffLookup[aff.id]) { | ||
return aff; | ||
if ((_l = frontmatter.authors) === null || _l === void 0 ? void 0 : _l.length) { | ||
frontmatter.authors = frontmatter.authors.map((auth) => { | ||
var _a; | ||
if (!auth.id) | ||
return auth; | ||
// If contributors are in final author list, do not add to contributor list | ||
contributorIds.delete(auth.id); | ||
return (_a = peopleLookup[auth.id]) !== null && _a !== void 0 ? _a : stashPlaceholder(auth.id); | ||
}); | ||
} | ||
if (contributorIds.size) { | ||
frontmatter.contributors = [...contributorIds].map((id) => { | ||
var _a; | ||
return (_a = peopleLookup[id]) !== null && _a !== void 0 ? _a : stashPlaceholder(id); | ||
}); | ||
} | ||
} | ||
// Add affiliations from reconstructed author/contributor lists and explicit page affiliations | ||
[...((_m = frontmatter.authors) !== null && _m !== void 0 ? _m : []), ...((_o = frontmatter.contributors) !== null && _o !== void 0 ? _o : [])].forEach((auth) => { | ||
var _a; | ||
(_a = auth.affiliations) === null || _a === void 0 ? void 0 : _a.forEach((aff) => { | ||
affiliationIds.add(aff); | ||
}); | ||
}); | ||
(_p = frontmatter.affiliations) === null || _p === void 0 ? void 0 : _p.forEach((aff) => { | ||
if (aff.id) | ||
affiliationIds.add(aff.id); | ||
}); | ||
if (affiliationIds.size) { | ||
const affiliations = [ | ||
...((_q = pageFrontmatter.affiliations) !== null && _q !== void 0 ? _q : []), | ||
...((_r = projectFrontmatter.affiliations) !== null && _r !== void 0 ? _r : []), | ||
]; | ||
const affiliationLookup = {}; | ||
affiliations.forEach((aff) => { | ||
if (!aff.id || isStashPlaceholder(aff)) | ||
return; | ||
if (!affiliationLookup[aff.id]) { | ||
affiliationLookup[aff.id] = aff; | ||
} | ||
else if (isStashPlaceholder(aff)) { | ||
return projectAffLookup[aff.id]; | ||
} | ||
else if (normalizedString(aff) !== normalizedString(projectAffLookup[aff.id])) { | ||
else if (normalizedString(aff) !== normalizedString(affiliationLookup[aff.id])) { | ||
validationWarning(`Duplicate affiliation id within project: ${aff.id}`, incrementOptions('affiliations', opts)); | ||
} | ||
return aff; | ||
}); | ||
frontmatter.affiliations = [...affiliationIds].map((id) => { | ||
var _a; | ||
return (_a = affiliationLookup[id]) !== null && _a !== void 0 ? _a : stashPlaceholder(id); | ||
}); | ||
} | ||
@@ -1133,0 +1302,0 @@ return frontmatter; |
{ | ||
"name": "myst-frontmatter", | ||
"version": "1.1.5", | ||
"version": "1.1.6", | ||
"sideEffects": false, | ||
@@ -43,4 +43,5 @@ "license": "MIT", | ||
"@types/spdx-correct": "^3.1.0", | ||
"js-yaml": "^4.1.0", | ||
"moment": "^2.29.4" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
142235
41
3998
3