mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
Derive site metadata from posts and config
This commit is contained in:
parent
54a0543a7f
commit
ca316bf470
10 changed files with 169 additions and 21 deletions
|
|
@ -141,10 +141,18 @@ module Pressa
|
||||||
entries.map.with_index do |item, index|
|
entries.map.with_index do |item, index|
|
||||||
case item
|
case item
|
||||||
when String
|
when String
|
||||||
|
validate_asset_path!(
|
||||||
|
item,
|
||||||
|
context: "#{context}[#{index}]"
|
||||||
|
)
|
||||||
Script.new(src: item, defer: true)
|
Script.new(src: item, defer: true)
|
||||||
when Hash
|
when Hash
|
||||||
src = item["src"]
|
src = item["src"]
|
||||||
raise ValidationError, "Expected #{context}[#{index}].src to be a String" unless src.is_a?(String) && !src.empty?
|
raise ValidationError, "Expected #{context}[#{index}].src to be a String" unless src.is_a?(String) && !src.empty?
|
||||||
|
validate_asset_path!(
|
||||||
|
src,
|
||||||
|
context: "#{context}[#{index}].src"
|
||||||
|
)
|
||||||
|
|
||||||
defer = item.key?("defer") ? item["defer"] : true
|
defer = item.key?("defer") ? item["defer"] : true
|
||||||
unless [true, false].include?(defer)
|
unless [true, false].include?(defer)
|
||||||
|
|
@ -164,10 +172,18 @@ module Pressa
|
||||||
entries.map.with_index do |item, index|
|
entries.map.with_index do |item, index|
|
||||||
case item
|
case item
|
||||||
when String
|
when String
|
||||||
|
validate_asset_path!(
|
||||||
|
item,
|
||||||
|
context: "#{context}[#{index}]"
|
||||||
|
)
|
||||||
Stylesheet.new(href: item)
|
Stylesheet.new(href: item)
|
||||||
when Hash
|
when Hash
|
||||||
href = item["href"]
|
href = item["href"]
|
||||||
raise ValidationError, "Expected #{context}[#{index}].href to be a String" unless href.is_a?(String) && !href.empty?
|
raise ValidationError, "Expected #{context}[#{index}].href to be a String" unless href.is_a?(String) && !href.empty?
|
||||||
|
validate_asset_path!(
|
||||||
|
href,
|
||||||
|
context: "#{context}[#{index}].href"
|
||||||
|
)
|
||||||
|
|
||||||
Stylesheet.new(href:)
|
Stylesheet.new(href:)
|
||||||
else
|
else
|
||||||
|
|
@ -190,6 +206,12 @@ module Pressa
|
||||||
normalized = value.start_with?("/") ? value : "/#{value}"
|
normalized = value.start_with?("/") ? value : "/#{value}"
|
||||||
"#{site_url}#{normalized}"
|
"#{site_url}#{normalized}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_asset_path!(value, context:)
|
||||||
|
return if value.start_with?("/", "http://", "https://")
|
||||||
|
|
||||||
|
raise ValidationError, "Expected #{context} to start with / or use http(s) scheme"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,10 @@ module Pressa
|
||||||
def recent_posts(limit = 10)
|
def recent_posts(limit = 10)
|
||||||
all_posts.take(limit)
|
all_posts.take(limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def earliest_year
|
||||||
|
by_year.keys.min
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ module Pressa
|
||||||
attribute :description, Types::String
|
attribute :description, Types::String
|
||||||
attribute :url, Types::String
|
attribute :url, Types::String
|
||||||
attribute :image_url, Types::String.optional.default(nil)
|
attribute :image_url, Types::String.optional.default(nil)
|
||||||
|
attribute :copyright_start_year, Types::Integer.optional.default(nil)
|
||||||
attribute :scripts, Types::Array.of(Script).default([].freeze)
|
attribute :scripts, Types::Array.of(Script).default([].freeze)
|
||||||
attribute :styles, Types::Array.of(Stylesheet).default([].freeze)
|
attribute :styles, Types::Array.of(Stylesheet).default([].freeze)
|
||||||
attribute :plugins, Types::Array.default([].freeze)
|
attribute :plugins, Types::Array.default([].freeze)
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,10 @@ module Pressa
|
||||||
FileUtils.rm_rf(target_path)
|
FileUtils.rm_rf(target_path)
|
||||||
FileUtils.mkdir_p(target_path)
|
FileUtils.mkdir_p(target_path)
|
||||||
|
|
||||||
site.plugins.each { |plugin| plugin.setup(site:, source_path:) }
|
setup_site = site
|
||||||
|
setup_site.plugins.each { |plugin| plugin.setup(site: setup_site, source_path:) }
|
||||||
|
|
||||||
|
@site = site_with_copyright_start_year(setup_site)
|
||||||
site.plugins.each { |plugin| plugin.render(site:, target_path:) }
|
site.plugins.each { |plugin| plugin.render(site:, target_path:) }
|
||||||
|
|
||||||
copy_static_files(source_path, target_path)
|
copy_static_files(source_path, target_path)
|
||||||
|
|
@ -99,5 +101,23 @@ module Pressa
|
||||||
basename = File.basename(source_file)
|
basename = File.basename(source_file)
|
||||||
basename.start_with?(".")
|
basename.start_with?(".")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def site_with_copyright_start_year(base_site)
|
||||||
|
start_year = find_copyright_start_year(base_site)
|
||||||
|
Site.new(**base_site.to_h.merge(copyright_start_year: start_year))
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_copyright_start_year(base_site)
|
||||||
|
years = base_site.plugins.filter_map do |plugin|
|
||||||
|
next unless plugin.respond_to?(:posts_by_year)
|
||||||
|
|
||||||
|
posts_by_year = plugin.posts_by_year
|
||||||
|
next unless posts_by_year.respond_to?(:earliest_year)
|
||||||
|
|
||||||
|
posts_by_year.earliest_year
|
||||||
|
end
|
||||||
|
|
||||||
|
years.min || Time.now.year
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ require "pressa/views/icons"
|
||||||
module Pressa
|
module Pressa
|
||||||
module Views
|
module Views
|
||||||
class Layout < Phlex::HTML
|
class Layout < Phlex::HTML
|
||||||
START_YEAR = 2006
|
|
||||||
|
|
||||||
attr_reader :site,
|
attr_reader :site,
|
||||||
:page_subtitle,
|
:page_subtitle,
|
||||||
:page_description,
|
:page_description,
|
||||||
|
|
@ -34,10 +32,6 @@ module Pressa
|
||||||
@content = content
|
@content = content
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_output?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def view_template
|
def view_template
|
||||||
doctype
|
doctype
|
||||||
|
|
||||||
|
|
@ -172,7 +166,7 @@ module Pressa
|
||||||
|
|
||||||
def render_footer
|
def render_footer
|
||||||
footer do
|
footer do
|
||||||
plain "© #{START_YEAR} - #{Time.now.year} "
|
plain "© #{footer_years} "
|
||||||
a(href: site.url_for("/about")) { site.author }
|
a(href: site.url_for("/about")) { site.author }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -201,6 +195,14 @@ module Pressa
|
||||||
normalized = path.start_with?("/") ? path : "/#{path}"
|
normalized = path.start_with?("/") ? path : "/#{path}"
|
||||||
site.url_for(normalized)
|
site.url_for(normalized)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def footer_years
|
||||||
|
current_year = Time.now.year
|
||||||
|
start_year = site.copyright_start_year || current_year
|
||||||
|
return current_year.to_s if start_year >= current_year
|
||||||
|
|
||||||
|
"#{start_year} - #{current_year}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ description = "Sami Samhuri's blog about programming, mainly about iOS and Ruby
|
||||||
url = "https://samhuri.net"
|
url = "https://samhuri.net"
|
||||||
image_url = "/images/me.jpg"
|
image_url = "/images/me.jpg"
|
||||||
scripts = []
|
scripts = []
|
||||||
styles = ["css/normalize.css", "css/style.css", "css/syntax.css"]
|
styles = ["/css/normalize.css", "/css/style.css", "/css/syntax.css"]
|
||||||
plugins = ["posts", "projects"]
|
plugins = ["posts", "projects"]
|
||||||
|
|
||||||
[projects_plugin]
|
[projects_plugin]
|
||||||
scripts = [
|
scripts = [
|
||||||
"https://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js",
|
"https://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js",
|
||||||
"js/gitter.js",
|
"/js/gitter.js",
|
||||||
"js/store.js",
|
"/js/store.js",
|
||||||
"js/projects.js"
|
"/js/projects.js"
|
||||||
]
|
]
|
||||||
styles = []
|
styles = []
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@ class Pressa::Config::LoaderTest < Minitest::Test
|
||||||
assert_equal("Sami Samhuri", site.author)
|
assert_equal("Sami Samhuri", site.author)
|
||||||
assert_equal("https://samhuri.net", site.url)
|
assert_equal("https://samhuri.net", site.url)
|
||||||
assert_equal("https://samhuri.net/images/me.jpg", site.image_url)
|
assert_equal("https://samhuri.net/images/me.jpg", site.image_url)
|
||||||
assert_equal(["css/style.css"], site.styles.map(&:href))
|
assert_equal(["/css/style.css"], site.styles.map(&:href))
|
||||||
|
|
||||||
projects_plugin = site.plugins.find { |plugin| plugin.is_a?(Pressa::Projects::Plugin) }
|
projects_plugin = site.plugins.find { |plugin| plugin.is_a?(Pressa::Projects::Plugin) }
|
||||||
refute_nil(projects_plugin)
|
refute_nil(projects_plugin)
|
||||||
assert_equal(["js/projects.js"], projects_plugin.scripts.map(&:src))
|
assert_equal(["/js/projects.js"], projects_plugin.scripts.map(&:src))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -338,6 +338,25 @@ class Pressa::Config::LoaderTest < Minitest::Test
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_build_site_rejects_non_absolute_local_asset_paths
|
||||||
|
Dir.mktmpdir do |dir|
|
||||||
|
File.write(File.join(dir, "site.toml"), <<~TOML)
|
||||||
|
author = "Sami Samhuri"
|
||||||
|
email = "sami@samhuri.net"
|
||||||
|
title = "samhuri.net"
|
||||||
|
description = "blog"
|
||||||
|
url = "https://samhuri.net"
|
||||||
|
scripts = ["js/site.js"]
|
||||||
|
styles = ["css/site.css"]
|
||||||
|
TOML
|
||||||
|
File.write(File.join(dir, "projects.toml"), "")
|
||||||
|
|
||||||
|
loader = Pressa::Config::Loader.new(source_path: dir)
|
||||||
|
error = assert_raises(Pressa::Config::ValidationError) { loader.build_site }
|
||||||
|
assert_match(%r{start with / or use http\(s\) scheme}, error.message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def with_temp_config
|
def with_temp_config
|
||||||
|
|
@ -350,11 +369,11 @@ class Pressa::Config::LoaderTest < Minitest::Test
|
||||||
url = "https://samhuri.net"
|
url = "https://samhuri.net"
|
||||||
image_url = "/images/me.jpg"
|
image_url = "/images/me.jpg"
|
||||||
scripts = []
|
scripts = []
|
||||||
styles = ["css/style.css"]
|
styles = ["/css/style.css"]
|
||||||
plugins = ["posts", "projects"]
|
plugins = ["posts", "projects"]
|
||||||
|
|
||||||
[projects_plugin]
|
[projects_plugin]
|
||||||
scripts = ["js/projects.js"]
|
scripts = ["/js/projects.js"]
|
||||||
styles = []
|
styles = []
|
||||||
TOML
|
TOML
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,13 @@ class Pressa::Posts::ModelsTest < Minitest::Test
|
||||||
assert_equal([11, 10], year_2025.sorted_months.map { |mp| mp.month.number })
|
assert_equal([11, 10], year_2025.sorted_months.map { |mp| mp.month.number })
|
||||||
assert_equal([regular_post, link_post], year_2025.all_posts)
|
assert_equal([regular_post, link_post], year_2025.all_posts)
|
||||||
assert_equal([2025, 2024], posts_by_year.sorted_years)
|
assert_equal([2025, 2024], posts_by_year.sorted_years)
|
||||||
|
assert_equal(2024, posts_by_year.earliest_year)
|
||||||
assert_equal(3, posts_by_year.all_posts.length)
|
assert_equal(3, posts_by_year.all_posts.length)
|
||||||
assert_equal([regular_post], posts_by_year.recent_posts(1))
|
assert_equal([regular_post], posts_by_year.recent_posts(1))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_posts_by_year_earliest_year_is_nil_for_empty_collection
|
||||||
|
posts_by_year = Pressa::Posts::PostsByYear.new(by_year: {})
|
||||||
|
assert_nil(posts_by_year.earliest_year)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,20 @@ class Pressa::SiteGeneratorRenderingTest < Minitest::Test
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class PostsPluginSpy < PluginSpy
|
||||||
|
attr_reader :posts_by_year, :render_site_year
|
||||||
|
|
||||||
|
def initialize(posts_by_year:)
|
||||||
|
super()
|
||||||
|
@posts_by_year = posts_by_year
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(site:, target_path:)
|
||||||
|
@render_site_year = site.copyright_start_year
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class MarkdownRendererSpy
|
class MarkdownRendererSpy
|
||||||
attr_reader :calls
|
attr_reader :calls
|
||||||
|
|
||||||
|
|
@ -51,6 +65,27 @@ class Pressa::SiteGeneratorRenderingTest < Minitest::Test
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_posts_by_year(year:)
|
||||||
|
post = Pressa::Posts::Post.new(
|
||||||
|
slug: "first-post",
|
||||||
|
title: "First Post",
|
||||||
|
author: "Sami Samhuri",
|
||||||
|
date: DateTime.parse("#{year}-02-01T10:00:00-08:00"),
|
||||||
|
formatted_date: "1st February, #{year}",
|
||||||
|
body: "<p>First post</p>",
|
||||||
|
excerpt: "First post...",
|
||||||
|
path: "/posts/#{year}/02/first-post"
|
||||||
|
)
|
||||||
|
|
||||||
|
month_posts = Pressa::Posts::MonthPosts.new(
|
||||||
|
month: Pressa::Posts::Month.new(name: "February", number: 2, padded: "02"),
|
||||||
|
posts: [post]
|
||||||
|
)
|
||||||
|
|
||||||
|
year_posts = Pressa::Posts::YearPosts.new(year:, by_month: {2 => month_posts})
|
||||||
|
Pressa::Posts::PostsByYear.new(by_year: {year => year_posts})
|
||||||
|
end
|
||||||
|
|
||||||
def test_generate_runs_plugins_copies_static_files_and_renders_supported_files
|
def test_generate_runs_plugins_copies_static_files_and_renders_supported_files
|
||||||
Dir.mktmpdir do |root|
|
Dir.mktmpdir do |root|
|
||||||
source_path = File.join(root, "source")
|
source_path = File.join(root, "source")
|
||||||
|
|
@ -110,4 +145,20 @@ class Pressa::SiteGeneratorRenderingTest < Minitest::Test
|
||||||
assert_empty(renderer.calls)
|
assert_empty(renderer.calls)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_generate_sets_copyright_start_year_from_earliest_post_year
|
||||||
|
Dir.mktmpdir do |root|
|
||||||
|
source_path = File.join(root, "source")
|
||||||
|
target_path = File.join(root, "target")
|
||||||
|
FileUtils.mkdir_p(source_path)
|
||||||
|
|
||||||
|
plugin = PostsPluginSpy.new(posts_by_year: build_posts_by_year(year: 2006))
|
||||||
|
renderer = MarkdownRendererSpy.new
|
||||||
|
site = build_site(plugin:, renderer:)
|
||||||
|
|
||||||
|
Pressa::SiteGenerator.new(site:).generate(source_path:, target_path:)
|
||||||
|
|
||||||
|
assert_equal(2006, plugin.render_site_year)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,17 @@ class Pressa::Views::LayoutTest < Minitest::Test
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def site_with_copyright_start_year(year)
|
||||||
|
Pressa::Site.new(
|
||||||
|
author: "Sami Samhuri",
|
||||||
|
email: "sami@samhuri.net",
|
||||||
|
title: "samhuri.net",
|
||||||
|
description: "blog",
|
||||||
|
url: "https://samhuri.net",
|
||||||
|
copyright_start_year: year
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def test_rendering_child_components_as_html_instead_of_escaped_text
|
def test_rendering_child_components_as_html_instead_of_escaped_text
|
||||||
html = Pressa::Views::Layout.new(
|
html = Pressa::Views::Layout.new(
|
||||||
site:,
|
site:,
|
||||||
|
|
@ -64,13 +75,25 @@ class Pressa::Views::LayoutTest < Minitest::Test
|
||||||
assert_includes(html, %(<link rel="stylesheet" type="text/css" href="https://cdn.example.com/site.css">))
|
assert_includes(html, %(<link rel="stylesheet" type="text/css" href="https://cdn.example.com/site.css">))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_format_output_is_enabled
|
def test_footer_renders_year_range_using_copyright_start_year
|
||||||
layout = Pressa::Views::Layout.new(
|
html = Pressa::Views::Layout.new(
|
||||||
site:,
|
site: site_with_copyright_start_year(2006),
|
||||||
canonical_url: "https://samhuri.net/posts/",
|
canonical_url: "https://samhuri.net/posts/",
|
||||||
content: content_view
|
content: content_view
|
||||||
)
|
).call
|
||||||
|
|
||||||
assert(layout.format_output?)
|
assert_includes(html, "<footer>© 2006 - #{Time.now.year} <a href=\"https://samhuri.net/about\">Sami Samhuri</a></footer>")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_footer_renders_single_year_when_start_year_matches_current_year
|
||||||
|
current_year = Time.now.year
|
||||||
|
html = Pressa::Views::Layout.new(
|
||||||
|
site: site_with_copyright_start_year(current_year),
|
||||||
|
canonical_url: "https://samhuri.net/posts/",
|
||||||
|
content: content_view
|
||||||
|
).call
|
||||||
|
|
||||||
|
assert_includes(html, "<footer>© #{current_year} <a href=\"https://samhuri.net/about\">Sami Samhuri</a></footer>")
|
||||||
|
refute_includes(html, "<footer>© #{current_year} - #{current_year} ")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue