Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions .github/ISSUE_TEMPLATE/suggest-bibliography.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,20 @@ body:
label: Relevant method tags
multiple: true
options:
# extraction
- silica-column
- magnetic-bead
- phenol-chloroform
- ctab
- chelex
- tagmentation
- ligation
- commercial-kit
- diy-approach
# library
- single-stranded
- pcr-free
- hybridization-capture
- double-stranded
# sequencing
- genome-skimming
- shotgun-wgs
- target-capture
- metabarcoding
- rad-seq
- sanger
validations:
required: false
- type: input
Expand Down
19 changes: 6 additions & 13 deletions .github/ISSUE_TEMPLATE/suggest-method.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,10 @@ body:
label: Preservation type*
multiple: true
options:
- dry-pinned
- study-skin
- herbarium-pressed
- dry
- fluid-ethanol
- fluid-formalin
- frozen
- dried-tissue
- bone-subfossil
- "Other (describe in detail box)"
validations:
required: true
Expand All @@ -79,7 +75,8 @@ body:
- magnetic-bead
- phenol-chloroform
- ctab
- chelex
- commercial-kit
- diy-approach
- "Other (describe in detail box)"
validations:
required: true
Expand All @@ -89,11 +86,10 @@ body:
label: Library prep method*
multiple: true
options:
- tagmentation
- ligation
- single-stranded
- pcr-free
- hybridization-capture
- double-stranded
- commercial-kit
- diy-approach
- "Other (describe in detail box)"
validations:
required: true
Expand All @@ -106,9 +102,6 @@ body:
- genome-skimming
- shotgun-wgs
- target-capture
- metabarcoding
- rad-seq
- sanger
- "Other (describe in detail box)"
validations:
required: true
Expand Down
10 changes: 6 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ truth. CI (`scripts/validate_entries.rb`) fails the build if an entry uses a
value that isn't listed there, which keeps filtering consistent and stops typos
fragmenting the taxonomy.

**Need a new tag value?** Add it to `_data/tags.yml` in the same PR. If that
value is also offered as a dropdown in an issue form, add it to the matching
list in `.github/ISSUE_TEMPLATE/*.yml` too — the forms can't read `tags.yml`
automatically. The maintainer curates this vocabulary.
**Need a new tag value?** Add it to `_data/tags.yml` in the same PR, and add it
to the matching dropdown in `.github/ISSUE_TEMPLATE/*.yml` too — the forms can't
read `tags.yml` automatically. CI enforces this: `scripts/validate_entries.rb`
fails if a form dropdown and the vocabulary drift apart in either direction (a
stale option, or a vocab value the form doesn't offer). The maintainer curates
this vocabulary.

## Local preview

Expand Down
57 changes: 53 additions & 4 deletions scripts/validate_entries.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# Validates every method and bibliography entry against the controlled
# vocabulary in _data/tags.yml and a small required-field schema.
# Exits non-zero (failing CI) if any entry is invalid. Run:
# Validates (1) every method and bibliography entry against the controlled
# vocabulary in _data/tags.yml and a small required-field schema, and (2) that
# the GitHub Issue Form dropdowns stay in sync with that vocabulary (the forms
# can't read _data/tags.yml, so this guards against drift).
# Exits non-zero (failing CI) if anything is invalid. Run:
# ruby scripts/validate_entries.rb

require "yaml"
require "date"

ROOT = File.expand_path("..", __dir__)
TAGS = YAML.safe_load_file(File.join(ROOT, "_data", "tags.yml"))
FACETS = YAML.safe_load_file(File.join(ROOT, "_data", "facets.yml"))

errors = []

Expand Down Expand Up @@ -96,9 +99,55 @@ def as_array(v)
end
end

# --- issue-form vocabulary sync -------------------------------------------
# Each form dropdown must offer exactly the vocabulary it maps to: no stale
# values (offered but no longer in tags.yml) and no missing values (in the
# vocab but not offered). The "Other (describe…)" escape hatch is exempt.
def facet_vocab(facet)
facet["grouped"] ? TAGS[facet["key"]].values.flatten : (TAGS[facet["key"]] || [])
end

def check_form(rel, expected, errors)
path = File.join(ROOT, rel)
unless File.exist?(path)
errors << "#{rel}: file not found"
return
end
form = YAML.safe_load_file(path)
dropdowns = (form["body"] || []).each_with_object({}) do |b, h|
h[b["id"]] = b if b.is_a?(Hash) && b["type"] == "dropdown"
end

expected.each do |id, vocab|
field = dropdowns[id]
unless field
errors << "#{rel}: expected dropdown `#{id}` is missing"
next
end
offered = Array(field.dig("attributes", "options")).reject { |o| o.to_s.start_with?("Other") }

dupes = offered.select { |o| offered.count(o) > 1 }.uniq
dupes.each { |v| errors << "#{rel}: `#{id}` lists \"#{v}\" more than once" }

(offered - vocab).each { |v| errors << "#{rel}: `#{id}` offers \"#{v}\" which is not in _data/tags.yml" }
(vocab - offered).each { |v| errors << "#{rel}: `#{id}` is missing vocab value \"#{v}\" (add it or the form drifts from tags.yml)" }
end
end

# Method form: one dropdown per method-applicable facet, plus outcome.
method_expected = {}
FACETS.select { |f| f["methods"] }.each { |f| method_expected[f["key"]] = facet_vocab(f) }
method_expected["outcome"] = TAGS["outcome"] || []
check_form(".github/ISSUE_TEMPLATE/suggest-method.yml", method_expected, errors)

# Bibliography form: a single `tags` dropdown = union of bibliography-applicable
# facet vocabularies (deduped).
bib_tag_vocab = FACETS.select { |f| f["bibliography"] }.flat_map { |f| facet_vocab(f) }.uniq
check_form(".github/ISSUE_TEMPLATE/suggest-bibliography.yml", { "tags" => bib_tag_vocab }, errors)

# --- report ----------------------------------------------------------------
if errors.empty?
puts "✓ All entries valid."
puts "✓ All entries and issue forms valid."
exit 0
else
warn "✗ #{errors.length} validation error(s):"
Expand Down
Loading