brut 0.0.13 → 0.0.20

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -6
  3. data/brut.gemspec +1 -3
  4. data/lib/brut/back_end.rb +7 -0
  5. data/lib/brut/cli/apps/scaffold.rb +16 -24
  6. data/lib/brut/framework/config.rb +4 -43
  7. data/lib/brut/framework/mcp.rb +1 -1
  8. data/lib/brut/front_end/asset_path_resolver.rb +15 -0
  9. data/lib/brut/front_end/component.rb +66 -234
  10. data/lib/brut/front_end/components/constraint_violations.rb +9 -9
  11. data/lib/brut/front_end/components/form_tag.rb +16 -28
  12. data/lib/brut/front_end/components/i18n_translations.rb +12 -13
  13. data/lib/brut/front_end/components/input.rb +0 -1
  14. data/lib/brut/front_end/components/inputs/csrf_token.rb +2 -2
  15. data/lib/brut/front_end/components/inputs/radio_button.rb +6 -6
  16. data/lib/brut/front_end/components/inputs/select.rb +13 -20
  17. data/lib/brut/front_end/components/inputs/text_field.rb +18 -33
  18. data/lib/brut/front_end/components/inputs/textarea.rb +11 -26
  19. data/lib/brut/front_end/components/locale_detection.rb +2 -2
  20. data/lib/brut/front_end/components/page_identifier.rb +3 -5
  21. data/lib/brut/front_end/components/{time.rb → time_tag.rb} +13 -10
  22. data/lib/brut/front_end/components/traceparent.rb +5 -6
  23. data/lib/brut/front_end/http_method.rb +4 -0
  24. data/lib/brut/front_end/inline_svg_locator.rb +21 -0
  25. data/lib/brut/front_end/layout.rb +3 -0
  26. data/lib/brut/front_end/page.rb +16 -29
  27. data/lib/brut/front_end/request_context.rb +13 -0
  28. data/lib/brut/front_end/routing.rb +3 -2
  29. data/lib/brut/front_end.rb +41 -0
  30. data/lib/brut/i18n/base_methods.rb +14 -8
  31. data/lib/brut/i18n/for_back_end.rb +5 -0
  32. data/lib/brut/i18n/for_cli.rb +2 -1
  33. data/lib/brut/i18n/for_html.rb +9 -1
  34. data/lib/brut/i18n.rb +1 -0
  35. data/lib/brut/sinatra_helpers.rb +12 -7
  36. data/lib/brut/spec_support/component_support.rb +9 -9
  37. data/lib/brut/spec_support/e2e_support.rb +4 -0
  38. data/lib/brut/spec_support/matchers/have_i18n_string.rb +5 -0
  39. data/lib/brut/spec_support/rspec_setup.rb +1 -0
  40. data/lib/brut/spec_support.rb +4 -3
  41. data/lib/brut/version.rb +1 -1
  42. data/lib/brut.rb +2 -46
  43. metadata +13 -41
  44. data/lib/brut/front_end/template.rb +0 -47
  45. data/lib/brut/front_end/templates/block_filter.rb +0 -61
  46. data/lib/brut/front_end/templates/erb_engine.rb +0 -26
  47. data/lib/brut/front_end/templates/erb_parser.rb +0 -84
  48. data/lib/brut/front_end/templates/escapable_filter.rb +0 -20
  49. data/lib/brut/front_end/templates/html_safe_string.rb +0 -68
  50. data/lib/brut/front_end/templates/locator.rb +0 -60
@@ -8,6 +8,19 @@
8
8
  # and create an initializer for it that accepts the `clock:` keyword argument, the managed instance of {Clock} will be passed into it
9
9
  # when Brut creates an instance of the class.
10
10
  class Brut::FrontEnd::RequestContext
11
+
12
+ def self.current
13
+ Thread.current.thread_variable_get(:request_context)
14
+ end
15
+
16
+ # Create an instance of klass injected with the request context.
17
+ def self.inject(klass, request_params: nil)
18
+ self.current.then { |request_context|
19
+ request_context.as_constructor_args(klass,request_params:)
20
+ }.then { |constructor_args|
21
+ klass.new(**constructor_args)
22
+ }
23
+ end
11
24
  # Create a new RequestContext based on some of the information provided by Rack
12
25
  #
13
26
  # @param [Hash] env the Rack `env` object, as available to any middleware
@@ -1,4 +1,5 @@
1
1
  require "uri"
2
+ require "phlex"
2
3
 
3
4
  # Holds the registered routes for this app.
4
5
  class Brut::FrontEnd::Routing
@@ -218,11 +219,11 @@ private
218
219
  end
219
220
  uri = URI(joined_path)
220
221
  uri.query = URI.encode_www_form(query_string_params)
221
- uri
222
+ uri.extend(Phlex::SGML::SafeObject)
222
223
  end
223
224
 
224
225
  def url(**query_string_params)
225
- request_context = Thread.current.thread_variable_get(:request_context)
226
+ request_context = Brut::FrontEnd::RequestContext.current
226
227
  path = self.path(**query_string_params)
227
228
  host = if request_context
228
229
  request_context[:host]
@@ -0,0 +1,41 @@
1
+ # In Brut, the _front end_ is considered anything that interacts directly with a web browser or HTTP. This includes rendering HTML,
2
+ # managing JavaScript and CSS, and processing form submissions. It contrasts to {Brut::BackEnd}, which handles the business logic
3
+ # and database.
4
+ #
5
+ # You {Brut::App} defines pages, forms, and actions. A page is backed by a subclass of {Brut::FrontEnd::Page}, which provides
6
+ # dynamic data for rendering. A page can reference {Brut::FrontEnd::Component} subclasses to allow functional decomposition of front
7
+ # end logic and markup, as well as re-use. Both pages and components have ERB files that describe the HTML to be rendered.
8
+ #
9
+ # A {Brut::FrontEnd::Form} subclass defines a form that a browser will submit to your app. That
10
+ # submission is processed by a {Brut::FrontEnd::Handler} subclass. Handlers can also respond to other HTTP requests.
11
+ #
12
+ # In addition to responding to requests, you can subclass {Brut::FrontEnd::RouteHook} or {Brut::FrontEnd::Middleware} to perform
13
+ # further manipulation of the request.
14
+ #
15
+ # The entire front-end is based on Rack, so you should be able to achieve anything you need to.
16
+ module Brut::FrontEnd
17
+ autoload(:AssetMetadata, "brut/front_end/asset_metadata")
18
+ autoload(:AssetPathResolver, "brut/front_end/asset_path_resolver")
19
+ autoload(:Component, "brut/front_end/component")
20
+ autoload(:Components, "brut/front_end/component")
21
+ autoload(:Download, "brut/front_end/download")
22
+ autoload(:Flash, "brut/front_end/flash")
23
+ autoload(:Form, "brut/front_end/form")
24
+ autoload(:GenericResponse, "brut/front_end/generic_response")
25
+ autoload(:Handler, "brut/front_end/handler")
26
+ autoload(:Handlers, "brut/front_end/handler")
27
+ autoload(:HandlingResults, "brut/front_end/handling_results")
28
+ autoload(:HttpMethod, "brut/front_end/http_method")
29
+ autoload(:HttpStatus, "brut/front_end/http_status")
30
+ autoload(:InlineSvgLocator, "brut/front_end/inline_svg_locator")
31
+ autoload(:Layout, "brut/front_end/layout")
32
+ autoload(:Middleware, "brut/front_end/middleware")
33
+ autoload(:Middlewares, "brut/front_end/middleware")
34
+ autoload(:Page, "brut/front_end/page")
35
+ autoload(:Pages, "brut/front_end/page")
36
+ autoload(:RequestContext, "brut/front_end/request_context")
37
+ autoload(:RouteHook, "brut/front_end/route_hook")
38
+ autoload(:RouteHooks, "brut/front_end/route_hook")
39
+ autoload(:Routing, "brut/front_end/routing")
40
+ autoload(:Session, "brut/front_end/session")
41
+ end
@@ -1,6 +1,11 @@
1
- # Interface for translations. This is prefered over using Ruby's I18n directly.
2
- # This is intended to be mixed-in to any class that requires this, so that you can more
3
- # expediently access the `t` method.
1
+ # Interface for translations, preferred over Ruby's I18n classes. Note that this is a
2
+ # base module and not intended to be directly used in your classes. Include one of
3
+ # the other modules in this namespace:
4
+ #
5
+ # * {Brut::I18n::ForHTML} for components or pages, or anything use Phlex
6
+ # * {Brut::I18n::ForCLI} for CLI apps
7
+ # * {Brut::I18n::ForBackEnd} for back-end classes that aren't generating HTML
8
+ #
4
9
  module Brut::I18n::BaseMethods
5
10
 
6
11
  # Access a translation and insert interpolated elemens as needed. This will use the provided key to determine
@@ -101,7 +106,7 @@ module Brut::I18n::BaseMethods
101
106
  # }
102
107
  # # in your code for HomePage
103
108
  # t(page: [ :captions, :new ]) # => New Widgets
104
- def t(key=:look_in_rest,**rest)
109
+ def t(key=:look_in_rest,**rest,&block)
105
110
  if key == :look_in_rest
106
111
 
107
112
  page = rest.delete(:page)
@@ -126,13 +131,14 @@ module Brut::I18n::BaseMethods
126
131
  key = Array(key).join('.')
127
132
  key = [key,"general.#{key}"]
128
133
  end
129
- if block_given?
134
+ if !block.nil?
130
135
  if rest[:block]
131
136
  raise ArgumentError,"t was given a block and a block: param. You can't do both "
132
137
  end
133
- rest[:block] = html_safe(yield.to_s.strip)
138
+ block_contents = safe(capture(&block))
139
+ rest[:block] = block_contents
134
140
  end
135
- html_safe(t_direct(key,**rest))
141
+ t_direct(key,**rest)
136
142
  rescue I18n::MissingInterpolationArgument => ex
137
143
  if ex.key.to_s == "block"
138
144
  raise ArgumentError,"One of the keys #{key.join(", ")} contained a %{block} interpolation value: '#{ex.string}'. This means you must use t_html *and* yield a block to it"
@@ -168,7 +174,7 @@ module Brut::I18n::BaseMethods
168
174
  }
169
175
  escaped_interpolated_values = interpolated_values.map { |key,value|
170
176
  if value.kind_of?(String)
171
- [ key, Brut::FrontEnd::Template.escape_html(value) ]
177
+ [ key, CGI.escapeHTML(value) ]
172
178
  else
173
179
  [ key, value ]
174
180
  end
@@ -0,0 +1,5 @@
1
+ module Brut::I18n::ForBackEnd
2
+ include Brut::I18n::BaseMethods
3
+ def safe(string) = string
4
+ def capture(&block) = block.()
5
+ end
@@ -1,4 +1,5 @@
1
1
  module Brut::I18n::ForCLI
2
2
  include Brut::I18n::BaseMethods
3
- def html_safe(string) = string
3
+ def safe(string) = string
4
+ def capture(&block) = block.()
4
5
  end
@@ -1,4 +1,12 @@
1
+ # I18n for components or pages, which are assumed to be Phlex components.
2
+ # To use this outside of a Phlex context, you must define these two
3
+ # methods to ensure proper HTML escaping happens:
4
+ #
5
+ # * `safe` to accept a string and return a string.
6
+ # * `capture` to accept a block and return its contents as a string.
1
7
  module Brut::I18n::ForHTML
2
8
  include Brut::I18n::BaseMethods
3
- def html_safe(string) = Brut::FrontEnd::Templates::HTMLSafeString.from_string(string)
9
+ def t(...)
10
+ safe(super)
11
+ end
4
12
  end
data/lib/brut/i18n.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # I18n holds all the code useful for translating and localizing information. It's based on Ruby's I18n.
2
2
  module Brut::I18n
3
3
  autoload(:BaseMethods, "brut/i18n/base_methods")
4
+ autoload(:ForBackEnd, "brut/i18n/for_back_end")
4
5
  autoload(:ForCLI, "brut/i18n/for_cli")
5
6
  autoload(:ForHTML, "brut/i18n/for_html")
6
7
  autoload(:HTTPAcceptLanguage, "brut/i18n/http_accept_language")
@@ -77,15 +77,16 @@ module Brut::SinatraHelpers
77
77
 
78
78
  Brut.container.instrumentation.span(page_class.name) do |span|
79
79
  span.add_prefixed_attributes("brut", type: :page, class: page_class)
80
- request_context = Thread.current.thread_variable_get(:request_context)
81
- constructor_args = request_context.as_constructor_args(
80
+ constructor_args = Brut::FrontEnd::RequestContext.current.as_constructor_args(
82
81
  page_class,
83
82
  request_params: params,
84
83
  route: brut_route,
85
84
  )
86
85
  span.add_prefixed_attributes("brut.initializer.args", constructor_args.map { |k,v| [k.to_s,v.class.name] }.to_h)
87
86
  page_instance = page_class.new(**constructor_args)
87
+
88
88
  result = page_instance.handle!
89
+
89
90
  span.add_prefixed_attributes("brut", result_class: result.class)
90
91
  case result
91
92
  in URI => uri
@@ -177,7 +178,6 @@ module Brut::SinatraHelpers
177
178
  form_class: form_class,
178
179
  )
179
180
 
180
- request_context = Thread.current.thread_variable_get(:request_context)
181
181
  handler = handler_class.new
182
182
  form = if form_class.nil?
183
183
  nil
@@ -185,7 +185,7 @@ module Brut::SinatraHelpers
185
185
  form_class.new(params: params)
186
186
  end
187
187
 
188
- process_args = request_context.as_method_args(handler,:handle,request_params: params,form: form,route:brut_route)
188
+ process_args = Brut::FrontEnd::RequestContext.current.as_method_args(handler,:handle,request_params: params,form: form,route:brut_route)
189
189
 
190
190
  result = handler.handle!(**process_args)
191
191
 
@@ -193,12 +193,17 @@ module Brut::SinatraHelpers
193
193
  in URI => uri
194
194
  redirect to(uri.to_s)
195
195
  in Brut::FrontEnd::Component => component_instance
196
- render_html(component_instance).to_s
197
- in [ Brut::FrontEnd::Component => component_instance, Brut::FrontEnd::HttpStatus => http_status ]
196
+ component_instance.call.to_s
197
+ in [
198
+ Brut::FrontEnd::Component => component_instance,
199
+ Brut::FrontEnd::HttpStatus => http_status,
200
+ ]
201
+
198
202
  [
199
203
  http_status.to_i,
200
- render_html(component_instance).to_s,
204
+ component_instance.call.to_s,
201
205
  ]
206
+
202
207
  in Brut::FrontEnd::HttpStatus => http_status
203
208
  http_status.to_i
204
209
  in Brut::FrontEnd::Download => download
@@ -8,7 +8,7 @@ module Brut::SpecSupport::ComponentSupport
8
8
  include Brut::SpecSupport::FlashSupport
9
9
  include Brut::SpecSupport::SessionSupport
10
10
  include Brut::SpecSupport::ClockSupport
11
- include Brut::I18n::ForHTML
11
+ include Brut::I18n::BaseMethods
12
12
 
13
13
  # Render a component into its text representation. This mimics what happens when a component is used
14
14
  # inside a template. You typically don't want this, but should use {#render_and_parse}, since that will
@@ -20,8 +20,13 @@ module Brut::SpecSupport::ComponentSupport
20
20
  end
21
21
  component.handle!
22
22
  else
23
- component.yielded_block = block
24
- component.render
23
+ if block.nil?
24
+ component.call
25
+ else
26
+ component.call do
27
+ component.raw(component.safe(block.()))
28
+ end
29
+ end
25
30
  end
26
31
  end
27
32
 
@@ -41,7 +46,7 @@ module Brut::SpecSupport::ComponentSupport
41
46
  # @return [Brut::SpecSupport::EnhancedNode] a wrapper around a Nokogiri node to provide convienience methods.
42
47
  def render_and_parse(component,&block)
43
48
  rendered_text = render(component,&block)
44
- if !rendered_text.kind_of?(String) && !rendered_text.kind_of?(Brut::FrontEnd::Templates::HTMLSafeString)
49
+ if !rendered_text.kind_of?(String)
45
50
  if rendered_text.kind_of?(URI::Generic)
46
51
  raise "#{component.class} redirected to #{rendered_text} instead of rendering"
47
52
  else
@@ -80,9 +85,4 @@ module Brut::SpecSupport::ComponentSupport
80
85
  def routing_for(klass,**args)
81
86
  Brut.container.routing.path(klass,**args)
82
87
  end
83
-
84
- # Escape HTML using the same code Brut uses for rendering templates.
85
- def escape_html(...)
86
- Brut::FrontEnd::Templates::EscapableFilter.escape_html(...)
87
- end
88
88
  end
@@ -0,0 +1,4 @@
1
+ # Convienience methods for writing E2E tests
2
+ module Brut::SpecSupport::E2eSupport
3
+ include Brut::I18n::BaseMethods
4
+ end
@@ -1,5 +1,10 @@
1
1
  RSpec::Matchers.define :have_i18n_string do |key,**args|
2
2
  include Brut::I18n::ForHTML
3
+
4
+ # XXX: Figure out how to not have to do this
5
+ def safe(x) = x
6
+ def capture(&block) = block.()
7
+
3
8
  match do |nokogiri_node|
4
9
 
5
10
  text = nokogiri_node.text.strip
@@ -80,6 +80,7 @@ class Brut::SpecSupport::RSpecSetup
80
80
  @config.include Brut::SpecSupport::GeneralSupport
81
81
  @config.include Brut::SpecSupport::ComponentSupport, component: true
82
82
  @config.include Brut::SpecSupport::HandlerSupport, handler: true
83
+ @config.include Brut::SpecSupport::E2eSupport, e2e: true
83
84
  @config.include Playwright::Test::Matchers, e2e: true
84
85
 
85
86
  @config.around do |example|
@@ -5,11 +5,12 @@ module Brut
5
5
  module SpecSupport
6
6
  end
7
7
  end
8
- require_relative "spec_support/matcher"
9
8
  require_relative "spec_support/component_support"
10
- require_relative "spec_support/handler_support"
11
- require_relative "spec_support/general_support"
9
+ require_relative "spec_support/e2e_support"
12
10
  require_relative "spec_support/e2e_test_server"
11
+ require_relative "spec_support/general_support"
12
+ require_relative "spec_support/handler_support"
13
+ require_relative "spec_support/matcher"
13
14
  require_relative "spec_support/rspec_setup"
14
15
  require_relative "factory_bot"
15
16
  # Convention here is different. We don't want to autoload
data/lib/brut/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Brut
2
2
  # @!visibility private
3
- VERSION = "0.0.13"
3
+ VERSION = "0.0.20"
4
4
  end
data/lib/brut.rb CHANGED
@@ -9,52 +9,8 @@ require_relative "brut/framework"
9
9
  #
10
10
  # Have fun!
11
11
  module Brut
12
- # In Brut, the _front end_ is considered anything that interacts directly with a web browser or HTTP. This includes rendering HTML,
13
- # managing JavaScript and CSS, and processing form submissions. It contrasts to {Brut::BackEnd}, which handles the business logic
14
- # and database.
15
- #
16
- # You {Brut::App} defines pages, forms, and actions. A page is backed by a subclass of {Brut::FrontEnd::Page}, which provides
17
- # dynamic data for rendering. A page can reference {Brut::FrontEnd::Component} subclasses to allow functional decomposition of front
18
- # end logic and markup, as well as re-use. Both pages and components have ERB files that describe the HTML to be rendered.
19
- #
20
- # A {Brut::FrontEnd::Form} subclass defines a form that a browser will submit to your app. That
21
- # submission is processed by a {Brut::FrontEnd::Handler} subclass. Handlers can also respond to other HTTP requests.
22
- #
23
- # In addition to responding to requests, you can subclass {Brut::FrontEnd::RouteHook} or {Brut::FrontEnd::Middleware} to perform
24
- # further manipulation of the request.
25
- #
26
- # The entire front-end is based on Rack, so you should be able to achieve anything you need to.
27
- module FrontEnd
28
- autoload(:AssetMetadata, "brut/front_end/asset_metadata")
29
- autoload(:Component, "brut/front_end/component")
30
- autoload(:Components, "brut/front_end/component")
31
- autoload(:Download, "brut/front_end/download")
32
- autoload(:Flash, "brut/front_end/flash")
33
- autoload(:Form, "brut/front_end/form")
34
- autoload(:Handler, "brut/front_end/handler")
35
- autoload(:Handlers, "brut/front_end/handler")
36
- autoload(:HandlingResults, "brut/front_end/handling_results")
37
- autoload(:HttpMethod, "brut/front_end/http_method")
38
- autoload(:HttpStatus, "brut/front_end/http_status")
39
- autoload(:GenericResponse, "brut/front_end/generic_response")
40
- autoload(:Middleware, "brut/front_end/middleware")
41
- autoload(:Middlewares, "brut/front_end/middleware")
42
- autoload(:Page, "brut/front_end/page")
43
- autoload(:Pages, "brut/front_end/page")
44
- autoload(:RequestContext, "brut/front_end/request_context")
45
- autoload(:RouteHook, "brut/front_end/route_hook")
46
- autoload(:RouteHooks, "brut/front_end/route_hook")
47
- autoload(:Routing, "brut/front_end/routing")
48
- autoload(:Session, "brut/front_end/session")
49
- end
50
- # The _back end_ of a Brut app is where your app's business logic and database are managed. While the bulk of your Brut app's code
51
- # will be in the back end, Brut is far less prescriptive about how to manage that than it is the front end.
52
- module BackEnd
53
- autoload(:Validators, "brut/back_end/validator")
54
- autoload(:Sidekiq, "brut/back_end/sidekiq")
55
- # Do not put SeedData here - it must be loaded only when needed
56
- end
57
- # I18n is where internationalization and localization support lives.
12
+ autoload(:FrontEnd, "brut/front_end")
13
+ autoload(:BackEnd, "brut/back_end")
58
14
  autoload(:I18n, "brut/i18n")
59
15
  autoload(:Instrumentation,"brut/instrumentation")
60
16
  autoload(:SinatraHelpers, "brut/sinatra_helpers")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Bryant Copeland
@@ -80,7 +80,7 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0'
82
82
  - !ruby/object:Gem::Dependency
83
- name: prism
83
+ name: phlex
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - ">="
@@ -94,7 +94,7 @@ dependencies:
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  - !ruby/object:Gem::Dependency
97
- name: rack-protection
97
+ name: prism
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - ">="
@@ -108,7 +108,7 @@ dependencies:
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
110
  - !ruby/object:Gem::Dependency
111
- name: rackup
111
+ name: rack-protection
112
112
  requirement: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - ">="
@@ -122,7 +122,7 @@ dependencies:
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
124
  - !ruby/object:Gem::Dependency
125
- name: rexml
125
+ name: rackup
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - ">="
@@ -177,34 +177,6 @@ dependencies:
177
177
  - - ">="
178
178
  - !ruby/object:Gem::Version
179
179
  version: '0'
180
- - !ruby/object:Gem::Dependency
181
- name: temple
182
- requirement: !ruby/object:Gem::Requirement
183
- requirements:
184
- - - ">="
185
- - !ruby/object:Gem::Version
186
- version: '0'
187
- type: :runtime
188
- prerelease: false
189
- version_requirements: !ruby/object:Gem::Requirement
190
- requirements:
191
- - - ">="
192
- - !ruby/object:Gem::Version
193
- version: '0'
194
- - !ruby/object:Gem::Dependency
195
- name: tilt
196
- requirement: !ruby/object:Gem::Requirement
197
- requirements:
198
- - - ">="
199
- - !ruby/object:Gem::Version
200
- version: '0'
201
- type: :runtime
202
- prerelease: false
203
- version_requirements: !ruby/object:Gem::Requirement
204
- requirements:
205
- - - ">="
206
- - !ruby/object:Gem::Version
207
- version: '0'
208
180
  - !ruby/object:Gem::Dependency
209
181
  name: tzinfo
210
182
  requirement: !ruby/object:Gem::Requirement
@@ -454,6 +426,7 @@ files:
454
426
  - dx/start
455
427
  - dx/stop
456
428
  - lib/brut.rb
429
+ - lib/brut/back_end.rb
457
430
  - lib/brut/back_end/seed_data.rb
458
431
  - lib/brut/back_end/sidekiq.rb
459
432
  - lib/brut/back_end/sidekiq/middlewares.rb
@@ -491,7 +464,9 @@ files:
491
464
  - lib/brut/framework/mcp.rb
492
465
  - lib/brut/framework/patch_semantic_logger.rb
493
466
  - lib/brut/framework/project_environment.rb
467
+ - lib/brut/front_end.rb
494
468
  - lib/brut/front_end/asset_metadata.rb
469
+ - lib/brut/front_end/asset_path_resolver.rb
495
470
  - lib/brut/front_end/component.rb
496
471
  - lib/brut/front_end/components/constraint_violations.rb
497
472
  - lib/brut/front_end/components/form_tag.rb
@@ -504,7 +479,7 @@ files:
504
479
  - lib/brut/front_end/components/inputs/textarea.rb
505
480
  - lib/brut/front_end/components/locale_detection.rb
506
481
  - lib/brut/front_end/components/page_identifier.rb
507
- - lib/brut/front_end/components/time.rb
482
+ - lib/brut/front_end/components/time_tag.rb
508
483
  - lib/brut/front_end/components/traceparent.rb
509
484
  - lib/brut/front_end/download.rb
510
485
  - lib/brut/front_end/flash.rb
@@ -527,6 +502,8 @@ files:
527
502
  - lib/brut/front_end/handling_results.rb
528
503
  - lib/brut/front_end/http_method.rb
529
504
  - lib/brut/front_end/http_status.rb
505
+ - lib/brut/front_end/inline_svg_locator.rb
506
+ - lib/brut/front_end/layout.rb
530
507
  - lib/brut/front_end/layouts/_internal.html.erb
531
508
  - lib/brut/front_end/middleware.rb
532
509
  - lib/brut/front_end/middlewares/annotate_brut_owned_paths.rb
@@ -545,15 +522,9 @@ files:
545
522
  - lib/brut/front_end/route_hooks/setup_request_context.rb
546
523
  - lib/brut/front_end/routing.rb
547
524
  - lib/brut/front_end/session.rb
548
- - lib/brut/front_end/template.rb
549
- - lib/brut/front_end/templates/block_filter.rb
550
- - lib/brut/front_end/templates/erb_engine.rb
551
- - lib/brut/front_end/templates/erb_parser.rb
552
- - lib/brut/front_end/templates/escapable_filter.rb
553
- - lib/brut/front_end/templates/html_safe_string.rb
554
- - lib/brut/front_end/templates/locator.rb
555
525
  - lib/brut/i18n.rb
556
526
  - lib/brut/i18n/base_methods.rb
527
+ - lib/brut/i18n/for_back_end.rb
557
528
  - lib/brut/i18n/for_cli.rb
558
529
  - lib/brut/i18n/for_html.rb
559
530
  - lib/brut/i18n/http_accept_language.rb
@@ -565,6 +536,7 @@ files:
565
536
  - lib/brut/spec_support.rb
566
537
  - lib/brut/spec_support/clock_support.rb
567
538
  - lib/brut/spec_support/component_support.rb
539
+ - lib/brut/spec_support/e2e_support.rb
568
540
  - lib/brut/spec_support/e2e_test_server.rb
569
541
  - lib/brut/spec_support/enhanced_node.rb
570
542
  - lib/brut/spec_support/flash_support.rb
@@ -1,47 +0,0 @@
1
- require "temple"
2
-
3
- # Holds code related to rendering ERB templates
4
- module Brut::FrontEnd::Templates
5
- autoload(:HTMLSafeString,"brut/front_end/templates/html_safe_string")
6
- autoload(:ERBParser,"brut/front_end/templates/erb_parser")
7
- autoload(:EscapableFilter,"brut/front_end/templates/escapable_filter")
8
- autoload(:BlockFilter,"brut/front_end/templates/block_filter")
9
- autoload(:ERBEngine,"brut/front_end/templates/erb_engine")
10
- autoload(:Locator,"brut/front_end/templates/locator")
11
- end
12
-
13
- # Handles rendering HTML templates written in ERB. This is a light wrapper around `Tilt`.
14
- # This also configured a few customizations to allow a Rails-like rendering of ERB:
15
- #
16
- # * HTML escaping by default
17
- # * Helpers that return {Brut::FrontEnd::Templates::HTMLSafeString}s won't be escaped
18
- #
19
- # @see https://github.com/rtomayko/tilt
20
- class Brut::FrontEnd::Template
21
-
22
- # @!visibility private
23
- # This sets up global state somewhere, even though we aren't using `TempleTemplate`
24
- # anywhere.
25
- TempleTemplate = Temple::Templates::Tilt(Brut::FrontEnd::Templates::ERBEngine,
26
- register_as: "html.erb")
27
-
28
- attr_reader :template_file_path
29
-
30
- # Wraps a string that is deemed safe to insert into
31
- # HTML without escaping it. This allows stuff like
32
- # <%= component(SomeComponent) %> to work without
33
- # having to remember to <%== all the time.
34
- def initialize(template_file_path)
35
- @template_file_path = template_file_path
36
- @tilt_template = Tilt.new(@template_file_path)
37
- end
38
-
39
- def render_template(...)
40
- @tilt_template.render(...)
41
- end
42
-
43
- # Convienience method to escape HTML in the canonical way.
44
- def self.escape_html(string)
45
- Brut::FrontEnd::Templates::EscapableFilter.escape_html(string)
46
- end
47
- end
@@ -1,61 +0,0 @@
1
- # Allows rendering blocks in ERB the way Rails' helpers like `form_with` do.
2
- # This is a slightly modified copy of Hanami's `Filters::Block`.
3
- #
4
- # @see https://github.com/hanami/view/blob/main/lib/hanami/view/erb/filters/block.rb
5
- class Brut::FrontEnd::Templates::BlockFilter < Temple::Filter
6
- END_LINE_RE = /\bend\b/
7
-
8
- # @!visibility private
9
- def on_erb_block(escape, code, content)
10
- tmp = unique_name
11
-
12
- # Remove the last `end` :code sexp, since this is technically "outside" the block
13
- # contents, which we want to capture separately below. This `end` is added back after
14
- # capturing the content below.
15
- case content.last
16
- in [:code, c] if c =~ END_LINE_RE
17
- content.pop
18
- end
19
-
20
- [:multi,
21
- # Capture the result of the code in a variable. We can't do `[:dynamic, code]` because
22
- # it's probably not a complete expression (which is a requirement for Temple).
23
- # DBC: an example is that 'code' might be "form_for do" which is not an expression.
24
- # Because we later put an "end" in, the result will be
25
- #
26
- # some_var = helper do
27
- # end
28
- #
29
- # Which IS valid Ruby.
30
- [:code, "#{tmp} = #{code}"],
31
- # Capture the content of a block in a separate buffer. This means that `yield` will
32
- # not output the content to the current buffer, but rather return the output.
33
- [:capture, unique_name, compile(content)],
34
- [:code, "end"],
35
- # Output the content, without escaping it.
36
- # Hanami has this ↴
37
- # [:escape, escape, [:dynamic, tmp]]
38
- [:escape, escape, [:dynamic, Brut::FrontEnd::Templates.name + "::HTMLSafeString.new(#{tmp})"]]
39
- ]
40
-
41
- # Details explaining the change:
42
- #
43
- # The sexps for template are quite convoluted and highly dynamic, so it is hard
44
- # to understand exactly what effect they will have. Basically, what this [:multi thing is
45
- # doing is to capture the result of the block in a variable:
46
- #
47
- # some_var = form_for(args) do
48
- #
49
- # It then captures the inside of the block in a new variable:
50
- #
51
- # some_other_var = «whatever was inside that `do`»
52
- #
53
- # And follows it with an end.
54
- #
55
- # The first variable—some_var—now holds the return value of the helper, form_for in this case. To
56
- # output this content to the actual view, it must be dereferenced, thus [ :dynamic, "some_var" ].
57
- #
58
- # We are going to treat the return value of the block helper as HTML safe. Thus, we'll wrap it
59
- # with HTMLSafeString.new(…).
60
- end
61
- end
@@ -1,26 +0,0 @@
1
- # A temple "engine" that can be used to parse ERB and generate HTML
2
- # in just the way we need.
3
- class Brut::FrontEnd::Templates::ERBEngine < Temple::Engine
4
- # Parse the ERB into sexps
5
- use Brut::FrontEnd::Templates::ERBParser
6
-
7
- # Handle block syntax used in a <%=
8
- use Brut::FrontEnd::Templates::BlockFilter
9
-
10
- # Trim whitespace like ERB does
11
- use Temple::ERB::Trimming
12
-
13
- # Escape strings only if they are not HTMLSafeString
14
- use Brut::FrontEnd::Templates::EscapableFilter
15
- # This filter actually runs the Ruby code
16
- use Temple::Filters::StaticAnalyzer
17
- # Flattens nested :multi expressions which I'm not sure is needed, but
18
- # have cargo-culted from hanami
19
- use Temple::Filters::MultiFlattener
20
- # merges sequential :static, which again, not sure is needed, but
21
- # have cargo-culted from hanami
22
- use Temple::Filters::StaticMerger
23
-
24
- # This generates everything into a string
25
- use Temple::Generators::ArrayBuffer
26
- end