Module: Theoj::MystFrontmatter
- Defined in:
- lib/theoj/myst_frontmatter.rb
Overview
Fill gaps in a paper's front matter from the project's myst.yml.
A NeuroLibre submission declares its title, authors, and affiliations in myst.yml for the living preprint, so paper.md need not repeat them. The deposit path still wants them in the shape paper.md front matter uses: affiliations numbered by index, and each author's affiliations as a comma-joined string of those indices.
This is a port of inara's data/filters/myst-frontmatter.lua, which is the canonical statement of the mapping; full-stack-server's api/myst_frontmatter.py mirrors the same rules on the Python side. Keep the three in step -- an author whose affiliations resolve differently depending on which service is looking is worse than one that fails outright.
The fallback is best-effort by design: a missing, unreadable, or malformed myst.yml leaves the metadata exactly as it was. It must never itself be the reason a deposit fails.
Constant Summary collapse
- MYST_FILE =
"myst.yml".freeze
- NAME_PARTS =
Parts of a myst.yml affiliation, joined into one name string. Department precedes institution to match the convention in existing NeuroLibre front matter.
%w[ department institution address city region postal_code country ].freeze
- ALIASES =
MyST accepts these aliases for two of the parts.
{ "institution" => "name", "region" => "state" }.freeze
- SCALAR_KEYS =
Keys that fill individually, unlike authors and affiliations.
%w[title date tags bibliography].freeze
Class Method Summary collapse
-
.config_text(paper_path, search_root: nil) ⇒ Object
The contents of the nearest myst.yml at or above the paper, or nil.
-
.merge(front_matter, myst_text) ⇒ Object
Returns the paper's metadata with any gap filled from myst.yml.
-
.project_metadata(project) ⇒ Object
Returns paper metadata derived from a myst.yml
projectmapping, holding only the keys the project actually defines so the caller can treat it as a set of defaults.
Class Method Details
.config_text(paper_path, search_root: nil) ⇒ Object
The contents of the nearest myst.yml at or above the paper, or nil.
paper_path - path to the paper (paper.md, paper.tex ...). search_root - the directory the walk may climb to, normally the root of a cloned repository. When nil the walk stops at the first directory holding a .git, which is the repository root for a plain checkout; failing that, at the paper's own directory. myst.yml sits at the project root while the paper is often nested (content/paper.md), so the walk has to happen -- but it must never wander out of the tree the caller meant.
116 117 118 119 |
# File 'lib/theoj/myst_frontmatter.rb', line 116 def config_text(paper_path, search_root: nil) path = config_path(paper_path, search_root) path.nil? ? nil : File.read(path) end |
.merge(front_matter, myst_text) ⇒ Object
Returns the paper's metadata with any gap filled from myst.yml.
front_matter - the already-parsed paper.md front matter, or nil for a paper that has none. myst_text - the raw contents of myst.yml, or nil. Parsed here rather than in the caller so a malformed file is tolerated in one place.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/theoj/myst_frontmatter.rb', line 48 def merge(front_matter, myst_text) = front_matter.is_a?(Hash) ? front_matter.dup : {} return if myst_text.to_s.strip.empty? fallback = (parse_project(myst_text)) # Authors and affiliations are filled as a pair. An affiliation index # only means something relative to the list that defines it, so mixing # front matter authors with myst.yml affiliations would silently attach # authors to the wrong institutions. if blank?(["authors"]) || blank?(["affiliations"]) unless blank?(fallback["authors"]) unless blank?(["authors"]) warn "[neurolibre] #{MYST_FILE}: the paper names authors but no " \ "affiliations, so its author list is replaced by the one in " \ "#{MYST_FILE} rather than merged -- an affiliation index only " \ "means something relative to the list that defines it." end ["authors"] = fallback["authors"] ["affiliations"] = fallback["affiliations"] || [] end end SCALAR_KEYS.each do |key| [key] = fallback[key] if blank?([key]) && fallback.key?(key) end end |
.project_metadata(project) ⇒ Object
Returns paper metadata derived from a myst.yml project mapping,
holding only the keys the project actually defines so the caller can
treat it as a set of defaults. Junk input yields an empty hash.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/theoj/myst_frontmatter.rb', line 81 def (project) return {} unless project.is_a?(Hash) = {} ["title"] = project["title"] unless blank?(project["title"]) unless blank?(project["date"]) # `date: 2024-01-15` -- unquoted ISO, the MyST-canonical form -- # parses to a Date. This value ends up in a deposit payload, and # nothing downstream reads it structurally, so the string form is the # right shape. date = project["date"] ["date"] = date.is_a?(String) ? date : date.to_s end ["tags"] = project["keywords"] unless blank?(project["keywords"]) ["bibliography"] = project["bibliography"] unless blank?(project["bibliography"]) affiliations, index_of = build_affiliations(project) = (project, affiliations, index_of) ["authors"] = unless .empty? ["affiliations"] = affiliations unless affiliations.empty? end |