Skip to content

Commit

Permalink
πŸ‘©β€πŸ”¬ Add more frontmatter fields for different venue types (#1503)
Browse files Browse the repository at this point in the history
  • Loading branch information
fwkoch authored Oct 3, 2024
1 parent 64a3383 commit 3c65de0
Show file tree
Hide file tree
Showing 21 changed files with 392 additions and 131 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-toes-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'myst-frontmatter': patch
---

Add more frontmatter fields for different venue types
7 changes: 7 additions & 0 deletions .changeset/young-snails-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'myst-frontmatter': patch
'myst-to-jats': patch
'myst-cli': patch
---

Deprecate biblio in favor of complete volume/issue objects
91 changes: 70 additions & 21 deletions docs/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,19 @@ The following table lists the available frontmatter fields, a brief description
- a string (max 40 chars)
- page can override project
* - `venue`
- a venue object
- a venue object with journal and conference metadata fields
- page can override project
* - `biblio`
- a biblio object with various fields
* - `volume`
- information about the journal volume, see [](#publication-metadata)
- page can override project
* - `issue`
- information about the journal issue, see [](#publication-metadata)
- page can override project
* - `first_page`
- first page of the project or article, for published works
- page can override project
* - `last_page`
- last page of the project or article, for published works
- page can override project
* - `math`
- a dictionary of math macros (see [](#math-macros))
Expand Down Expand Up @@ -747,7 +756,34 @@ The term `venue` is borrowed from the [OpenAlex](https://docs.openalex.org/about

> Venues are where works are hosted.

Available fields in the `venue` object are `title`, `short_title` (or title abbreviation), and `url`. You may also provide a DOI under `biblio`; this DOI is the _venue_ DOI.
For MyST frontmatter, the `venue` object holds metadata for journals and conferences where a work may be presented.

```{list-table} Available Venue fields
:header-rows: 1
:label: table-frontmatter-venue
* - field
- description
* - `title`
- full title of the venue
* - `short_title`
- short title of the venue; often journals have a standard abbreviation that should be defined here
* - `url`
- URL of the venue
* - `doi`
- the _venue_ DOI
* - `number`
- number of the venue in a series, for example the "18th Python in Science Conference"
* - `location`
- physical location of a conference
* - `date`
- date associated with the venue, for example the dates of a conference. This field is a string, not a timestamp, so it may be a date range.
* - `series`
- title of a series that this venue or work is part of. Examples include a conference proceedings series, where each year a new conference-specific proceedings journal is created, or a category of articles across multiple issues, such as colloquium papers.
* - `issn`
- ISSN for the publication
* - `publisher`
- publisher of the journal
```
Some typical `venue` values may be:

Expand All @@ -766,30 +802,43 @@ venue:
url: https://www.euroscipy.org/2022
```

## Biblio
(publication-metadata)=

The term `biblio` is borrowed from the [OpenAlex](https://docs.openalex.org/about-the-data/venue) API definition:
## Publication Metadata

> Old-timey bibliographic info for this work. This is mostly useful only in citation/reference contexts. These are all strings because sometimes you'll get fun values like "Spring" and "Inside cover."

Available fields in the `biblio` object are `volume`, `issue`, `first_page` and `last_page`. You may also provide a DOI under `biblio`; this DOI is the _issue_ DOI.
MyST includes several fields to maintain bibliographic metadata for journal publications. First, it has `first_page` and `last_page` - these are page numbers for the article in its printed form. Also, `volume` and `issue` are available to describe the journal volume/issue containing the article. Each of these properties has the same fields available, described in @table-frontmatter-biblio.

Some example `biblio` values may be:

```yaml
biblio:
volume: '42'
issue: '3'
first_page: '1' # can be a number or string
last_page: '99' # can be a number or string
```{list-table} Available Volume and Issue fields
:header-rows: 1
:label: table-frontmatter-biblio
* - field
- description
* - `number`
- a string or a number to identify journal volume/issue
* - `title`
- title of the volume/issue, if provided separately from number
* - `subject`
- description of the subject of the volume/issue
* - `doi`
- the volume/issue DOI
```
OR
An example of publication metadata for an article may be:
```yaml
biblio:
volume: '2022'
issue: Winter
first_page: Inside cover # can be a number or string
first_page: 1500
last_page: 1503
volume:
number: 12
issue:
name: Winter
description: Special issue on software documentation
doi: 10.62329/MYISSUE
```
These fields provide a more complete superset of publication metadata than the ["biblio" object defined by OpenAlex API](https://docs.openalex.org/about-the-data/venue):
> Old-timey bibliographic info for this work. This is mostly useful only in citation/reference contexts. These are all strings because sometimes you'll get fun values like "Spring" and "Inside cover."
If MyST frontmatter includes an OpenAlex `biblio` object, it will be coerced to valid publication metadata.
52 changes: 52 additions & 0 deletions packages/myst-cli/src/config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, it, beforeEach, vi } from 'vitest';
import memfs from 'memfs';
import { Session } from '../session';
import { projectFromPath } from './fromPath';
import { pagesFromSphinxTOC, projectFromSphinxTOC } from './fromTOC';
import { tocFromProject } from './toTOC';
import { handleDeprecatedFields } from './config';
import { VFile } from 'vfile';

describe('config file coercion', () => {
it('site frontmatter is lifted', async () => {
const vfile = new VFile();
const conf = { site: { frontmatter: { a: 1 }, other: true } };
handleDeprecatedFields(conf, '', vfile);
expect(conf).toEqual({ site: { a: 1, other: true } });
expect(vfile.messages.length).toBe(1);
});
it('project frontmatter is lifted', async () => {
const vfile = new VFile();
const conf = { project: { frontmatter: { a: 1 }, other: true } };
handleDeprecatedFields(conf, '', vfile);
expect(conf).toEqual({ project: { a: 1, other: true } });
expect(vfile.messages.length).toBe(1);
});
it('site logoText is renamed logo_text', async () => {
const vfile = new VFile();
const conf = { site: { logoText: 'my logo', other: true } };
handleDeprecatedFields(conf, '', vfile);
expect(conf).toEqual({ site: { logo_text: 'my logo', other: true } });
expect(vfile.messages.length).toBe(1);
});
it('project biblio is lifted', async () => {
const vfile = new VFile();
const conf = {
project: {
biblio: { volume: 12, issue: 'spring', first_page: 1, last_page: 100 },
other: true,
},
};
handleDeprecatedFields(conf, '', vfile);
expect(conf).toEqual({
project: {
volume: 12,
issue: 'spring',
first_page: 1,
last_page: 100,
other: true,
},
});
expect(vfile.messages.length).toBe(1);
});
});
73 changes: 48 additions & 25 deletions packages/myst-cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,53 @@ function fillSiteConfig(base: SiteConfig, filler: SiteConfig, opts: ValidationOp
return fillSiteFrontmatter(base, filler, opts, Object.keys(filler));
}

/**
* Mutate config object to coerce deprecated frontmatter fields to valid schema
*/
export function handleDeprecatedFields(
conf: {
site?: Record<string, any>;
project?: Record<string, any>;
},
file: string,
vfile: VFile,
) {
if (conf.site?.frontmatter) {
fileWarn(
vfile,
`Frontmatter fields should be defined directly on site, not nested under "${file}#site.frontmatter"`,
{ ruleId: RuleId.configHasNoDeprecatedFields },
);
const { frontmatter, ...rest } = conf.site;
conf.site = { ...frontmatter, ...rest };
}
if (conf.project?.frontmatter) {
fileWarn(
vfile,
`Frontmatter fields should be defined directly on project, not nested under "${file}#project.frontmatter"`,
{ ruleId: RuleId.configHasNoDeprecatedFields },
);
const { frontmatter, ...rest } = conf.project;
conf.project = { ...frontmatter, ...rest };
}
if (conf.project?.biblio) {
fileWarn(
vfile,
`biblio is deprecated, please use first_page/last_page/volume/issue fields "${file}#project"`,
{ ruleId: RuleId.configHasNoDeprecatedFields },
);
const { biblio, ...rest } = conf.project;
conf.project = { ...biblio, ...rest };
}
if (conf.site?.logoText) {
fileWarn(vfile, `logoText is deprecated, please use logo_text in "${file}#site"`, {
ruleId: RuleId.configHasNoDeprecatedFields,
});
const { logoText, ...rest } = conf.site;
conf.site = { logo_text: logoText, ...rest };
}
}

/**
* Load and validate a file as yaml config file
*
Expand Down Expand Up @@ -125,31 +172,7 @@ async function getValidatedConfigsFromFile(
throw Error(`Please address invalid config file ${file}`);
}
// Keep original config object with extra keys, etc.
if (conf.site?.frontmatter) {
fileWarn(
vfile,
`Frontmatter fields should be defined directly on site, not nested under "${file}#site.frontmatter"`,
{ ruleId: RuleId.configHasNoDeprecatedFields },
);
const { frontmatter, ...rest } = conf.site;
conf.site = { ...frontmatter, ...rest };
}
if (conf.project?.frontmatter) {
fileWarn(
vfile,
`Frontmatter fields should be defined directly on project, not nested under "${file}#project.frontmatter"`,
{ ruleId: RuleId.configHasNoDeprecatedFields },
);
const { frontmatter, ...rest } = conf.project;
conf.project = { ...frontmatter, ...rest };
}
if (conf.site?.logoText) {
fileWarn(vfile, `logoText is deprecated, please use logo_text in "${file}#site"`, {
ruleId: RuleId.configHasNoDeprecatedFields,
});
const { logoText, ...rest } = conf.site;
conf.site = { logo_text: logoText, ...rest };
}
handleDeprecatedFields(conf, file, vfile);
let site: SiteConfig | undefined;
let project: ProjectConfig | undefined;
const projectOpts = configValidationOpts(vfile, 'config.project', RuleId.validProjectConfig);
Expand Down
53 changes: 36 additions & 17 deletions packages/myst-frontmatter/src/biblio/biblio.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,48 @@
title: Biblio
cases:
- title: empty object returns self
- title: empty object is removed
raw:
biblio: {}
normalized:
biblio: {}
volume: {}
issue: {}
normalized: {}
- title: extra keys removed
raw:
biblio:
volume:
extra: ''
normalized:
biblio: {}
normalized: {}
warnings: 1
- title: full object returns self
raw:
biblio:
volume: test
issue: example
first_page: 1
last_page: 2
volume:
number: 12
doi: 10.0000/abc123
title: test proceedings
subject: test subject
issue:
name: spring
doi: 10.0000/abc456
title: test proceedings
subject: test subject
normalized:
biblio:
volume: test
issue: example
first_page: 1
last_page: 2
volume:
number: 12
doi: 10.0000/abc123
title: test proceedings
subject: test subject
issue:
number: spring
doi: 10.0000/abc456
title: test proceedings
subject: test subject
- title: number input coerces to number field
raw:
volume: 12
normalized:
volume:
number: 12
- title: string input coerces to number field
raw:
volume: twelve
normalized:
volume:
number: twelve
12 changes: 5 additions & 7 deletions packages/myst-frontmatter/src/biblio/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
export type Biblio = {
// https://docs.openalex.org/about-the-data/work#biblio
volume?: string | number; // sometimes you'll get fun values like "Spring" and "Inside cover."
issue?: string | number;
doi?: string; // Issue DOI
first_page?: string | number;
last_page?: string | number;
export type PublicationMeta = {
number?: string | number; // sometimes you'll get fun values like "Spring" and "Inside cover."
doi?: string;
title?: string;
subject?: string;
};
Loading

0 comments on commit 3c65de0

Please sign in to comment.