utopia-project 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9857e236b8690b3e3c8ae113309fdf2af695222643cc998bcac8862c09232465
4
- data.tar.gz: b8ccb22331616e6f1ff287c067bf22ace7148c7627a6be9209be9c86f928a87d
3
+ metadata.gz: 2b939e146930849d4cf8f02d043129ed9ac4d4e30cef2c672936deeba8ebf834
4
+ data.tar.gz: e481fe629ee2812bf5138577ee0e55b8a1025960bb0f050129b40a1bae3a3565
5
5
  SHA512:
6
- metadata.gz: c9e2c1640bb841704474c7bf6e3bf1a077fa9eae05267fb45874ea5b07c2beefcdfd92011d152e55db42fff3a1b037611355ddcd94e9f43b6425049f765e4741
7
- data.tar.gz: 21796bcb1f5844dce244d0421a92d86ee3e25a0bf8daa05e12e6c41103fbc0e64f6703f8de664e1ce1110509876f0073850e55980c95847a8c492959ec14da3c
6
+ metadata.gz: d403f98935fc293324f8d0b1001535cfe38d7ce04aea3040257b651d89fd2bcacad593d0749fe2c250bd8b61ccbd30dd00db40f981164865d74ae26831841f51
7
+ data.tar.gz: 28ecbb2dae306f4a0568e7b325a5f47f2d487de5854190db1d82e8bb489effcd8d77d62ef3cf94ece65c21a863a2c7b41158fc5f4141242497908752d8910c20
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2025, by Samuel Williams.
4
+ # Copyright, 2020-2026, by Samuel Williams.
5
5
 
6
6
  def initialize(context)
7
7
  super
@@ -35,7 +35,7 @@ end
35
35
  # @parameter port [Integer] The port to bind to.
36
36
  # @parameter bind [String] The URL to bind to, e.g. `http://localhost:80`.
37
37
  def serve(port: nil, bind: nil)
38
- config_path = File.expand_path("../../template/config.ru", __dir__)
38
+ config_path = File.expand_path("../../template/config/serve.rb", __dir__)
39
39
  preload_path = File.expand_path("../../template/preload.rb", __dir__)
40
40
 
41
41
  options = []
@@ -55,22 +55,15 @@ end
55
55
  # @parameter output_path [String] The output path for the static site.
56
56
  # @parameter force [Boolean] Remove the output directory before generating the static content.
57
57
  def static(output_path: "docs", force: true)
58
- require "rackula/command"
59
-
60
- config_path = File.expand_path("../../template/config.ru", __dir__)
58
+ application_path = File.expand_path("../../template/config/application.rb", __dir__)
61
59
  public_path = File.expand_path("../../public", __dir__)
62
60
 
63
- arguments = []
64
-
65
- if force
66
- arguments << "--force"
67
- end
68
-
69
- Rackula::Command::Top["generate", *arguments,
70
- "--config", config_path,
71
- "--public", public_path,
72
- "--output-path", output_path
73
- ].call
61
+ context["utopia:static:generate"].call(
62
+ output_path: output_path,
63
+ application_path: application_path,
64
+ public_path: public_path,
65
+ force: force,
66
+ )
74
67
 
75
68
  FileUtils.touch File.expand_path(".nojekyll", output_path)
76
69
  end
@@ -94,4 +87,3 @@ def description(root: context.root)
94
87
  end
95
88
  end
96
89
  end
97
-
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2025, by Samuel Williams.
4
+ # Copyright, 2020-2026, by Samuel Williams.
5
5
  # Copyright, 2023, by Michael Adams.
6
6
 
7
7
  require "utopia/path"
@@ -24,6 +24,8 @@ module Utopia
24
24
  class Base
25
25
  extend Thread::Local
26
26
 
27
+ # Load the current project and index its Ruby source files.
28
+ # @returns [Base] The loaded project.
27
29
  def self.local
28
30
  instance = self.new
29
31
 
@@ -100,6 +102,9 @@ module Utopia
100
102
  end
101
103
  end
102
104
 
105
+ # Load the supplemental document associated with a definition.
106
+ # @parameter definition [Decode::Definition] The definition to load documentation for.
107
+ # @returns [Document | Nil] The supplemental document, if it exists.
103
108
  def document_for(definition)
104
109
  document_path = File.join("lib", definition.lexical_path.map{|_| _.to_s.downcase}) + ".md"
105
110
 
@@ -114,6 +119,11 @@ module Utopia
114
119
  end
115
120
  end
116
121
 
122
+ # Format source text with links to referenced definitions.
123
+ # @parameter text [String] The source text to format.
124
+ # @parameter definition [Decode::Definition] The definition that provides the lexical context.
125
+ # @parameter language [Decode::Language::Generic] The source language.
126
+ # @returns [String] The formatted source code markup.
117
127
  def linkify(text, definition, language: definition&.language)
118
128
  rewriter = Linkify.new(self, language, text)
119
129
 
@@ -205,22 +215,30 @@ module Utopia
205
215
  @guides ||= Guides.new(self, @links)
206
216
  end
207
217
 
218
+ # Load the project README document.
219
+ # @returns [Document | Nil] The README document, if one exists.
208
220
  def readme_document
209
221
  if path = self.path_for("readme.md") || self.path_for("README.md")
210
222
  Document.new(File.read(path), self)
211
223
  end
212
224
  end
213
225
 
226
+ # Get the project title from its README.
227
+ # @returns [String] The project title, or a generic fallback.
214
228
  def project_title
215
229
  readme_document&.title || "Project"
216
230
  end
217
231
 
232
+ # Load the project release notes document.
233
+ # @returns [ReleasesDocument | Nil] The release notes document, if one exists.
218
234
  def releases_document
219
235
  if path = self.path_for("releases.md")
220
236
  ReleasesDocument.new(File.read(path), self)
221
237
  end
222
238
  end
223
239
 
240
+ # Enumerate the project releases.
241
+ # @returns [Enumerator(ReleasesDocument::Release) | Nil] The releases, if release notes exist.
224
242
  def releases
225
243
  if releases_document = self.releases_document
226
244
  releases_document.releases
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2025, by Samuel Williams.
4
+ # Copyright, 2020-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "renderer"
7
7
  require "xrb"
8
8
 
9
9
  module Utopia
10
10
  module Project
11
+ # Represents a Markdown document with optional source code cross-references.
11
12
  class Document
13
+ # Initialize a document from Markdown text.
14
+ # @parameter text [String] The Markdown source text.
15
+ # @parameter base [Base | Nil] The project used to resolve source code references.
16
+ # @parameter definition [Decode::Definition | Nil] The definition that provides the lexical context.
17
+ # @parameter default_language [Decode::Language::Generic | Nil] The default language for source code references.
12
18
  def initialize(text, base = nil, definition: nil, default_language: nil)
13
19
  @text = text
14
20
  @base = base
@@ -20,10 +26,14 @@ module Utopia
20
26
  @root = nil
21
27
  end
22
28
 
29
+ # Parse and resolve the document root.
30
+ # @returns [Markly::Node] The root document node.
23
31
  def root
24
32
  @root ||= resolve(Markly.parse(@text, extensions: [:table]))
25
33
  end
26
34
 
35
+ # Extract the leading heading as the document title.
36
+ # @returns [String | Nil] The title, if the document starts with a heading.
27
37
  def title
28
38
  child = self.root.first_child
29
39
 
@@ -32,10 +42,17 @@ module Utopia
32
42
  end
33
43
  end
34
44
 
45
+ # Get the first node in the document.
46
+ # @returns [Markly::Node | Nil] The first child node.
35
47
  def first_child
36
48
  self.root.first_child
37
49
  end
38
50
 
51
+ # Remove a named section and yield its heading for replacement.
52
+ # @parameter name [String] A fragment of the heading text to match.
53
+ # @parameter children [Boolean] Whether to remove nested subsections too.
54
+ # @yields {|header| ...} The matched heading node.
55
+ # @parameter header [Markly::Node] The matched heading.
39
56
  def replace_section(name, children: false)
40
57
  child = self.first_child
41
58
 
@@ -72,39 +89,62 @@ module Utopia
72
89
  end
73
90
  end
74
91
 
92
+ # Render the document as Markdown.
93
+ # @returns [String] The rendered Markdown.
75
94
  def to_markdown(**options)
76
95
  self.root.to_markdown(**options)
77
96
  end
78
97
 
98
+ # Render a document node as HTML.
99
+ # @parameter node [Markly::Node] The node to render.
100
+ # @returns [XRB::MarkupString] The rendered HTML markup.
79
101
  def to_html(node = self.root, **options)
80
102
  renderer = Renderer.new(ids: true, flags: Markly::UNSAFE, **options)
81
103
  XRB::Markup.raw(renderer.render(node))
82
104
  end
83
105
 
106
+ # Wrap a node in a paragraph.
107
+ # @parameter child [Markly::Node] The node to wrap.
108
+ # @returns [Markly::Node] The paragraph node.
84
109
  def paragraph_node(child)
85
110
  node = Markly::Node.new(:paragraph)
86
111
  node.append_child(child)
87
112
  return node
88
113
  end
89
114
 
115
+ # Build an HTML block node.
116
+ # @parameter content [String] The raw HTML content.
117
+ # @parameter type [Symbol] The node type retained for compatibility.
118
+ # @returns [Markly::Node] The HTML node.
90
119
  def html_node(content, type = :html)
91
120
  node = Markly::Node.new(:html)
92
121
  node.string_content = content
93
122
  return node
94
123
  end
95
124
 
125
+ # Build an inline HTML node.
126
+ # @parameter content [String] The raw HTML content.
127
+ # @returns [Markly::Node] The inline HTML node.
96
128
  def inline_html_node(content)
97
129
  node = Markly::Node.new(:inline_html)
98
130
  node.string_content = content
99
131
  return node
100
132
  end
101
133
 
134
+ # Build a text node.
135
+ # @parameter content [String] The text content.
136
+ # @returns [Markly::Node] The text node.
102
137
  def text_node(content)
103
138
  node = Markly::Node.new(:text)
104
139
  node.string_content = content
105
140
  return node
106
141
  end
107
142
 
143
+ # Build a link node around a child node.
144
+ # @parameter title [String | Nil] The link title.
145
+ # @parameter url [String | XRB::Reference] The link target.
146
+ # @parameter child [Markly::Node] The linked child node.
147
+ # @returns [Markly::Node] The link node.
108
148
  def link_node(title, url, child)
109
149
  node = Markly::Node.new(:link)
110
150
  node.title = title
@@ -115,6 +155,10 @@ module Utopia
115
155
  return node
116
156
  end
117
157
 
158
+ # Build an inline code node.
159
+ # @parameter content [String] The code content.
160
+ # @parameter language [String | Nil] The source language name.
161
+ # @returns [Markly::Node] The code node.
118
162
  def code_node(content, language = nil)
119
163
  if language
120
164
  node = inline_html_node(
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2025, by Samuel Williams.
4
+ # Copyright, 2020-2026, by Samuel Williams.
5
5
 
6
6
  require "utopia/path"
7
7
  require "xrb/reference"
@@ -38,10 +38,15 @@ module Utopia
38
38
  # @attribute [Hash]
39
39
  attr :metadata
40
40
 
41
+ # The explicit display order of the guide.
42
+ # @returns [Integer | Nil]
41
43
  def order
42
44
  metadata[:order]
43
45
  end
44
46
 
47
+ # Compare guides by explicit order and then by name.
48
+ # @parameter other [Guide] The other guide to compare.
49
+ # @returns [Integer] The comparison result.
45
50
  def <=> other
46
51
  if order = self.order
47
52
  if other_order = other.order
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "guide"
7
7
 
@@ -45,14 +45,14 @@ module Utopia
45
45
  # @parameter name [String] The guide name.
46
46
  # @returns [Guide | Nil]
47
47
  def [](name)
48
- to_a.find { |guide| guide.name == name }
48
+ to_a.find{|guide| guide.name == name}
49
49
  end
50
50
 
51
51
  # Get the related guides (previous and next) for the given guide.
52
52
  # @parameter guide [Guide] The current guide.
53
53
  # @returns [Array(Guide | Nil, Guide | Nil)] A two-element array containing the previous and next guides.
54
54
  def related(guide)
55
- index = to_a.index { |g| g.name == guide.name }
55
+ index = to_a.index{|g| g.name == guide.name}
56
56
  return [nil, nil] unless index
57
57
 
58
58
  previous_guide = index > 0 ? to_a[index - 1] : nil
@@ -63,4 +63,3 @@ module Utopia
63
63
  end
64
64
  end
65
65
  end
66
-
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
+
3
6
  require "utopia/import_map"
4
7
 
5
8
  module Utopia
@@ -9,4 +12,4 @@ module Utopia
9
12
  map.import("@socketry/syntax", "./@socketry/syntax/Syntax.js")
10
13
  end
11
14
  end
12
- end
15
+ end
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2025, by Samuel Williams.
4
+ # Copyright, 2020-2026, by Samuel Williams.
5
5
 
6
6
  require "decode/syntax/rewriter"
7
7
 
8
8
  module Utopia
9
9
  module Project
10
+ # Rewrites source code as HTML with links to indexed definitions.
10
11
  class Linkify < Decode::Syntax::Rewriter
12
+ # Initialize a source code rewriter.
11
13
  # @parameter base [Base] The base data.
14
+ # @parameter language [Decode::Language::Generic] The source language.
15
+ # @parameter text [String] The source text to rewrite.
12
16
  def initialize(base, language, text)
13
17
  @base = base
14
18
  @language = language
@@ -16,12 +20,19 @@ module Utopia
16
20
  super(text)
17
21
  end
18
22
 
23
+ # Escape an unmatched range of source text for HTML output.
24
+ # @parameter range [Range] The source text range.
25
+ # @returns [String] The escaped text.
19
26
  def text_for(range)
20
27
  text = super(range)
21
28
 
22
29
  return XRB::Strings.to_html(text)
23
30
  end
24
31
 
32
+ # Build a link to an indexed definition.
33
+ # @parameter definition [Decode::Definition] The linked definition.
34
+ # @parameter text [String] The visible link text.
35
+ # @returns [XRB::MarkupString] The link markup.
25
36
  def link_to(definition, text)
26
37
  XRB::Builder.fragment do |builder|
27
38
  builder.inline("a", href: @base.link_for(definition), title: definition.qualified_name) do
@@ -30,6 +41,9 @@ module Utopia
30
41
  end
31
42
  end
32
43
 
44
+ # Apply the source links and wrap the result in a code element.
45
+ # @parameter output [XRB::Builder] The output builder.
46
+ # @returns [String] The generated HTML markup.
33
47
  def apply(output = XRB::Builder.new)
34
48
  output.inline("code", class: "language-#{@language.name}") do
35
49
  super
@@ -1,39 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2024-2025, by Samuel Williams.
4
+ # Copyright, 2024-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "document"
7
7
  require_relative "sidebar"
8
8
 
9
9
  module Utopia
10
10
  module Project
11
+ # Represents project release notes organized by release headings.
11
12
  class ReleasesDocument < Document
13
+ # Represents a section within a release.
12
14
  class Summary
15
+ # Initialize a release section summary.
16
+ # @parameter node [Markly::Node] The section heading node.
13
17
  def initialize(node)
14
18
  @node = node
15
19
  end
16
20
 
21
+ # The section heading node.
22
+ # @attribute [Markly::Node]
17
23
  attr :node
18
24
 
25
+ # Generate the section identifier.
26
+ # @returns [String] The normalized section identifier.
19
27
  def id
20
28
  @node.to_plaintext.chomp.downcase.gsub(/\s+/, "-")
21
29
  end
22
30
 
31
+ # Render the section heading contents as Markdown.
32
+ # @returns [String] The rendered Markdown.
23
33
  def to_markdown
24
34
  @node.dup.extract_children.to_markdown
25
35
  end
26
36
 
37
+ # Render the section heading contents as HTML.
38
+ # @returns [String] The rendered HTML.
27
39
  def to_html
28
40
  @node.dup.extract_children.to_html
29
41
  end
30
42
  end
31
43
 
44
+ # Represents a single release and its notes.
32
45
  class Release
46
+ # Initialize a release from its heading node.
47
+ # @parameter node [Markly::Node] The release heading node.
33
48
  def initialize(node)
34
49
  @node = node
35
50
  end
36
51
 
52
+ # Extract the introductory notes for the release.
53
+ # @returns [Markly::Node] A document containing the release notes.
37
54
  def notes
38
55
  node = @node.next
39
56
 
@@ -48,6 +65,10 @@ module Utopia
48
65
  return notes
49
66
  end
50
67
 
68
+ # Enumerate the change sections for the release.
69
+ # @yields {|summary| ...} If a block is given.
70
+ # @parameter summary [Summary] A change section.
71
+ # @returns [Enumerator(Summary)] If no block is given.
51
72
  def changes
52
73
  return to_enum(:changes) unless block_given?
53
74
 
@@ -68,15 +89,25 @@ module Utopia
68
89
  end
69
90
  end
70
91
 
92
+ # Get the release name from its heading.
93
+ # @returns [String] The release name.
71
94
  def name
72
95
  @node.to_plaintext.chomp
73
96
  end
74
97
 
98
+ # Build a link to a section within the release.
99
+ # @parameter base [String] The base URL path.
100
+ # @parameter anchor [String] The section anchor.
101
+ # @returns [String] The release section URL.
75
102
  def href(base = "/", anchor:)
76
103
  "#{base}releases/index##{anchor.downcase.gsub(/\s+/, "-")}"
77
104
  end
78
105
  end
79
106
 
107
+ # Enumerate release names in document order.
108
+ # @yields {|name| ...} If a block is given.
109
+ # @parameter name [String] A release name.
110
+ # @returns [Enumerator(String)] If no block is given.
80
111
  def release_names
81
112
  return to_enum(:release_names) unless block_given?
82
113
 
@@ -87,6 +118,9 @@ module Utopia
87
118
  end
88
119
  end
89
120
 
121
+ # Find a release by name.
122
+ # @parameter name [String] The release name.
123
+ # @returns [Release | Nil] The matching release.
90
124
  def release(name)
91
125
  self.root.each do |node|
92
126
  if node.type == :header and node.header_level == 2 and node.to_plaintext.chomp == name
@@ -95,12 +129,18 @@ module Utopia
95
129
  end
96
130
  end
97
131
 
132
+ # Get the first release in the document.
133
+ # @returns [Release | Nil] The latest release.
98
134
  def latest_release
99
135
  if name = release_names.first
100
136
  release(name)
101
137
  end
102
138
  end
103
139
 
140
+ # Enumerate releases in document order.
141
+ # @yields {|release| ...} If a block is given.
142
+ # @parameter release [Release] A release.
143
+ # @returns [Enumerator(Release)] If no block is given.
104
144
  def releases
105
145
  return to_enum(:releases) unless block_given?
106
146
 
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2025, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  require "markly"
7
7
  require "markly/renderer/html"
8
8
 
9
9
  module Utopia
10
10
  module Project
11
+ # Renders project Markdown with support for Mermaid code blocks.
11
12
  class Renderer < Markly::Renderer::HTML
13
+ # Render a fenced code block, including Mermaid diagrams.
14
+ # @parameter node [Markly::Node] The fenced code block node.
12
15
  def code_block(node)
13
16
  language, _ = node.fence_info.split(/\s+/, 2)
14
17
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "renderer"
7
7
 
@@ -12,6 +12,10 @@ module Utopia
12
12
 
13
13
  # Represents a sidebar navigation entry with title, level, and anchor.
14
14
  class Entry
15
+ # Initialize a sidebar entry.
16
+ # @parameter title_html [XRB::Markup] The rendered heading title.
17
+ # @parameter level [Integer] The heading level.
18
+ # @parameter anchor [String] The heading anchor.
15
19
  def initialize(title_html, level, anchor)
16
20
  @title_html = title_html
17
21
  @level = level
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Utopia
7
7
  module Project
8
- VERSION = "0.40.0"
8
+ VERSION = "0.41.0"
9
9
  end
10
10
  end
@@ -1,18 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2025, by Samuel Williams.
4
+ # Copyright, 2020-2026, by Samuel Williams.
5
5
 
6
6
  require "utopia/project/version"
7
7
 
8
8
  require "variant"
9
9
 
10
+ require "utopia/application"
11
+ require "utopia/content"
12
+ require "utopia/controller"
13
+ require "utopia/exceptions"
10
14
  require "utopia/localization"
15
+ require "utopia/redirection"
16
+ require "utopia/static"
11
17
 
12
18
  require_relative "project/base"
13
19
  require_relative "project/import_map"
14
20
 
21
+ # @namespace
15
22
  module Utopia
23
+ # Provides project documentation generation and rendering.
16
24
  module Project
17
25
  # The root directory of the web application files.
18
26
  SITE_ROOT = File.expand_path("../..", __dir__)
@@ -23,9 +31,9 @@ module Utopia
23
31
  # The root directory for static assets.
24
32
  PUBLIC_ROOT = File.expand_path("public", SITE_ROOT)
25
33
 
26
- # Appends a project application to the rack builder.
34
+ # Appends a project application to the protocol HTTP middleware builder.
27
35
  #
28
- # @parameter builder [Rack::Builder]
36
+ # @parameter builder [Protocol::HTTP::Middleware::Builder]
29
37
  # @parameter root [String] The file-system root path of the project/gem.
30
38
  # @parameter locales [Array(String)] an array of locales to support, e.g. `['en', 'ja']`.
31
39
  def self.call(builder, root = Dir.pwd, locales: nil)
@@ -33,9 +41,6 @@ module Utopia
33
41
  # Handle exceptions in production with a error page and send an email notification:
34
42
  builder.use Utopia::Exceptions::Handler
35
43
  builder.use Utopia::Exceptions::Mailer
36
- else
37
- # We want to propate exceptions up when running tests:
38
- builder.use Rack::ShowExceptions unless UTOPIA.testing?
39
44
  end
40
45
 
41
46
  # We serve static files from the project root:
@@ -46,7 +51,6 @@ module Utopia
46
51
  builder.use Utopia::Redirection::Rewrite, {
47
52
  "/" => "/index"
48
53
  }
49
-
50
54
  builder.use Utopia::Redirection::DirectoryIndex
51
55
 
52
56
  builder.use Utopia::Redirection::Errors, {
@@ -67,7 +71,6 @@ module Utopia
67
71
  # 'gallery' => Utopia::Gallery::Tags.new
68
72
  }
69
73
 
70
- builder.run lambda {|env| [404, {}, []]}
71
74
  end
72
75
  end
73
76
  end
data/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2020-2025, by Samuel Williams.
3
+ Copyright, 2020-2026, by Samuel Williams.
4
4
  Copyright, 2020, by Olle Jonsson.
5
5
  Copyright, 2022-2023, by dependabot[bot].
6
6
  Copyright, 2023, by Michael Adams.
data/pages/_header.xnode CHANGED
@@ -6,7 +6,7 @@
6
6
  path = path + "index"
7
7
  end
8
8
 
9
- if link = links(path.dirname, name: path.last, locale: localization.current_locale, indices: true).first
9
+ if link = links(path.dirname, name: path.last, locale: localization&.locale, indices: true).first
10
10
  if replace = link[:replace]&.to_sym and base = controller[:base]
11
11
  ?> &rsaquo; #{link.to_href(content: base.public_send(replace))}<?r
12
12
  else
@@ -30,11 +30,11 @@
30
30
  ?> &rsaquo; <a href="/guides/#{guide.name}/index">#{guide.title}</a> <?r
31
31
  end
32
32
 
33
- if localization.localized?
33
+ if localization&.localized?
34
34
  ?> &bull; (<?r
35
35
  localization.all_locales.each.with_index do |locale, index|
36
36
  ?>#{index.zero? ? '' : ' '}<a href="#{localization.localized_path(page_path, locale)}">#{locale}</a><?r
37
37
  end
38
38
  ?>)<?r
39
39
  end
40
- ?></header>
40
+ ?></header>
data/pages/_page.xnode CHANGED
@@ -15,7 +15,7 @@
15
15
  <link rel="icon" type="image/png" href="/_static/icon.png" />
16
16
  <link rel="stylesheet" href="/_static/site.css" type="text/css" media="screen" />
17
17
 
18
- #{Utopia::Project::IMPORT_MAP.relative_to(request.env["REQUEST_PATH"]).to_html}
18
+ #{Utopia::Project::IMPORT_MAP.relative_to(request.request_path).to_html}
19
19
  <script type="module" src="/_static/application.js"></script>
20
20
  </head>
21
21
  <body>
@@ -3,7 +3,21 @@
3
3
  symbol = self[:symbol]
4
4
  documentation = symbol&.documentation
5
5
 
6
+ # Check if there are any non-Example children before rendering:
7
+ has_signature_children = false
6
8
  if documentation&.children?
9
+ documentation.traverse do |node, descend|
10
+ node.each do |child|
11
+ unless child.is_a?(Decode::Comment::Example)
12
+ has_signature_children = true
13
+ break
14
+ end
15
+ end
16
+ break if has_signature_children
17
+ end
18
+ end
19
+
20
+ if has_signature_children
7
21
  ?>
8
22
  <details open>
9
23
  <summary><h4>Signature</h4></summary>
data/readme.md CHANGED
@@ -33,6 +33,11 @@ Please see the [project documentation](https://socketry.github.io/utopia-project
33
33
 
34
34
  Please see the [project releases](https://socketry.github.io/utopia-project/releases/index) for all releases.
35
35
 
36
+ ### v0.41.0
37
+
38
+ - Don't render empty signature block when there are only examples.
39
+ - Use protocol HTTP middleware for serving and testing project documentation.
40
+
36
41
  ### v0.40.0
37
42
 
38
43
  - Fixed duplicate heading IDs when multiple sections have the same title. Permalinks and sidebar scroll tracking now work correctly when you have headings with identical text in different sections (e.g., multiple "Deployment" subsections under "Kubernetes" and "Systemd").
@@ -71,10 +76,6 @@ Please see the [project releases](https://socketry.github.io/utopia-project/rele
71
76
 
72
77
  - Support brief release notes in `releases.md` document.
73
78
 
74
- ### v0.30.0
75
-
76
- - [Rename `changes.md` to `releases.md`](https://socketry.github.io/utopia-project/releases/index#rename-changes.md-to-releases.md)
77
-
78
79
  ## See Also
79
80
 
80
81
  - [Utopia](https://github.com/socketry/utopia) — The website framework which powers this web application.
@@ -84,11 +85,27 @@ Please see the [project releases](https://socketry.github.io/utopia-project/rele
84
85
 
85
86
  We welcome contributions to this project.
86
87
 
87
- 1. Fork it.
88
+ 1. Fork the repository.
88
89
  2. Create your feature branch (`git checkout -b my-new-feature`).
89
- 3. Commit your changes (`git commit -am 'Add some feature'`).
90
+ 3. Commit your changes (`git commit -am 'Add some feature.'`).
90
91
  4. Push to the branch (`git push origin my-new-feature`).
91
- 5. Create new Pull Request.
92
+ 5. Create a new pull request.
93
+
94
+ ### Running Tests
95
+
96
+ To run the test suite:
97
+
98
+ ``` shell
99
+ bundle exec sus
100
+ ```
101
+
102
+ ### Making Releases
103
+
104
+ To make a new release:
105
+
106
+ ``` shell
107
+ bundle exec bake gem:release:patch # or minor or major
108
+ ```
92
109
 
93
110
  ### Developer Certificate of Origin
94
111
 
data/releases.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changes
2
2
 
3
+ ## v0.41.0
4
+
5
+ - Don't render empty signature block when there are only examples.
6
+ - Use protocol HTTP middleware for serving and testing project documentation.
7
+
3
8
  ## v0.40.0
4
9
 
5
10
  - Fixed duplicate heading IDs when multiple sections have the same title. Permalinks and sidebar scroll tracking now work correctly when you have headings with identical text in different sections (e.g., multiple "Deployment" subsections under "Kubernetes" and "Systemd").
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "environment"
7
+
8
+ require "utopia/project"
9
+
10
+ Application = Utopia::Application.build do |builder|
11
+ Utopia::Project.call(builder)
12
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2020-2026, by Samuel Williams.
5
+
6
+ require "utopia/setup"
7
+
8
+ UTOPIA ||= Utopia.setup(File.expand_path("..", __dir__))
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "utopia/application"
7
+
8
+ run Utopia::Application.load(File.expand_path("application.rb", __dir__))
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: utopia-project
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.40.0
4
+ version: 0.41.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -83,20 +83,6 @@ dependencies:
83
83
  - - "~>"
84
84
  - !ruby/object:Gem::Version
85
85
  version: '0.15'
86
- - !ruby/object:Gem::Dependency
87
- name: rackula
88
- requirement: !ruby/object:Gem::Requirement
89
- requirements:
90
- - - "~>"
91
- - !ruby/object:Gem::Version
92
- version: '1.3'
93
- type: :runtime
94
- prerelease: false
95
- version_requirements: !ruby/object:Gem::Requirement
96
- requirements:
97
- - - "~>"
98
- - !ruby/object:Gem::Version
99
- version: '1.3'
100
86
  - !ruby/object:Gem::Dependency
101
87
  name: thread-local
102
88
  requirement: !ruby/object:Gem::Requirement
@@ -117,14 +103,14 @@ dependencies:
117
103
  requirements:
118
104
  - - "~>"
119
105
  - !ruby/object:Gem::Version
120
- version: '2.32'
106
+ version: '3.0'
121
107
  type: :runtime
122
108
  prerelease: false
123
109
  version_requirements: !ruby/object:Gem::Requirement
124
110
  requirements:
125
111
  - - "~>"
126
112
  - !ruby/object:Gem::Version
127
- version: '2.32'
113
+ version: '3.0'
128
114
  executables: []
129
115
  extensions: []
130
116
  extra_rdoc_files: []
@@ -674,7 +660,9 @@ files:
674
660
  - public/robots.txt
675
661
  - readme.md
676
662
  - releases.md
677
- - template/config.ru
663
+ - template/config/application.rb
664
+ - template/config/environment.rb
665
+ - template/config/serve.rb
678
666
  - template/gems.rb
679
667
  - template/preload.rb
680
668
  homepage: https://socketry.github.io/utopia-project
@@ -691,14 +679,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
691
679
  requirements:
692
680
  - - ">="
693
681
  - !ruby/object:Gem::Version
694
- version: '3.2'
682
+ version: '3.3'
695
683
  required_rubygems_version: !ruby/object:Gem::Requirement
696
684
  requirements:
697
685
  - - ">="
698
686
  - !ruby/object:Gem::Version
699
687
  version: '0'
700
688
  requirements: []
701
- rubygems_version: 3.6.9
689
+ rubygems_version: 4.0.10
702
690
  specification_version: 4
703
691
  summary: A project documentation tool based on Utopia.
704
692
  test_files: []
metadata.gz.sig CHANGED
Binary file
data/template/config.ru DELETED
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "utopia/setup"
4
- UTOPIA ||= Utopia.setup(Dir.pwd)
5
-
6
- require "utopia/project"
7
- Utopia::Project.call(self)