From 1a0be7dcbd23537a98fd694dbc156a53599b5736 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Fri, 5 Jun 2026 14:02:39 +0200 Subject: [PATCH 1/4] refactor: configure gem defaults on boot and remove manual config merging (closes html2rss/html2rss#210) --- Gemfile | 4 +-- Gemfile.lock | 46 ++++++++++++++++++---------------- app/web/boot/setup.rb | 10 ++++++++ app/web/config/local_config.rb | 5 ---- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/Gemfile b/Gemfile index 5b972c55..ca51a2aa 100644 --- a/Gemfile +++ b/Gemfile @@ -4,12 +4,12 @@ source 'https://rubygems.org' git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } -gem 'html2rss', '~> 0.19' +# gem 'html2rss', '~> 0.19' # gem 'html2rss', github: 'html2rss/html2rss', branch: 'master' gem 'html2rss-configs', github: 'html2rss/html2rss-configs' # Use these instead of the two above (uncomment them) when developing locally: -# gem 'html2rss', path: '../html2rss' +gem 'html2rss', path: '../html2rss' # gem 'html2rss-configs', path: '../html2rss-configs' gem 'concurrent-ruby' diff --git a/Gemfile.lock b/Gemfile.lock index 9a1eb204..3091d1a3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,6 +5,29 @@ GIT html2rss-configs (0.2.0) html2rss +PATH + remote: ../html2rss + specs: + html2rss (0.19.1) + addressable (~> 2.7) + brotli + dry-validation + faraday (> 2.0.1, < 3.0) + faraday-follow_redirects + faraday-gzip (~> 3) + kramdown + mime-types (> 3.0) + nokogiri (>= 1.10, < 2.0) + parallel + puppeteer-ruby + regexp_parser + reverse_markdown (~> 3.0) + rss + sanitize + thor + tzinfo + zeitwerk + GEM remote: https://rubygems.org/ specs: @@ -136,25 +159,6 @@ GEM fiber-storage fiber-storage (1.0.1) hashdiff (1.2.1) - html2rss (0.19.1) - addressable (~> 2.7) - brotli - dry-validation - faraday (> 2.0.1, < 3.0) - faraday-follow_redirects - faraday-gzip (~> 3) - kramdown - mime-types (> 3.0) - nokogiri (>= 1.10, < 2.0) - parallel - puppeteer-ruby - regexp_parser - reverse_markdown (~> 3.0) - rss - sanitize - thor - tzinfo - zeitwerk i18n (1.14.8) concurrent-ruby (~> 1.0) io-console (0.8.2) @@ -371,7 +375,7 @@ PLATFORMS DEPENDENCIES climate_control concurrent-ruby - html2rss (~> 0.19) + html2rss! html2rss-configs! irb parallel @@ -439,7 +443,7 @@ CHECKSUMS fiber-local (1.1.0) sha256=c885f94f210fb9b05737de65d511136ea602e00c5105953748aa0f8793489f06 fiber-storage (1.0.1) sha256=f48e5b6d8b0be96dac486332b55cee82240057065dc761c1ea692b2e719240e1 hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1 - html2rss (0.19.1) sha256=0af89c99421eb45ad386885cd4e9d946fbb1a887e757922c6f6bc5d825a32ac2 + html2rss (0.19.1) html2rss-configs (0.2.0) i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc diff --git a/app/web/boot/setup.rb b/app/web/boot/setup.rb index cf807ecf..f06718ac 100644 --- a/app/web/boot/setup.rb +++ b/app/web/boot/setup.rb @@ -23,11 +23,21 @@ def call! configure_sentry! configure_request_service! configure_runtime_logging! + configure_gem_defaults! log_startup! end private + # @return [void] + def configure_gem_defaults! + global_config = LocalConfig.global + Html2rss.configure do |config| + config.headers = global_config[:headers] if global_config[:headers] + config.stylesheets = global_config[:stylesheets] if global_config[:stylesheets] + end + end + # @return [void] def validate_environment! EnvironmentValidator.validate_environment! diff --git a/app/web/config/local_config.rb b/app/web/config/local_config.rb index 038f83a2..d663469a 100644 --- a/app/web/config/local_config.rb +++ b/app/web/config/local_config.rb @@ -125,11 +125,6 @@ def embedded_feed_config(normalized_name) # @param config [Hash{Symbol=>Object}] # @return [Hash{Symbol=>Object}] def apply_global_defaults(config) - global_config = global - - config[:stylesheets] ||= deep_dup(global_config[:stylesheets]) if global_config[:stylesheets] - config[:headers] ||= deep_dup(global_config[:headers]) if global_config[:headers] - config end From 3d8f3aca95ea17a3f5eac24913e2e77c65a05ff5 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sat, 6 Jun 2026 10:24:41 +0200 Subject: [PATCH 2/4] fix: inject stylesheets into synthetic error and warning feeds This refactors XmlBuilder to use RSS::Maker internally instead of manual Nokogiri manipulation, which allows us to use the html2rss gem's native stylesheet injection support (Html2rss::RssBuilder::Stylesheet) and automatically pick up globally configured stylesheets. --- app/web/rendering/xml_builder.rb | 81 ++++++++++++--------------- spec/html2rss/web/xml_builder_spec.rb | 4 ++ 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/app/web/rendering/xml_builder.rb b/app/web/rendering/xml_builder.rb index 057e997b..d8986171 100644 --- a/app/web/rendering/xml_builder.rb +++ b/app/web/rendering/xml_builder.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true -require 'nokogiri' +require 'rss' require 'time' + module Html2rss module Web ## @@ -18,17 +19,11 @@ class << self # @param timestamp [Time, nil] # @return [String] serialized RSS XML document. def build_rss_feed(title:, description:, link: nil, items: [], timestamp: nil) - current_time = timestamp || Time.now - formatted_now = format_pub_date(current_time) - - Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| - xml.rss(version: '2.0') do - xml.channel do - build_channel(xml, title:, description:, link:, now: formatted_now) - build_items(xml, items, default_pub_date: formatted_now) - end - end - end.doc.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML) + RSS::Maker.make('2.0') do |maker| + apply_stylesheets(maker) + build_channel(maker.channel, title:, description:, link:, timestamp:) + build_items(maker, items, default_timestamp: timestamp) + end.to_s end # @param message [String] @@ -61,6 +56,16 @@ def build_empty_feed_warning(url:, strategy:, site_title: nil) private + # @param maker [RSS::Maker::RSS20] + # @return [void] + def apply_stylesheets(maker) + # Use the gem's internal stylesheet support. + stylesheets = Html2rss.configuration.stylesheets.map do |s| + Html2rss::RssBuilder::Stylesheet.new(**s) + end + Html2rss::RssBuilder::Stylesheet.add(maker, stylesheets) + end + # @param title [String] # @param description [String] # @param item [Hash{Symbol=>Object}] @@ -81,57 +86,43 @@ def build_single_item_feed(title:, description:, item:, link: nil) # @param timestamp [Time] # @return [Hash{Symbol=>Object}] normalized item with required RSS fields. def feed_item(item, timestamp:) - feed_item = { + { title: item[:title], description: item[:description], + link: item[:link], pubDate: timestamp } - feed_item[:link] = item[:link] if item[:link] - feed_item end - # @param xml [Nokogiri::XML::Builder] + # @param channel [RSS::Maker::RSS20::Channel] # @param title [String] # @param description [String] # @param link [String, nil] - # @param now [String] + # @param timestamp [Time, nil] # @return [void] - def build_channel(xml, title:, description:, link:, now:) - xml.title(title.to_s) - xml.description(description.to_s) - xml.link(link.to_s) if link - xml.lastBuildDate(now) - xml.pubDate(now) + def build_channel(channel, title:, description:, link:, timestamp:) + now = timestamp || Time.now + channel.title = title.to_s + channel.description = description.to_s + channel.link = link.to_s + channel.lastBuildDate = now + channel.pubDate = now end - # @param xml [Nokogiri::XML::Builder] + # @param maker [RSS::Maker::RSS20] # @param items [ArrayObject}>] - # @param default_pub_date [String] + # @param default_timestamp [Time, nil] # @return [void] - def build_items(xml, items, default_pub_date:) + def build_items(maker, items, default_timestamp:) items.each do |item| - xml.item do - append_text_node(xml, :title, item[:title]) - append_text_node(xml, :description, item[:description]) - append_text_node(xml, :link, item[:link]) - xml.pubDate(format_pub_date(item[:pubDate] || default_pub_date)) + maker.items.new_item do |i| + i.title = item[:title].to_s + i.description = item[:description].to_s + i.link = item[:link].to_s + i.pubDate = item[:pubDate] || default_timestamp || Time.now end end end - - # @param xml [Nokogiri::XML::Builder] - # @param node_name [Symbol] - # @param value [Object] - # @return [void] - def append_text_node(xml, node_name, value) - xml.public_send(node_name, value.to_s) if value - end - - # @param pub_date [Time, String] - # @return [String] RFC2822 date string for RSS output. - def format_pub_date(pub_date) - pub_date.is_a?(Time) ? pub_date.rfc2822 : pub_date.to_s - end end end end diff --git a/spec/html2rss/web/xml_builder_spec.rb b/spec/html2rss/web/xml_builder_spec.rb index a06d9047..ffd4f921 100644 --- a/spec/html2rss/web/xml_builder_spec.rb +++ b/spec/html2rss/web/xml_builder_spec.rb @@ -29,5 +29,9 @@ it 'does not mention hidden strategy controls in item text' do expect(xml_doc.at_xpath('//item/description').text).not_to include('browserless strategy') end + + it 'includes the default stylesheet processing instruction' do + expect(xml_doc.to_s).to include('') + end end end From 6c270028a154ce02a1e90acd3cf02535709a75d7 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sat, 6 Jun 2026 13:35:13 +0200 Subject: [PATCH 3/4] refactor: remove stray method --- app/web/config/local_config.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/web/config/local_config.rb b/app/web/config/local_config.rb index d663469a..7518eda0 100644 --- a/app/web/config/local_config.rb +++ b/app/web/config/local_config.rb @@ -42,7 +42,7 @@ def find(name) config_hash = local_feed_config(normalized_name) || embedded_feed_config(normalized_name) raise NotFound, "Did not find local feed config at '#{normalized_name}'" unless config_hash - apply_global_defaults(config_hash) + config_hash end ## @@ -120,14 +120,6 @@ def embedded_feed_config(normalized_name) nil end - # Applies global defaults only when feed-level keys are absent. - # - # @param config [Hash{Symbol=>Object}] - # @return [Hash{Symbol=>Object}] - def apply_global_defaults(config) - config - end - # @param name [String, Symbol, #to_s] # @return [String] path without feed extension for feed lookup. def normalize_name(name) From 593bde1ce7a032d907ffd9f0170bf6d6011ed47c Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sat, 6 Jun 2026 14:43:22 +0200 Subject: [PATCH 4/4] chore: switch to released gem --- Gemfile | 4 ++-- Gemfile.lock | 46 +++++++++++++++++++++------------------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Gemfile b/Gemfile index ca51a2aa..78224b06 100644 --- a/Gemfile +++ b/Gemfile @@ -4,12 +4,12 @@ source 'https://rubygems.org' git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } -# gem 'html2rss', '~> 0.19' +gem 'html2rss', '~> 0.20' # gem 'html2rss', github: 'html2rss/html2rss', branch: 'master' gem 'html2rss-configs', github: 'html2rss/html2rss-configs' # Use these instead of the two above (uncomment them) when developing locally: -gem 'html2rss', path: '../html2rss' +# gem 'html2rss', path: '../html2rss' # gem 'html2rss-configs', path: '../html2rss-configs' gem 'concurrent-ruby' diff --git a/Gemfile.lock b/Gemfile.lock index 3091d1a3..1a2acf88 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,29 +5,6 @@ GIT html2rss-configs (0.2.0) html2rss -PATH - remote: ../html2rss - specs: - html2rss (0.19.1) - addressable (~> 2.7) - brotli - dry-validation - faraday (> 2.0.1, < 3.0) - faraday-follow_redirects - faraday-gzip (~> 3) - kramdown - mime-types (> 3.0) - nokogiri (>= 1.10, < 2.0) - parallel - puppeteer-ruby - regexp_parser - reverse_markdown (~> 3.0) - rss - sanitize - thor - tzinfo - zeitwerk - GEM remote: https://rubygems.org/ specs: @@ -159,6 +136,25 @@ GEM fiber-storage fiber-storage (1.0.1) hashdiff (1.2.1) + html2rss (0.20.0) + addressable (~> 2.7) + brotli + dry-validation + faraday (> 2.0.1, < 3.0) + faraday-follow_redirects + faraday-gzip (~> 3) + kramdown + mime-types (> 3.0) + nokogiri (>= 1.10, < 2.0) + parallel + puppeteer-ruby + regexp_parser + reverse_markdown (~> 3.0) + rss + sanitize + thor + tzinfo + zeitwerk i18n (1.14.8) concurrent-ruby (~> 1.0) io-console (0.8.2) @@ -375,7 +371,7 @@ PLATFORMS DEPENDENCIES climate_control concurrent-ruby - html2rss! + html2rss (~> 0.20) html2rss-configs! irb parallel @@ -443,7 +439,7 @@ CHECKSUMS fiber-local (1.1.0) sha256=c885f94f210fb9b05737de65d511136ea602e00c5105953748aa0f8793489f06 fiber-storage (1.0.1) sha256=f48e5b6d8b0be96dac486332b55cee82240057065dc761c1ea692b2e719240e1 hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1 - html2rss (0.19.1) + html2rss (0.20.0) sha256=88454f78ce125234f2934b969f18c4d2e3de1884d773a355c62e0b99ad2b7588 html2rss-configs (0.2.0) i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc