utopia 2.30.2 → 2.31.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/bake/utopia/server.rb +1 -1
  4. data/bake/utopia/site.rb +3 -3
  5. data/context/getting-started.md +93 -0
  6. data/context/index.yaml +32 -0
  7. data/context/integrating-with-javascript.md +75 -0
  8. data/context/middleware.md +157 -0
  9. data/context/server-setup.md +116 -0
  10. data/context/updating-utopia.md +69 -0
  11. data/context/what-is-xnode.md +41 -0
  12. data/lib/utopia/content/document.rb +39 -37
  13. data/lib/utopia/content/link.rb +1 -2
  14. data/lib/utopia/content/links.rb +2 -2
  15. data/lib/utopia/content/markup.rb +10 -10
  16. data/lib/utopia/content/middleware.rb +195 -0
  17. data/lib/utopia/content/namespace.rb +1 -1
  18. data/lib/utopia/content/node.rb +1 -1
  19. data/lib/utopia/content/response.rb +1 -1
  20. data/lib/utopia/content/tags.rb +1 -1
  21. data/lib/utopia/content.rb +4 -186
  22. data/lib/utopia/controller/actions.md +8 -8
  23. data/lib/utopia/controller/actions.rb +1 -1
  24. data/lib/utopia/controller/base.rb +4 -4
  25. data/lib/utopia/controller/middleware.rb +133 -0
  26. data/lib/utopia/controller/respond.rb +2 -46
  27. data/lib/utopia/controller/responder.rb +103 -0
  28. data/lib/utopia/controller/rewrite.md +2 -2
  29. data/lib/utopia/controller/rewrite.rb +1 -1
  30. data/lib/utopia/controller/variables.rb +11 -5
  31. data/lib/utopia/controller.rb +4 -126
  32. data/lib/utopia/exceptions/mailer.rb +4 -4
  33. data/lib/utopia/extensions/array_split.rb +2 -2
  34. data/lib/utopia/extensions/date_comparisons.rb +3 -3
  35. data/lib/utopia/import_map.rb +374 -0
  36. data/lib/utopia/localization/middleware.rb +173 -0
  37. data/lib/utopia/localization/wrapper.rb +52 -0
  38. data/lib/utopia/localization.rb +4 -202
  39. data/lib/utopia/path.rb +26 -11
  40. data/lib/utopia/redirection.rb +2 -2
  41. data/lib/utopia/session/lazy_hash.rb +1 -1
  42. data/lib/utopia/session/middleware.rb +218 -0
  43. data/lib/utopia/session/serialization.rb +1 -1
  44. data/lib/utopia/session.rb +4 -205
  45. data/lib/utopia/static/local_file.rb +19 -19
  46. data/lib/utopia/static/middleware.rb +120 -0
  47. data/lib/utopia/static/mime_types.rb +1 -1
  48. data/lib/utopia/static.rb +4 -108
  49. data/lib/utopia/version.rb +1 -1
  50. data/lib/utopia.rb +1 -0
  51. data/readme.md +7 -0
  52. data/releases.md +7 -0
  53. data/setup/site/config.ru +1 -1
  54. data.tar.gz.sig +0 -0
  55. metadata +31 -4
  56. metadata.gz.sig +0 -0
  57. data/lib/utopia/locale.rb +0 -29
  58. data/lib/utopia/responder.rb +0 -59
data/lib/utopia/static.rb CHANGED
@@ -3,116 +3,12 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2009-2025, by Samuel Williams.
5
5
 
6
- require_relative "middleware"
7
- require_relative "localization"
8
-
9
- require_relative "static/local_file"
10
- require_relative "static/mime_types"
11
-
12
- require "traces/provider"
6
+ require_relative "static/middleware"
13
7
 
14
8
  module Utopia
15
- # A middleware which serves static files from the specified root directory.
16
- class Static
17
- DEFAULT_CACHE_CONTROL = "public, max-age=3600".freeze
18
-
19
- # @param root [String] The root directory to serve files from.
20
- # @param types [Array] The mime-types (and file extensions) to recognize/serve.
21
- # @param cache_control [String] The cache-control header to set for static content.
22
- def initialize(app, root: Utopia::default_root, types: MIME_TYPES[:default], cache_control: DEFAULT_CACHE_CONTROL)
23
- @app = app
24
- @root = root
25
-
26
- @extensions = MimeTypeLoader.extensions_for(types)
27
-
28
- @cache_control = cache_control
29
- end
30
-
31
- def freeze
32
- return self if frozen?
33
-
34
- @root.freeze
35
- @extensions.freeze
36
- @cache_control.freeze
37
-
38
- super
39
- end
40
-
41
- def fetch_file(path)
42
- # We need file_path to be an absolute path for X-Sendfile to work correctly.
43
- file_path = File.join(@root, path.components)
44
-
45
- if File.exist?(file_path)
46
- return LocalFile.new(@root, path)
47
- else
48
- return nil
49
- end
50
- end
51
-
52
- attr :extensions
53
-
54
- LAST_MODIFIED = "Last-Modified".freeze
55
- CONTENT_TYPE = HTTP::CONTENT_TYPE
56
- CACHE_CONTROL = HTTP::CACHE_CONTROL
57
- ETAG = "ETag".freeze
58
- ACCEPT_RANGES = "Accept-Ranges".freeze
59
-
60
- def response_headers_for(file, content_type)
61
- if @cache_control.respond_to?(:call)
62
- cache_control = @cache_control.call(file)
63
- else
64
- cache_control = @cache_control
65
- end
66
-
67
- {
68
- LAST_MODIFIED => file.mtime_date,
69
- CONTENT_TYPE => content_type,
70
- CACHE_CONTROL => cache_control,
71
- ETAG => file.etag,
72
- ACCEPT_RANGES => "bytes"
73
- }
74
- end
75
-
76
- def respond(env, path_info, extension)
77
- path = Path[path_info].simplify
78
-
79
- if locale = env[Localization::CURRENT_LOCALE_KEY]
80
- path.last.insert(path.last.rindex(".") || -1, ".#{locale}")
81
- end
82
-
83
- if file = fetch_file(path)
84
- response_headers = self.response_headers_for(file, @extensions[extension])
85
-
86
- if file.modified?(env)
87
- return file.serve(env, response_headers)
88
- else
89
- return [304, response_headers, []]
90
- end
91
- end
92
- end
93
-
94
- def call(env)
95
- path_info = env[Rack::PATH_INFO]
96
- extension = File.extname(path_info)
97
-
98
- if @extensions.key?(extension.downcase)
99
- if response = self.respond(env, path_info, extension)
100
- return response
101
- end
102
- end
103
-
104
- # else if no file was found:
105
- return @app.call(env)
106
- end
107
- end
108
-
109
- Traces::Provider(Static) do
110
- def respond(env, path_info, extension)
111
- attributes = {
112
- path_info: path_info,
113
- }
114
-
115
- Traces.trace("utopia.static.respond", attributes: attributes) {super}
9
+ module Static
10
+ def self.new(...)
11
+ Middleware.new(...)
116
12
  end
117
13
  end
118
14
  end
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2009-2025, by Samuel Williams.
5
5
 
6
6
  module Utopia
7
- VERSION = "2.30.2"
7
+ VERSION = "2.31.0"
8
8
  end
data/lib/utopia.rb CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  require_relative "utopia/version"
7
7
 
8
+ require_relative "utopia/import_map"
8
9
  require_relative "utopia/content"
9
10
  require_relative "utopia/controller"
10
11
  require_relative "utopia/exceptions"
data/readme.md CHANGED
@@ -31,6 +31,13 @@ Please see the [project documentation](https://socketry.github.io/utopia/) for m
31
31
 
32
32
  Please see the [project releases](https://socketry.github.io/utopia/releases/index) for all releases.
33
33
 
34
+ ### v2.31.0
35
+
36
+ - Add agent context.
37
+ - Better simplification of relative paths, e.g. `../../foo` is not modified to `foo`.
38
+ - Move top level classes into `class Middleware` in their respective namespaces.
39
+ - Move `Utopia::Responder` into `Utopia::Controller` layer.
40
+
34
41
  ### v2.30.1
35
42
 
36
43
  - Minor compatibility fixes.
data/releases.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Releases
2
2
 
3
+ ## v2.31.0
4
+
5
+ - Add agent context.
6
+ - Better simplification of relative paths, e.g. `../../foo` is not modified to `foo`.
7
+ - Move top level classes into `class Middleware` in their respective namespaces.
8
+ - Move `Utopia::Responder` into `Utopia::Controller` layer.
9
+
3
10
  ## v2.30.1
4
11
 
5
12
  - Minor compatibility fixes.
data/setup/site/config.ru CHANGED
@@ -46,4 +46,4 @@ use Utopia::Static
46
46
  # Serve dynamic content:
47
47
  use Utopia::Content
48
48
 
49
- run lambda { |env| [404, {}, []] }
49
+ run lambda {|env| [404, {}, []]}
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: utopia
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.30.2
4
+ version: 2.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -169,6 +169,20 @@ dependencies:
169
169
  - - ">="
170
170
  - !ruby/object:Gem::Version
171
171
  version: '0'
172
+ - !ruby/object:Gem::Dependency
173
+ name: protocol-url
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '0.4'
179
+ type: :runtime
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: '0.4'
172
186
  - !ruby/object:Gem::Dependency
173
187
  name: rack
174
188
  requirement: !ruby/object:Gem::Requirement
@@ -250,12 +264,20 @@ files:
250
264
  - bake/utopia/shell.rb
251
265
  - bake/utopia/site.rb
252
266
  - bake/utopia/static.rb
267
+ - context/getting-started.md
268
+ - context/index.yaml
269
+ - context/integrating-with-javascript.md
270
+ - context/middleware.md
271
+ - context/server-setup.md
272
+ - context/updating-utopia.md
273
+ - context/what-is-xnode.md
253
274
  - lib/utopia.rb
254
275
  - lib/utopia/content.rb
255
276
  - lib/utopia/content/document.rb
256
277
  - lib/utopia/content/link.rb
257
278
  - lib/utopia/content/links.rb
258
279
  - lib/utopia/content/markup.rb
280
+ - lib/utopia/content/middleware.rb
259
281
  - lib/utopia/content/namespace.rb
260
282
  - lib/utopia/content/node.rb
261
283
  - lib/utopia/content/response.rb
@@ -264,7 +286,9 @@ files:
264
286
  - lib/utopia/controller/actions.md
265
287
  - lib/utopia/controller/actions.rb
266
288
  - lib/utopia/controller/base.rb
289
+ - lib/utopia/controller/middleware.rb
267
290
  - lib/utopia/controller/respond.rb
291
+ - lib/utopia/controller/responder.rb
268
292
  - lib/utopia/controller/rewrite.md
269
293
  - lib/utopia/controller/rewrite.rb
270
294
  - lib/utopia/controller/variables.rb
@@ -274,20 +298,23 @@ files:
274
298
  - lib/utopia/extensions/array_split.rb
275
299
  - lib/utopia/extensions/date_comparisons.rb
276
300
  - lib/utopia/http.rb
277
- - lib/utopia/locale.rb
301
+ - lib/utopia/import_map.rb
278
302
  - lib/utopia/localization.rb
303
+ - lib/utopia/localization/middleware.rb
304
+ - lib/utopia/localization/wrapper.rb
279
305
  - lib/utopia/middleware.rb
280
306
  - lib/utopia/path.rb
281
307
  - lib/utopia/path/matcher.rb
282
308
  - lib/utopia/redirection.rb
283
- - lib/utopia/responder.rb
284
309
  - lib/utopia/session.rb
285
310
  - lib/utopia/session/lazy_hash.rb
311
+ - lib/utopia/session/middleware.rb
286
312
  - lib/utopia/session/serialization.rb
287
313
  - lib/utopia/setup.rb
288
314
  - lib/utopia/shell.rb
289
315
  - lib/utopia/static.rb
290
316
  - lib/utopia/static/local_file.rb
317
+ - lib/utopia/static/middleware.rb
291
318
  - lib/utopia/static/mime_types.rb
292
319
  - lib/utopia/version.rb
293
320
  - license.md
@@ -339,7 +366,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
339
366
  - !ruby/object:Gem::Version
340
367
  version: '0'
341
368
  requirements: []
342
- rubygems_version: 3.6.9
369
+ rubygems_version: 3.7.2
343
370
  specification_version: 4
344
371
  summary: Utopia is a framework for building dynamic content-driven websites.
345
372
  test_files: []
metadata.gz.sig CHANGED
Binary file
data/lib/utopia/locale.rb DELETED
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2015-2025, by Samuel Williams.
5
-
6
- module Utopia
7
- # A structured representation of locale based on RFC3066.
8
- Locale = Struct.new(:language, :country, :variant) do
9
- def to_s
10
- to_a.compact.join("-")
11
- end
12
-
13
- def self.dump(instance)
14
- if instance
15
- instance.to_s
16
- end
17
- end
18
-
19
- def self.load(instance)
20
- if instance.is_a? String
21
- self.new(*instance.split("-", 3))
22
- elsif instance.is_a? Array
23
- return self.new(*instance)
24
- elsif instance.is_a? self
25
- return instance.frozen? ? instance : instance.dup
26
- end
27
- end
28
- end
29
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2020-2025, by Samuel Williams.
5
-
6
- require_relative "middleware"
7
-
8
- module Utopia
9
- class Responder
10
- Handler = Struct.new(:content_type, :block) do
11
- def split(*arguments)
12
- self.content_type.split(*arguments)
13
- end
14
-
15
- def call(context, request, media_range, *arguments, **options)
16
- context.instance_exec(media_range, *arguments, **options, &self.block)
17
- end
18
- end
19
-
20
- Responds = Struct.new(:responder, :context, :request) do
21
- # @todo Refactor `object` -> `*arguments`...
22
- def with(object, **options)
23
- responder.call(context, request, object, **options)
24
- end
25
- end
26
-
27
- def initialize
28
- @handlers = HTTP::Accept::MediaTypes::Map.new
29
- end
30
-
31
- attr :handlers
32
-
33
- def freeze
34
- @handlers.freeze
35
-
36
- super
37
- end
38
-
39
- def call(context, request, *arguments, **options)
40
- # Parse the list of browser preferred content types and return ordered by priority:
41
- media_types = HTTP::Accept::MediaTypes.browser_preferred_media_types(request.env)
42
-
43
- handler, media_range = @handlers.for(media_types)
44
-
45
- if handler
46
- handler.call(context, request, media_range, *arguments, **options)
47
- end
48
- end
49
-
50
- # Add a converter for the specified content type. Call the block with the response content if the request accepts the specified content_type.
51
- def handle(content_type, &block)
52
- @handlers << Handler.new(content_type, block)
53
- end
54
-
55
- def respond_to(context, request)
56
- Responds.new(self, context, request)
57
- end
58
- end
59
- end