From d5834c9c04d631d0d2e9e8edd3f4e1cde0bf7800 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 23:35:39 +1200 Subject: [PATCH 1/9] Use protocol HTTP middleware. --- bake/utopia/project.rb | 26 +++++++++--------------- gems.rb | 3 +-- lib/utopia/project.rb | 17 +++++++++------- lib/utopia/project/guides.rb | 5 ++--- pages/_page.xnode | 2 +- releases.md | 1 + template/config.ru | 7 ------- template/config/application.rb | 12 +++++++++++ template/config/environment.rb | 8 ++++++++ template/config/serve.rb | 8 ++++++++ test/utopia/project/serve.rb | 37 ++++++++++------------------------ utopia-project.gemspec | 1 - 12 files changed, 63 insertions(+), 64 deletions(-) delete mode 100644 template/config.ru create mode 100644 template/config/application.rb create mode 100644 template/config/environment.rb create mode 100644 template/config/serve.rb diff --git a/bake/utopia/project.rb b/bake/utopia/project.rb index 27df3bc..efa121d 100644 --- a/bake/utopia/project.rb +++ b/bake/utopia/project.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2025, by Samuel Williams. +# Copyright, 2020-2026, by Samuel Williams. def initialize(context) super @@ -35,7 +35,7 @@ def update # @parameter port [Integer] The port to bind to. # @parameter bind [String] The URL to bind to, e.g. `http://localhost:80`. def serve(port: nil, bind: nil) - config_path = File.expand_path("../../template/config.ru", __dir__) + config_path = File.expand_path("../../template/config/serve.rb", __dir__) preload_path = File.expand_path("../../template/preload.rb", __dir__) options = [] @@ -55,22 +55,15 @@ def serve(port: nil, bind: nil) # @parameter output_path [String] The output path for the static site. # @parameter force [Boolean] Remove the output directory before generating the static content. def static(output_path: "docs", force: true) - require "rackula/command" - - config_path = File.expand_path("../../template/config.ru", __dir__) + application_path = File.expand_path("../../template/config/application.rb", __dir__) public_path = File.expand_path("../../public", __dir__) - arguments = [] - - if force - arguments << "--force" - end - - Rackula::Command::Top["generate", *arguments, - "--config", config_path, - "--public", public_path, - "--output-path", output_path - ].call + context["utopia:static:generate"].call( + output_path: output_path, + application_path: application_path, + public_path: public_path, + force: force, + ) FileUtils.touch File.expand_path(".nojekyll", output_path) end @@ -94,4 +87,3 @@ def description(root: context.root) end end end - diff --git a/gems.rb b/gems.rb index 4049ae0..6295b9f 100644 --- a/gems.rb +++ b/gems.rb @@ -18,6 +18,7 @@ group :test do gem "sus" + gem "sus-fixtures-protocol-http", "~> 0.1" gem "covered" gem "decode" @@ -25,7 +26,5 @@ gem "rubocop-md" gem "rubocop-socketry" - gem "rack-test" - gem "bake-test" end diff --git a/lib/utopia/project.rb b/lib/utopia/project.rb index 3d4175b..499d81b 100644 --- a/lib/utopia/project.rb +++ b/lib/utopia/project.rb @@ -1,13 +1,19 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2025, by Samuel Williams. +# Copyright, 2020-2026, by Samuel Williams. require "utopia/project/version" require "variant" +require "utopia/application" +require "utopia/content" +require "utopia/controller" +require "utopia/exceptions" require "utopia/localization" +require "utopia/redirection" +require "utopia/static" require_relative "project/base" require_relative "project/import_map" @@ -23,9 +29,9 @@ module Project # The root directory for static assets. PUBLIC_ROOT = File.expand_path("public", SITE_ROOT) - # Appends a project application to the rack builder. + # Appends a project application to the protocol HTTP middleware builder. # - # @parameter builder [Rack::Builder] + # @parameter builder [Protocol::HTTP::Middleware::Builder] # @parameter root [String] The file-system root path of the project/gem. # @parameter locales [Array(String)] an array of locales to support, e.g. `['en', 'ja']`. def self.call(builder, root = Dir.pwd, locales: nil) @@ -33,9 +39,6 @@ def self.call(builder, root = Dir.pwd, locales: nil) # Handle exceptions in production with a error page and send an email notification: builder.use Utopia::Exceptions::Handler builder.use Utopia::Exceptions::Mailer - else - # We want to propate exceptions up when running tests: - builder.use Rack::ShowExceptions unless UTOPIA.testing? end # We serve static files from the project root: @@ -67,7 +70,7 @@ def self.call(builder, root = Dir.pwd, locales: nil) # 'gallery' => Utopia::Gallery::Tags.new } - builder.run lambda {|env| [404, {}, []]} + builder.run Utopia::Response::NotFound end end end diff --git a/lib/utopia/project/guides.rb b/lib/utopia/project/guides.rb index 91ee54c..73a04b1 100644 --- a/lib/utopia/project/guides.rb +++ b/lib/utopia/project/guides.rb @@ -45,14 +45,14 @@ def to_a # @parameter name [String] The guide name. # @returns [Guide | Nil] def [](name) - to_a.find { |guide| guide.name == name } + to_a.find{|guide| guide.name == name} end # Get the related guides (previous and next) for the given guide. # @parameter guide [Guide] The current guide. # @returns [Array(Guide | Nil, Guide | Nil)] A two-element array containing the previous and next guides. def related(guide) - index = to_a.index { |g| g.name == guide.name } + index = to_a.index{|g| g.name == guide.name} return [nil, nil] unless index previous_guide = index > 0 ? to_a[index - 1] : nil @@ -63,4 +63,3 @@ def related(guide) end end end - diff --git a/pages/_page.xnode b/pages/_page.xnode index 44c9002..55f1d29 100644 --- a/pages/_page.xnode +++ b/pages/_page.xnode @@ -15,7 +15,7 @@ - #{Utopia::Project::IMPORT_MAP.relative_to(request.env["REQUEST_PATH"]).to_html} + #{Utopia::Project::IMPORT_MAP.relative_to(request.request_path).to_html} diff --git a/releases.md b/releases.md index 204702f..2089837 100644 --- a/releases.md +++ b/releases.md @@ -3,6 +3,7 @@ ## Unreleased - Don't render empty signature block when there are only examples. + - Use protocol HTTP middleware for serving and testing project documentation. ## v0.40.0 diff --git a/template/config.ru b/template/config.ru deleted file mode 100644 index 8f2be7f..0000000 --- a/template/config.ru +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require "utopia/setup" -UTOPIA ||= Utopia.setup(Dir.pwd) - -require "utopia/project" -Utopia::Project.call(self) diff --git a/template/config/application.rb b/template/config/application.rb new file mode 100644 index 0000000..02b0e09 --- /dev/null +++ b/template/config/application.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +require_relative "environment" + +require "utopia/project" + +Application = Utopia::Application.build do |builder| + Utopia::Project.call(builder) +end diff --git a/template/config/environment.rb b/template/config/environment.rb new file mode 100644 index 0000000..d3dda4f --- /dev/null +++ b/template/config/environment.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +require "utopia/setup" + +UTOPIA ||= Utopia.setup(File.expand_path("..", __dir__)) diff --git a/template/config/serve.rb b/template/config/serve.rb new file mode 100644 index 0000000..8ea867b --- /dev/null +++ b/template/config/serve.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2026, by Samuel Williams. + +require "utopia/application" + +run Utopia::Application.load(File.expand_path("application.rb", __dir__)) diff --git a/test/utopia/project/serve.rb b/test/utopia/project/serve.rb index 4c873a7..bb95797 100644 --- a/test/utopia/project/serve.rb +++ b/test/utopia/project/serve.rb @@ -1,52 +1,37 @@ # frozen_string_literal: true # Released under the MIT License. -# Copyright, 2020-2025, by Samuel Williams. +# Copyright, 2020-2026, by Samuel Williams. require "utopia/project" -require "rack/builder" -require "rack/test" +require "protocol/http/middleware/builder" +require "sus/fixtures/protocol/http/middleware_context" describe Utopia::Project do - include Rack::Test::Methods + include Sus::Fixtures::Protocol::HTTP::MiddlewareContext let(:template_root) {File.expand_path("../../../template", __dir__)} - let(:rackup_path) {File.expand_path("config.ru", template_root)} - let(:rackup_directory) {File.dirname(rackup_path)} + let(:configuration_path) {File.expand_path("config/serve.rb", template_root)} - let(:app) do - inner_app = Rack::Builder.parse_file(rackup_path) - - # Middleware to set REQUEST_PATH from PATH_INFO - lambda do |env| - env["REQUEST_PATH"] ||= env["PATH_INFO"] - inner_app.call(env) - end + let(:middleware) do + Protocol::HTTP::Middleware.load(configuration_path) end it "has root page" do - get "/index" - - expect(last_response.body).to be(:include?, "Project") + expect(client.get("/index").read).to be(:include?, "Project") end it "has guide page" do - get "/guides/getting-started/index" - - expect(last_response.body).to be(:include?, "Getting Started") + expect(client.get("/guides/getting-started/index").read).to be(:include?, "Getting Started") end it "has source code index" do - get "/source/index" - - expect(last_response.body).to be(:include?, "module Utopia") + expect(client.get("/source/index").read).to be(:include?, "module Utopia") end it "has source code file" do - get "/source/Utopia/Project/Base/index" - - expect(last_response.body).to be(:include?, "def initialize") + expect(client.get("/source/Utopia/Project/Base/index").read).to be(:include?, "def initialize") end end diff --git a/utopia-project.gemspec b/utopia-project.gemspec index ebcdfc8..3e95740 100644 --- a/utopia-project.gemspec +++ b/utopia-project.gemspec @@ -28,7 +28,6 @@ Gem::Specification.new do |spec| spec.add_dependency "decode", "~> 0.26" spec.add_dependency "falcon" spec.add_dependency "markly", "~> 0.15" - spec.add_dependency "rackula", "~> 1.3" spec.add_dependency "thread-local" spec.add_dependency "utopia", "~> 2.32" end From 99207e10e74fd758bb687ee336ffbabeed9a9e0c Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 9 Aug 2026 23:37:37 +1200 Subject: [PATCH 2/9] Require Ruby 3.3. --- .github/workflows/test.yaml | 1 - utopia-project.gemspec | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 69d7c6b..931339f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -18,7 +18,6 @@ jobs: - macos ruby: - - "3.2" - "3.3" - "3.4" diff --git a/utopia-project.gemspec b/utopia-project.gemspec index 3e95740..9fc9620 100644 --- a/utopia-project.gemspec +++ b/utopia-project.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| spec.files = Dir.glob(["{bake,context,lib,pages,public,template}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__) - spec.required_ruby_version = ">= 3.2" + spec.required_ruby_version = ">= 3.3" spec.add_dependency "decode", "~> 0.26" spec.add_dependency "falcon" From b20dcf5b1107d3108fb40af3e4830ab07773ab89 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 10 Aug 2026 13:53:52 +1200 Subject: [PATCH 3/9] Use unified redirection middleware. --- lib/utopia/project.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/utopia/project.rb b/lib/utopia/project.rb index 499d81b..8f87885 100644 --- a/lib/utopia/project.rb +++ b/lib/utopia/project.rb @@ -46,15 +46,11 @@ def self.call(builder, root = Dir.pwd, locales: nil) builder.use Utopia::Static, root: PUBLIC_ROOT - builder.use Utopia::Redirection::Rewrite, { - "/" => "/index" - } - - builder.use Utopia::Redirection::DirectoryIndex - - builder.use Utopia::Redirection::Errors, { - 404 => "/errors/file-not-found" - } + builder.use Utopia::Redirection do |redirects| + redirects.rewrite "/" => "/index" + redirects.directory_index + redirects.error 404, "/errors/file-not-found" + end if locales builder.use Utopia::Localization, From 48a2e36076852f731829aed4327dfc6674dfa4ac Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 10 Aug 2026 14:21:12 +1200 Subject: [PATCH 4/9] Separate internal error redirections. --- lib/utopia/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utopia/project.rb b/lib/utopia/project.rb index 8f87885..b0dbf23 100644 --- a/lib/utopia/project.rb +++ b/lib/utopia/project.rb @@ -49,8 +49,8 @@ def self.call(builder, root = Dir.pwd, locales: nil) builder.use Utopia::Redirection do |redirects| redirects.rewrite "/" => "/index" redirects.directory_index - redirects.error 404, "/errors/file-not-found" end + builder.use Utopia::Redirection::Errors, 404 => "/errors/file-not-found" if locales builder.use Utopia::Localization, From a83761f48ec28def700d48aadf7e42da689e2774 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 14 Aug 2026 21:18:01 +1200 Subject: [PATCH 5/9] Use separate redirection middleware. --- lib/utopia/project.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/utopia/project.rb b/lib/utopia/project.rb index b0dbf23..35c2792 100644 --- a/lib/utopia/project.rb +++ b/lib/utopia/project.rb @@ -46,10 +46,9 @@ def self.call(builder, root = Dir.pwd, locales: nil) builder.use Utopia::Static, root: PUBLIC_ROOT - builder.use Utopia::Redirection do |redirects| - redirects.rewrite "/" => "/index" - redirects.directory_index - end + builder.use Utopia::Redirection::Rewrite, "/" => "/index" + builder.use Utopia::Redirection::DirectoryIndex + builder.use Utopia::Redirection::Errors, 404 => "/errors/file-not-found" if locales From 4f374455aee5ab0c7f24c3377894431b45bbd2c3 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 14 Aug 2026 21:19:52 +1200 Subject: [PATCH 6/9] Pass redirection maps as arguments. --- lib/utopia/project.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/utopia/project.rb b/lib/utopia/project.rb index 35c2792..c0afe43 100644 --- a/lib/utopia/project.rb +++ b/lib/utopia/project.rb @@ -46,10 +46,14 @@ def self.call(builder, root = Dir.pwd, locales: nil) builder.use Utopia::Static, root: PUBLIC_ROOT - builder.use Utopia::Redirection::Rewrite, "/" => "/index" + builder.use Utopia::Redirection::Rewrite, { + "/" => "/index" + } builder.use Utopia::Redirection::DirectoryIndex - builder.use Utopia::Redirection::Errors, 404 => "/errors/file-not-found" + builder.use Utopia::Redirection::Errors, { + 404 => "/errors/file-not-found" + } if locales builder.use Utopia::Localization, From 4e03219ee636dc7a2d61495fcad343d45727c881 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 14 Aug 2026 21:20:16 +1200 Subject: [PATCH 7/9] Handle optional localization preferences. --- pages/_header.xnode | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/_header.xnode b/pages/_header.xnode index e11ff7b..7a16750 100644 --- a/pages/_header.xnode +++ b/pages/_header.xnode @@ -6,7 +6,7 @@ path = path + "index" end - if link = links(path.dirname, name: path.last, locale: localization.current_locale, indices: true).first + if link = links(path.dirname, name: path.last, locale: localization&.locale, indices: true).first if replace = link[:replace]&.to_sym and base = controller[:base] ?> › #{link.to_href(content: base.public_send(replace))}#{guide.title} • (#{index.zero? ? '' : ' '}#{locale}) \ No newline at end of file +?> From 737e5278b074739a95e1ad19609a28e5f5ebbacd Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 14 Aug 2026 21:39:33 +1200 Subject: [PATCH 8/9] Preserve the builder's default application. --- lib/utopia/project.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/utopia/project.rb b/lib/utopia/project.rb index c0afe43..e031021 100644 --- a/lib/utopia/project.rb +++ b/lib/utopia/project.rb @@ -69,7 +69,6 @@ def self.call(builder, root = Dir.pwd, locales: nil) # 'gallery' => Utopia::Gallery::Tags.new } - builder.run Utopia::Response::NotFound end end end From 5e2fd2aa60fb49383fccde131fb58be399ac1d65 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 14 Aug 2026 21:53:22 +1200 Subject: [PATCH 9/9] Depend on Utopia 3. --- utopia-project.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utopia-project.gemspec b/utopia-project.gemspec index 9fc9620..80be5d8 100644 --- a/utopia-project.gemspec +++ b/utopia-project.gemspec @@ -29,5 +29,5 @@ Gem::Specification.new do |spec| spec.add_dependency "falcon" spec.add_dependency "markly", "~> 0.15" spec.add_dependency "thread-local" - spec.add_dependency "utopia", "~> 2.32" + spec.add_dependency "utopia", "~> 3.0" end