Files
bqkc/.github/_plugins/cb_page_gen.rb
Nasir Anthony Montalvo 0a854da998 reconnect
2026-01-26 02:25:43 -06:00

213 lines
10 KiB
Ruby

# frozen_string_literal: true
#########
#
# CollectionBuilder Page Generator, v1.3-csv
#
# Jekyll plugin to generate pages from _data/ files.
# Designed to create Item pages from metadata CSV for digital collection sites.
# (c) 2021 CollectionBuilder, evanwill, https://github.com/CollectionBuilder/
# Distributed under the conditions of the MIT license
#
# Originally inspired by jekyll-datapage_gen, https://github.com/avillafiorita/jekyll-datapage_gen
#
#########
module CollectionBuilderPageGenerator
class ItemPageGenerator < Jekyll::Generator
safe true
# include jekyll utils so can use slugify
include Jekyll::Utils
# main function to read config, data, and generate pages
def generate(site)
#########
#
# Default Settings
#
# These values are used if not configured in the 'page_gen' object in _config.yml
# Defaults follow CollectionBuilder specific conventions.
#
data_file_default = site.config['metadata'] || 'metadata' # _data to use
template_location_default = "item/" # folder in _layouts used to organize templates, ends with slash, empty if using root
template_default = 'item' # layout to use for all pages by default
display_template_default = 'display_template' # metadata column to use to assign layout
name_default = 'objectid' # value to use for filename
dir_default = 'items' # where to output pages
extension_default = 'html' # extension, usually html
filter_default = 'objectid' # value to filter records on, filters on objectid by default
filter_condition_default = '!record["parentid"]' # expression to filter records on, default filters rows with a parentid
#
######
# get optional configuration from _config.yml, or create a single default one from CB metadata setting
configure_gen = site.config['page_gen'] || [{ 'data' => data_file_default }]
# iterate over each instance in configuration
# this allows to generate from multiple _data sources
configure_gen.each do |data_config|
data_file = data_config['data'] || data_file_default
template_location = data_config['template_location'] || template_location_default
template = template_location + (data_config['template'] || template_default)
display_template = data_config['display_template'] || display_template_default
name = data_config['name'] || name_default
dir = data_config['dir'] || dir_default
extension = data_config['extension'] || extension_default
filter = data_config['filter'] || filter_default
filter_condition = data_config['filter_condition'] || filter_condition_default
# check if data value uses .csv extension, if so provide error message and skip. This avoids common CB error.
if data_file.split('.')[1] == "csv"
puts color_text("Error cb_page_gen: metadata value '#{data_file}' includes '.csv' extension. Please remove the extension from _config.yml 'metadata' or page_gen 'data' value. Pages are NOT being generated from '#{data_file}'!", :red)
next
end
# check if data file exists, if not provide error message and skip. This supports nested key yml or json data sources
if !site.data.key? data_file.split('.')[0]
puts color_text("Error cb_page_gen: Data value '#{data_file}' does not match any site data. Please check _config.yml 'metadata' or page_gen 'data' value. Common issues are spelling errors. Pages are NOT being generated from '#{data_file}'!", :red)
next
end
# Get the records to generate pages from
# note: this splits on . to support a nested key in yml or json for page gen.
# however, CB template and other plugins do not support nested yml or json for metadata items.
# items described in unnested yml or json will work for both page gen and CB template pages.
records = nil
data_file.split('.').each do |level|
if records.nil?
records = site.data[level]
else
records = records[level]
end
end
# Filter records if filter is configured (default is on objectid)
if filter
filtered_records = records.select { |r| r[filter] }
filtered_number = records.size - filtered_records.size
# provide notice if filter is applied
puts color_text("Notice cb_page_gen: filter on '#{filter}' is applied. #{filtered_number} records are filtered because they do not have a value in '#{filter}'.", :green) if filtered_number != 0
records = filtered_records
end
# Filter records if filter_condition is configured
if filter_condition
filtered_records = records.select { |record| eval(filter_condition) }
filtered_number = records.size - filtered_records.size
records = filtered_records
# provide notice if non-default filter is applied
if filter_condition != filter_condition_default
puts color_text("Notice cb_page_gen: filter_condition '#{filter_condition}' is applied. #{filtered_number} records are filtered.", :green) if filtered_number != 0
end
end
# Check for unique names, if not provide error message
names_test = records.map { |x| x[name] }
if names_test.size != names_test.uniq.size
puts color_text("Error cb_page_gen: some values in '#{name}' are not unique! This means those pages will overwrite each other, so you will be missing some Item pages. Please check '#{name}' and make them all unique.", :red)
end
# Check for missing layouts
template_test = records.map { |x| x[display_template] ? template_location + x[display_template].strip : template }.uniq
all_layouts = site.layouts.keys
missing_layouts = (template_test - all_layouts)
if !missing_layouts.empty? # if there is missing layouts
if all_layouts.include? template
# if there is a valid default layout fallback, continue
puts color_text("Notice cb_page_gen: could not find layout(s) #{missing_layouts.join(', ')} in '_layouts/'. Records with these display_template will fallback to the default layout '#{template}'. If this is unexpected, please add the missing layout(s) or check configuration of 'template' and 'display_template' field.", :yellow)
else
# if there is no valid fallback / template
puts color_text("Notice cb_page_gen: could not find layout(s) #{missing_layouts.join(', ')} in '_layouts/'. This includes the default layout '#{template}'. Please add the layout(s) or check configuration of 'template' and 'display_template'. Item pages will not be generated for records using the missing layouts!", :yellow)
#next
end
end
# Generate pages for each record
records.each_with_index do |record, index|
# Check for valid name, skip page gen if none
if record[name].nil? || record[name].strip.empty?
puts color_text("Notice cb_page_gen: record '#{index}' in '#{data_file}' does not have a value in '#{name}'! This record will be skipped.", :yellow)
next
end
# create clean filename with Jekyll Slugify pretty mode
# this ensures safe filenames, but may cause unintended issues with links if objectid are not well formed
record['base_filename'] = slugify(record[name], mode: "pretty").to_s
puts color_text("Notice cb_page_gen: record '#{index}' in '#{data_file}', '#{record[name]}' is being sanitized to create a valid filename. This may cause issues with links generated on other pages. Please check the naming convention used in '#{name}' field.", :yellow) if record['base_filename'] != record[name]
# Provide index number for page object
record['index_number'] = index
# Find next item
if index == records.size - 1
next_item = records[0][name]
else
next_item = records[index + 1][name]
end
record['next_item'] = "/" + dir + "/" + slugify(next_item, mode: "pretty").to_s + "." + extension.to_s
# Find previous item
if index == 0
previous_item = records[records.size - 1][name]
else
previous_item = records[index -1][name]
end
record['previous_item'] = "/" + dir + "/" + slugify(previous_item, mode: "pretty").to_s + "." + extension.to_s
# Add layout value from display_template or the default
if record[display_template]
record['layout'] = template_location + record[display_template].strip
# if not valid layout, fall back to template default
if !all_layouts.include? record['layout']
record['layout'] = template
end
else
record['layout'] = template
end
# Check if layout exists, if not provide error message and skip
if !all_layouts.include? record['layout']
puts color_text("Error cb_page_gen: Could not find layout '#{record['layout']}'. Please check configuration or add the layout. Item page NOT generated for record '#{index}' in '#{data_file}'!", :red)
next
end
# Pass the page data to the ItemPage generator
site.pages << ItemPage.new(site, record, dir, extension)
end
end
end
# Color helper, to add warning colors to message outputs
# use like: puts color_text("example", :red)
def text_colors
@colors = {
red: 31,
yellow: 33,
green: 32
}
end
def color_text(str, color_code)
"\e[#{text_colors[color_code]}m#{str}\e[0m"
end
end
# Subclass of `Jekyll::Page` with custom method definitions.
class ItemPage < Jekyll::Page
# function to generate each individual page
def initialize(site, record, dir, extension)
@site = site # the current site instance.
@base = site.source # path to the source directory.
@dir = dir # the directory the page will output in
@basename = record['base_filename'] # filename without the extension.
@ext = "." + extension.to_s # the extension.
@name = record['base_filename'] + "." + extension.to_s # @basename + @ext.
# add record data to the page
# all record data will be available in page object
@data = record
end
end
end