utopia-project 0.39.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bake/utopia/project.rb +9 -17
- data/lib/utopia/project/base.rb +23 -15
- data/lib/utopia/project/document.rb +45 -1
- data/lib/utopia/project/guide.rb +6 -1
- data/lib/utopia/project/guides.rb +65 -0
- data/lib/utopia/project/import_map.rb +6 -1
- data/lib/utopia/project/linkify.rb +15 -1
- data/lib/utopia/project/releases_document.rb +41 -1
- data/lib/utopia/project/renderer.rb +4 -1
- data/lib/utopia/project/sidebar.rb +11 -17
- data/lib/utopia/project/version.rb +1 -1
- data/lib/utopia/project.rb +11 -8
- data/license.md +1 -1
- data/pages/_header.xnode +3 -3
- data/pages/_page.xnode +1 -1
- data/pages/_usage.xnode +2 -2
- data/pages/guides/index.xnode +17 -8
- data/pages/guides/show.xnode +34 -0
- data/pages/source/_signature.xnode +14 -0
- data/public/_static/site.css +34 -0
- data/readme.md +30 -11
- data/releases.md +11 -0
- data/template/config/application.rb +12 -0
- data/template/config/environment.rb +8 -0
- data/template/config/serve.rb +8 -0
- data.tar.gz.sig +0 -0
- metadata +11 -22
- metadata.gz.sig +0 -0
- data/template/config.ru +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2b939e146930849d4cf8f02d043129ed9ac4d4e30cef2c672936deeba8ebf834
|
|
4
|
+
data.tar.gz: e481fe629ee2812bf5138577ee0e55b8a1025960bb0f050129b40a1bae3a3565
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d403f98935fc293324f8d0b1001535cfe38d7ce04aea3040257b651d89fd2bcacad593d0749fe2c250bd8b61ccbd30dd00db40f981164865d74ae26831841f51
|
|
7
|
+
data.tar.gz: 28ecbb2dae306f4a0568e7b325a5f47f2d487de5854190db1d82e8bb489effcd8d77d62ef3cf94ece65c21a863a2c7b41158fc5f4141242497908752d8910c20
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/bake/utopia/project.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
data/lib/utopia/project/base.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
4
|
+
# Copyright, 2020-2026, by Samuel Williams.
|
|
5
5
|
# Copyright, 2023, by Michael Adams.
|
|
6
6
|
|
|
7
7
|
require "utopia/path"
|
|
@@ -15,7 +15,7 @@ require "thread/local"
|
|
|
15
15
|
require_relative "document"
|
|
16
16
|
require_relative "releases_document"
|
|
17
17
|
|
|
18
|
-
require_relative "
|
|
18
|
+
require_relative "guides"
|
|
19
19
|
require_relative "linkify"
|
|
20
20
|
|
|
21
21
|
module Utopia
|
|
@@ -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
|
|
|
@@ -193,10 +203,8 @@ module Utopia
|
|
|
193
203
|
end
|
|
194
204
|
end
|
|
195
205
|
|
|
196
|
-
#
|
|
197
|
-
# @
|
|
198
|
-
# @parameter guide [Guide]
|
|
199
|
-
# @returns [Enumerator(Guide)] If no block given.
|
|
206
|
+
# Get the guides collection for this project.
|
|
207
|
+
# @returns [Guides]
|
|
200
208
|
#
|
|
201
209
|
# @example List guide titles
|
|
202
210
|
# base = Utopia::Project::Base.new
|
|
@@ -204,33 +212,33 @@ module Utopia
|
|
|
204
212
|
# puts guide.title
|
|
205
213
|
# end
|
|
206
214
|
def guides
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
@links.index("/guides").each do |link|
|
|
210
|
-
guide_path = File.join(@root, link.path)
|
|
211
|
-
|
|
212
|
-
next unless File.directory?(guide_path)
|
|
213
|
-
|
|
214
|
-
yield Guide.new(self, guide_path, link.info)
|
|
215
|
-
end
|
|
215
|
+
@guides ||= Guides.new(self, @links)
|
|
216
216
|
end
|
|
217
217
|
|
|
218
|
+
# Load the project README document.
|
|
219
|
+
# @returns [Document | Nil] The README document, if one exists.
|
|
218
220
|
def readme_document
|
|
219
221
|
if path = self.path_for("readme.md") || self.path_for("README.md")
|
|
220
222
|
Document.new(File.read(path), self)
|
|
221
223
|
end
|
|
222
224
|
end
|
|
223
225
|
|
|
226
|
+
# Get the project title from its README.
|
|
227
|
+
# @returns [String] The project title, or a generic fallback.
|
|
224
228
|
def project_title
|
|
225
229
|
readme_document&.title || "Project"
|
|
226
230
|
end
|
|
227
231
|
|
|
232
|
+
# Load the project release notes document.
|
|
233
|
+
# @returns [ReleasesDocument | Nil] The release notes document, if one exists.
|
|
228
234
|
def releases_document
|
|
229
235
|
if path = self.path_for("releases.md")
|
|
230
236
|
ReleasesDocument.new(File.read(path), self)
|
|
231
237
|
end
|
|
232
238
|
end
|
|
233
239
|
|
|
240
|
+
# Enumerate the project releases.
|
|
241
|
+
# @returns [Enumerator(ReleasesDocument::Release) | Nil] The releases, if release notes exist.
|
|
234
242
|
def releases
|
|
235
243
|
if releases_document = self.releases_document
|
|
236
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-
|
|
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(
|
data/lib/utopia/project/guide.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
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
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "guide"
|
|
7
|
+
|
|
8
|
+
module Utopia
|
|
9
|
+
module Project
|
|
10
|
+
# A collection of guides with navigation and lookup capabilities.
|
|
11
|
+
class Guides
|
|
12
|
+
include Enumerable
|
|
13
|
+
|
|
14
|
+
# Initialize the guides collection.
|
|
15
|
+
# @parameter base [Base] The base instance for the project.
|
|
16
|
+
# @parameter links [Object] The links index for finding guides.
|
|
17
|
+
def initialize(base, links)
|
|
18
|
+
@base = base
|
|
19
|
+
@links = links
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Iterate over all guides.
|
|
23
|
+
# @yields {|guide| ...} If a block is given.
|
|
24
|
+
# @parameter guide [Guide]
|
|
25
|
+
# @returns [Enumerator(Guide)] If no block is given.
|
|
26
|
+
def each(&block)
|
|
27
|
+
return to_enum(:each) unless block_given?
|
|
28
|
+
|
|
29
|
+
@links.index("/guides").each do |link|
|
|
30
|
+
guide_path = File.join(@base.root, link.path)
|
|
31
|
+
|
|
32
|
+
next unless File.directory?(guide_path)
|
|
33
|
+
|
|
34
|
+
yield Guide.new(@base, guide_path, link.info)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get all guides as a sorted array.
|
|
39
|
+
# @returns [Array(Guide)]
|
|
40
|
+
def to_a
|
|
41
|
+
@array ||= super.sort
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Find a guide by name.
|
|
45
|
+
# @parameter name [String] The guide name.
|
|
46
|
+
# @returns [Guide | Nil]
|
|
47
|
+
def [](name)
|
|
48
|
+
to_a.find{|guide| guide.name == name}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get the related guides (previous and next) for the given guide.
|
|
52
|
+
# @parameter guide [Guide] The current guide.
|
|
53
|
+
# @returns [Array(Guide | Nil, Guide | Nil)] A two-element array containing the previous and next guides.
|
|
54
|
+
def related(guide)
|
|
55
|
+
index = to_a.index{|g| g.name == guide.name}
|
|
56
|
+
return [nil, nil] unless index
|
|
57
|
+
|
|
58
|
+
previous_guide = index > 0 ? to_a[index - 1] : nil
|
|
59
|
+
next_guide = index < to_a.size - 1 ? to_a[index + 1] : nil
|
|
60
|
+
|
|
61
|
+
[previous_guide, next_guide]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
|
+
|
|
1
6
|
require "utopia/import_map"
|
|
2
7
|
|
|
3
8
|
module Utopia
|
|
@@ -7,4 +12,4 @@ module Utopia
|
|
|
7
12
|
map.import("@socketry/syntax", "./@socketry/syntax/Syntax.js")
|
|
8
13
|
end
|
|
9
14
|
end
|
|
10
|
-
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-
|
|
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-
|
|
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-
|
|
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
|
|
@@ -90,24 +94,14 @@ module Utopia
|
|
|
90
94
|
private
|
|
91
95
|
|
|
92
96
|
def self.extract_headings_from_document(document)
|
|
93
|
-
|
|
94
|
-
return headings unless document&.root
|
|
97
|
+
return [] unless document&.root
|
|
95
98
|
|
|
96
|
-
document.root.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
title = XRB::Markup.raw(fragment.to_html)
|
|
103
|
-
level = node.header_level
|
|
104
|
-
anchor = Markly::Renderer::HTML.anchor_for(fragment)
|
|
105
|
-
|
|
106
|
-
headings << Entry.new(title, level, anchor)
|
|
107
|
-
end
|
|
99
|
+
Markly::Renderer::Headings.extract(document.root, min_level: 2, max_level: 3).map do |heading|
|
|
100
|
+
fragment = heading.node.dup.extract_children
|
|
101
|
+
title = XRB::Markup.raw(fragment.to_html)
|
|
102
|
+
|
|
103
|
+
Entry.new(title, heading.level, heading.anchor)
|
|
108
104
|
end
|
|
109
|
-
|
|
110
|
-
headings
|
|
111
105
|
end
|
|
112
106
|
end
|
|
113
107
|
end
|
data/lib/utopia/project.rb
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2020-
|
|
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
|
|
34
|
+
# Appends a project application to the protocol HTTP middleware builder.
|
|
27
35
|
#
|
|
28
|
-
# @parameter 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
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
|
|
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
|
?> › #{link.to_href(content: base.public_send(replace))}<?r
|
|
12
12
|
else
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
?> › <a href="/guides/#{guide.name}/index">#{guide.title}</a> <?r
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
if localization
|
|
33
|
+
if localization&.localized?
|
|
34
34
|
?> • (<?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.
|
|
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>
|
data/pages/_usage.xnode
CHANGED
data/pages/guides/index.xnode
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
<content:page>
|
|
2
|
-
<?r
|
|
3
|
-
base = self[:base]
|
|
4
|
-
?>
|
|
5
2
|
<content:heading>Guides</content:heading>
|
|
6
3
|
|
|
7
|
-
<
|
|
4
|
+
<section>
|
|
8
5
|
<?r
|
|
9
|
-
base =
|
|
6
|
+
base = self[:base]
|
|
10
7
|
|
|
11
|
-
base.guides do |guide|
|
|
12
|
-
|
|
8
|
+
base.guides.each do |guide|
|
|
9
|
+
?>
|
|
10
|
+
<section>
|
|
11
|
+
<h3><a href="#{guide.href}">#{guide.title}</a></h3>
|
|
12
|
+
|
|
13
|
+
<?r if description = guide.description ?>
|
|
14
|
+
#{Markup.raw description.to_html}
|
|
15
|
+
<?r elsif documentation = guide.documentation ?>
|
|
16
|
+
#{base.format(documentation.text, language: guide.documentation.language)}
|
|
17
|
+
<?r else ?>
|
|
18
|
+
<p>No description.</p>
|
|
19
|
+
<?r end ?>
|
|
20
|
+
</section>
|
|
21
|
+
<?r
|
|
13
22
|
end
|
|
14
23
|
?>
|
|
15
|
-
</
|
|
24
|
+
</section>
|
|
16
25
|
</content:page>
|
data/pages/guides/show.xnode
CHANGED
|
@@ -9,6 +9,24 @@
|
|
|
9
9
|
#{navigation.to_html}
|
|
10
10
|
</aside>
|
|
11
11
|
<div class="content">
|
|
12
|
+
<?r
|
|
13
|
+
previous_guide, next_guide = base.guides.related(guide)
|
|
14
|
+
|
|
15
|
+
if previous_guide or next_guide
|
|
16
|
+
?>
|
|
17
|
+
<nav class="top">
|
|
18
|
+
<?r if previous_guide
|
|
19
|
+
?><a href="#{previous_guide.href}" class="previous">← #{previous_guide.title}</a><?r
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if next_guide
|
|
23
|
+
?><a href="#{next_guide.href}" class="next">#{next_guide.title} →</a><?r
|
|
24
|
+
end ?>
|
|
25
|
+
</nav>
|
|
26
|
+
<?r
|
|
27
|
+
end
|
|
28
|
+
?>
|
|
29
|
+
|
|
12
30
|
<content:heading>#{guide.title}</content:heading>
|
|
13
31
|
|
|
14
32
|
<?r
|
|
@@ -27,5 +45,21 @@
|
|
|
27
45
|
end
|
|
28
46
|
end
|
|
29
47
|
?>
|
|
48
|
+
|
|
49
|
+
<?r
|
|
50
|
+
if previous_guide or next_guide
|
|
51
|
+
?>
|
|
52
|
+
<nav class="bottom">
|
|
53
|
+
<?r if previous_guide
|
|
54
|
+
?><a href="#{previous_guide.href}" class="previous">← #{previous_guide.title}</a><?r
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if next_guide
|
|
58
|
+
?><a href="#{next_guide.href}" class="next">#{next_guide.title} →</a><?r
|
|
59
|
+
end ?>
|
|
60
|
+
</nav>
|
|
61
|
+
<?r
|
|
62
|
+
end
|
|
63
|
+
?>
|
|
30
64
|
</div>
|
|
31
65
|
</content:page>
|
|
@@ -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/public/_static/site.css
CHANGED
|
@@ -594,3 +594,37 @@ ul.pragmas li.asynchronous {
|
|
|
594
594
|
border-left-color: var(--accent-color);
|
|
595
595
|
font-weight: 500;
|
|
596
596
|
}
|
|
597
|
+
|
|
598
|
+
/* Guide navigation (previous/next) */
|
|
599
|
+
.content nav {
|
|
600
|
+
display: flex;
|
|
601
|
+
justify-content: space-between;
|
|
602
|
+
gap: 1rem;
|
|
603
|
+
margin: 1rem;
|
|
604
|
+
font-size: 0.9rem;
|
|
605
|
+
font-weight: 500;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.content nav.top {
|
|
609
|
+
border-bottom: 1px solid var(--underlay-color);
|
|
610
|
+
padding-bottom: 0.5rem;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.content nav.bottom {
|
|
614
|
+
border-top: 1px solid var(--underlay-color);
|
|
615
|
+
padding-top: 0.5rem;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.content nav .next {
|
|
619
|
+
margin-left: auto;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
@media (max-width: 768px) {
|
|
623
|
+
.content nav {
|
|
624
|
+
flex-direction: column;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.content nav .next {
|
|
628
|
+
margin-left: 0;
|
|
629
|
+
}
|
|
630
|
+
}
|
data/readme.md
CHANGED
|
@@ -33,6 +33,17 @@ 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
|
+
|
|
41
|
+
### v0.40.0
|
|
42
|
+
|
|
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").
|
|
44
|
+
- Improved guides index page to show guide summaries (first paragraph) instead of just listing titles.
|
|
45
|
+
- Added previous/next navigation at the top and bottom of guide pages for easier sequential reading.
|
|
46
|
+
|
|
36
47
|
### v0.37.3
|
|
37
48
|
|
|
38
49
|
- Support for `@example` pragmas from the `decode` gem, allowing inline code examples to be rendered in API documentation.
|
|
@@ -65,14 +76,6 @@ Please see the [project releases](https://socketry.github.io/utopia-project/rele
|
|
|
65
76
|
|
|
66
77
|
- Support brief release notes in `releases.md` document.
|
|
67
78
|
|
|
68
|
-
### v0.30.0
|
|
69
|
-
|
|
70
|
-
- [Rename `changes.md` to `releases.md`](https://socketry.github.io/utopia-project/releases/index#rename-changes.md-to-releases.md)
|
|
71
|
-
|
|
72
|
-
### v0.29.0
|
|
73
|
-
|
|
74
|
-
- [Improve `changes.md` document organization](https://socketry.github.io/utopia-project/releases/index#improve-changes.md-document-organization)
|
|
75
|
-
|
|
76
79
|
## See Also
|
|
77
80
|
|
|
78
81
|
- [Utopia](https://github.com/socketry/utopia) — The website framework which powers this web application.
|
|
@@ -82,11 +85,27 @@ Please see the [project releases](https://socketry.github.io/utopia-project/rele
|
|
|
82
85
|
|
|
83
86
|
We welcome contributions to this project.
|
|
84
87
|
|
|
85
|
-
1. Fork
|
|
88
|
+
1. Fork the repository.
|
|
86
89
|
2. Create your feature branch (`git checkout -b my-new-feature`).
|
|
87
|
-
3. Commit your changes (`git commit -am 'Add some feature'`).
|
|
90
|
+
3. Commit your changes (`git commit -am 'Add some feature.'`).
|
|
88
91
|
4. Push to the branch (`git push origin my-new-feature`).
|
|
89
|
-
5. Create new
|
|
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
|
+
```
|
|
90
109
|
|
|
91
110
|
### Developer Certificate of Origin
|
|
92
111
|
|
data/releases.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
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
|
+
|
|
8
|
+
## v0.40.0
|
|
9
|
+
|
|
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").
|
|
11
|
+
- Improved guides index page to show guide summaries (first paragraph) instead of just listing titles.
|
|
12
|
+
- Added previous/next navigation at the top and bottom of guide pages for easier sequential reading.
|
|
13
|
+
|
|
3
14
|
## v0.37.3
|
|
4
15
|
|
|
5
16
|
- Support for `@example` pragmas from the `decode` gem, allowing inline code examples to be rendered in API documentation.
|
|
@@ -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
|
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.
|
|
4
|
+
version: 0.41.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -75,28 +75,14 @@ dependencies:
|
|
|
75
75
|
requirements:
|
|
76
76
|
- - "~>"
|
|
77
77
|
- !ruby/object:Gem::Version
|
|
78
|
-
version: '0.
|
|
78
|
+
version: '0.15'
|
|
79
79
|
type: :runtime
|
|
80
80
|
prerelease: false
|
|
81
81
|
version_requirements: !ruby/object:Gem::Requirement
|
|
82
82
|
requirements:
|
|
83
83
|
- - "~>"
|
|
84
84
|
- !ruby/object:Gem::Version
|
|
85
|
-
version: '0.
|
|
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'
|
|
85
|
+
version: '0.15'
|
|
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: '
|
|
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: '
|
|
113
|
+
version: '3.0'
|
|
128
114
|
executables: []
|
|
129
115
|
extensions: []
|
|
130
116
|
extra_rdoc_files: []
|
|
@@ -142,6 +128,7 @@ files:
|
|
|
142
128
|
- lib/utopia/project/base.rb
|
|
143
129
|
- lib/utopia/project/document.rb
|
|
144
130
|
- lib/utopia/project/guide.rb
|
|
131
|
+
- lib/utopia/project/guides.rb
|
|
145
132
|
- lib/utopia/project/import_map.rb
|
|
146
133
|
- lib/utopia/project/linkify.rb
|
|
147
134
|
- lib/utopia/project/releases_document.rb
|
|
@@ -673,7 +660,9 @@ files:
|
|
|
673
660
|
- public/robots.txt
|
|
674
661
|
- readme.md
|
|
675
662
|
- releases.md
|
|
676
|
-
- template/config.
|
|
663
|
+
- template/config/application.rb
|
|
664
|
+
- template/config/environment.rb
|
|
665
|
+
- template/config/serve.rb
|
|
677
666
|
- template/gems.rb
|
|
678
667
|
- template/preload.rb
|
|
679
668
|
homepage: https://socketry.github.io/utopia-project
|
|
@@ -690,14 +679,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
690
679
|
requirements:
|
|
691
680
|
- - ">="
|
|
692
681
|
- !ruby/object:Gem::Version
|
|
693
|
-
version: '3.
|
|
682
|
+
version: '3.3'
|
|
694
683
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
695
684
|
requirements:
|
|
696
685
|
- - ">="
|
|
697
686
|
- !ruby/object:Gem::Version
|
|
698
687
|
version: '0'
|
|
699
688
|
requirements: []
|
|
700
|
-
rubygems_version:
|
|
689
|
+
rubygems_version: 4.0.10
|
|
701
690
|
specification_version: 4
|
|
702
691
|
summary: A project documentation tool based on Utopia.
|
|
703
692
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|