samhuri.net/lib/config/loader.rb

157 lines
5.1 KiB
Ruby

require_relative "../site"
require_relative "../posts/plugin"
require_relative "../projects/plugin"
require_relative "../utils/markdown_renderer"
require_relative "simple_toml"
module Pressa
module Config
class ValidationError < StandardError; end
class Loader
REQUIRED_SITE_KEYS = %w[author email title description url].freeze
REQUIRED_PROJECT_KEYS = %w[name title description url].freeze
def initialize(source_path:)
@source_path = source_path
end
def build_site(url_override: nil)
site_config = load_toml("site.toml")
projects_config = load_toml("projects.toml")
validate_required!(site_config, REQUIRED_SITE_KEYS, context: "site.toml")
site_url = url_override || site_config["url"]
projects_plugin = hash_or_empty(site_config["projects_plugin"], "site.toml projects_plugin")
projects = build_projects(projects_config)
Site.new(
author: site_config["author"],
email: site_config["email"],
title: site_config["title"],
description: site_config["description"],
url: site_url,
image_url: normalize_image_url(site_config["image_url"], site_url),
scripts: build_scripts(site_config["scripts"], context: "site.toml scripts"),
styles: build_styles(site_config["styles"], context: "site.toml styles"),
plugins: [
Posts::Plugin.new,
Projects::Plugin.new(
projects:,
scripts: build_scripts(projects_plugin["scripts"], context: "site.toml projects_plugin.scripts"),
styles: build_styles(projects_plugin["styles"], context: "site.toml projects_plugin.styles")
)
],
renderers: [
Utils::MarkdownRenderer.new
]
)
end
private
def load_toml(filename)
path = File.join(@source_path, filename)
SimpleToml.load_file(path)
rescue ParseError => e
raise ValidationError, e.message
end
def build_projects(projects_config)
projects = projects_config["projects"]
raise ValidationError, "Missing required top-level array 'projects' in projects.toml" unless projects
raise ValidationError, "Expected 'projects' to be an array in projects.toml" unless projects.is_a?(Array)
projects.map.with_index do |project, index|
unless project.is_a?(Hash)
raise ValidationError, "Project entry #{index + 1} must be a table in projects.toml"
end
validate_required!(project, REQUIRED_PROJECT_KEYS, context: "projects.toml project ##{index + 1}")
Projects::Project.new(
name: project["name"],
title: project["title"],
description: project["description"],
url: project["url"]
)
end
end
def validate_required!(hash, keys, context:)
missing = keys.reject do |key|
hash[key].is_a?(String) && !hash[key].strip.empty?
end
return if missing.empty?
raise ValidationError, "Missing required #{context} keys: #{missing.join(", ")}"
end
def hash_or_empty(value, context)
return {} if value.nil?
return value if value.is_a?(Hash)
raise ValidationError, "Expected #{context} to be a table"
end
def build_scripts(value, context:)
entries = array_or_empty(value, context)
entries.map.with_index do |item, index|
case item
when String
Script.new(src: item, defer: true)
when Hash
src = item["src"]
raise ValidationError, "Expected #{context}[#{index}].src to be a String" unless src.is_a?(String) && !src.empty?
defer = item.key?("defer") ? item["defer"] : true
unless [true, false].include?(defer)
raise ValidationError, "Expected #{context}[#{index}].defer to be a Boolean"
end
Script.new(src:, defer:)
else
raise ValidationError, "Expected #{context}[#{index}] to be a String or table"
end
end
end
def build_styles(value, context:)
entries = array_or_empty(value, context)
entries.map.with_index do |item, index|
case item
when String
Stylesheet.new(href: item)
when Hash
href = item["href"]
raise ValidationError, "Expected #{context}[#{index}].href to be a String" unless href.is_a?(String) && !href.empty?
Stylesheet.new(href:)
else
raise ValidationError, "Expected #{context}[#{index}] to be a String or table"
end
end
end
def array_or_empty(value, context)
return [] if value.nil?
return value if value.is_a?(Array)
raise ValidationError, "Expected #{context} to be an array"
end
def normalize_image_url(value, site_url)
return nil if value.nil?
return value if value.start_with?("http://", "https://")
normalized = value.start_with?("/") ? value : "/#{value}"
"#{site_url}#{normalized}"
end
end
end
end