brut 0.0.12 → 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.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -6
- data/brut.gemspec +1 -3
- data/lib/brut/back_end.rb +7 -0
- data/lib/brut/cli/apps/scaffold.rb +16 -24
- data/lib/brut/framework/config.rb +4 -43
- data/lib/brut/framework/mcp.rb +1 -1
- data/lib/brut/front_end/asset_path_resolver.rb +15 -0
- data/lib/brut/front_end/component.rb +66 -234
- data/lib/brut/front_end/components/constraint_violations.rb +9 -9
- data/lib/brut/front_end/components/form_tag.rb +16 -28
- data/lib/brut/front_end/components/i18n_translations.rb +12 -13
- data/lib/brut/front_end/components/input.rb +0 -1
- data/lib/brut/front_end/components/inputs/csrf_token.rb +2 -2
- data/lib/brut/front_end/components/inputs/radio_button.rb +6 -6
- data/lib/brut/front_end/components/inputs/select.rb +13 -20
- data/lib/brut/front_end/components/inputs/text_field.rb +18 -33
- data/lib/brut/front_end/components/inputs/textarea.rb +11 -26
- data/lib/brut/front_end/components/locale_detection.rb +2 -2
- data/lib/brut/front_end/components/page_identifier.rb +3 -5
- data/lib/brut/front_end/components/{time.rb → time_tag.rb} +13 -10
- data/lib/brut/front_end/components/traceparent.rb +5 -6
- data/lib/brut/front_end/generic_response.rb +33 -0
- data/lib/brut/front_end/handler.rb +2 -0
- data/lib/brut/front_end/http_method.rb +4 -0
- data/lib/brut/front_end/inline_svg_locator.rb +21 -0
- data/lib/brut/front_end/layout.rb +3 -0
- data/lib/brut/front_end/page.rb +16 -29
- data/lib/brut/front_end/request_context.rb +15 -0
- data/lib/brut/front_end/routing.rb +3 -2
- data/lib/brut/front_end.rb +41 -0
- data/lib/brut/i18n/base_methods.rb +14 -8
- data/lib/brut/i18n/for_back_end.rb +5 -0
- data/lib/brut/i18n/for_cli.rb +2 -1
- data/lib/brut/i18n/for_html.rb +9 -1
- data/lib/brut/i18n.rb +1 -0
- data/lib/brut/sinatra_helpers.rb +14 -7
- data/lib/brut/spec_support/component_support.rb +9 -9
- data/lib/brut/spec_support/e2e_support.rb +4 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +5 -0
- data/lib/brut/spec_support/rspec_setup.rb +1 -0
- data/lib/brut/spec_support.rb +4 -3
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +2 -45
- metadata +16 -43
- data/lib/brut/front_end/template.rb +0 -47
- data/lib/brut/front_end/templates/block_filter.rb +0 -61
- data/lib/brut/front_end/templates/erb_engine.rb +0 -26
- data/lib/brut/front_end/templates/erb_parser.rb +0 -84
- data/lib/brut/front_end/templates/escapable_filter.rb +0 -20
- data/lib/brut/front_end/templates/html_safe_string.rb +0 -68
- data/lib/brut/front_end/templates/locator.rb +0 -60
@@ -31,27 +31,27 @@ class Brut::FrontEnd::Components::ConstraintViolations < Brut::FrontEnd::Compone
|
|
31
31
|
@input_name = input_name
|
32
32
|
@array = !index.nil?
|
33
33
|
@index = index || 0
|
34
|
-
@html_attributes = html_attributes
|
35
|
-
@message_html_attributes = message_html_attributes
|
34
|
+
@html_attributes = html_attributes.map {|name,value| [ name.to_sym, value ] }.to_h
|
35
|
+
@message_html_attributes = message_html_attributes.map {|name,value| [ name.to_sym, value ] }.to_h
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
38
|
+
def view_template
|
39
39
|
html_attributes = {
|
40
|
-
"input-name": @array ? "#{@input_name}[]" : @input_name
|
40
|
+
"input-name": @array ? "#{@input_name}[]" : @input_name.to_s,
|
41
41
|
}.merge(@html_attributes)
|
42
42
|
|
43
43
|
message_html_attributes = {
|
44
44
|
"server-side": true,
|
45
45
|
}.merge(@message_html_attributes)
|
46
46
|
|
47
|
-
|
47
|
+
brut_cv_messages(**html_attributes) do
|
48
48
|
@form.input(@input_name, index: @index).validity_state.select { |constraint|
|
49
49
|
!constraint.client_side?
|
50
|
-
}.
|
51
|
-
|
52
|
-
t("cv.be.#{constraint}", **constraint.context)
|
50
|
+
}.each do |constraint|
|
51
|
+
brut_cv(**message_html_attributes) do
|
52
|
+
t("cv.be.#{constraint}", **constraint.context)
|
53
53
|
end
|
54
|
-
|
54
|
+
end
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require "rexml"
|
2
1
|
# Represents a `<form>` HTML element that includes a CSRF token as needed. You likely want to use this class via the {Brut::FrontEnd::Component::Helpers#form_tag} method.
|
3
2
|
class Brut::FrontEnd::Components::FormTag < Brut::FrontEnd::Component
|
4
3
|
# Creates the form surrounding the contents of the block yielded to it. If the form's action is a POST, it will include a CSRF token.
|
@@ -22,7 +21,7 @@ class Brut::FrontEnd::Components::FormTag < Brut::FrontEnd::Component
|
|
22
21
|
# @option html_attributes [Class|Brut::FrontEnd::Form] :for the form object or class representing this HTML form *or* the class of a handler the form should submit to. If you pass this, you may not pass the HTML attributes `:action` or `:method`. Both will be derived from this object.
|
23
22
|
# @option html_attributes [String] «any-other-key» attributes to set on the `<form>` tag
|
24
23
|
# @yield No parameters given. This is expected to return additional markup to appear inside the `<form>` element.
|
25
|
-
def initialize(route_params: {}, **html_attributes
|
24
|
+
def initialize(route_params: {}, **html_attributes)
|
26
25
|
form_class = html_attributes.delete(:for) # Cannot be a keyword arg, since for is a reserved word
|
27
26
|
if !form_class.nil?
|
28
27
|
if form_class.kind_of?(Brut::FrontEnd::Form)
|
@@ -49,34 +48,23 @@ class Brut::FrontEnd::Components::FormTag < Brut::FrontEnd::Component
|
|
49
48
|
|
50
49
|
@include_csrf_token = http_method.post?
|
51
50
|
@csrf_token_omit_reasoning = http_method.get? ? "because this form's action is a GET" : nil
|
52
|
-
@attributes = html_attributes
|
53
|
-
@contents = contents
|
51
|
+
@attributes = html_attributes.merge(method: http_method)
|
54
52
|
end
|
55
53
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
REXML::Attribute.new(key,value).to_string
|
54
|
+
def view_template
|
55
|
+
form(**@attributes) do
|
56
|
+
if @include_csrf_token
|
57
|
+
render Brut::FrontEnd::RequestContext.inject(Brut::FrontEnd::Components::Inputs::CsrfToken)
|
58
|
+
elsif Brut.container.project_env.development?
|
59
|
+
comment do
|
60
|
+
"CSRF Token omitted #{@csrf_token_omit_reasoning} (this message only appears in development)"
|
61
|
+
end
|
66
62
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
html_safe!("<!-- CSRF Token omitted #{@csrf_token_omit_reasoning} (this message only appears in development) -->")
|
72
|
-
else
|
73
|
-
""
|
74
|
-
end
|
75
|
-
%{
|
76
|
-
<form #{attribute_string}>
|
77
|
-
#{ csrf_token_component }
|
78
|
-
#{ @contents.() }
|
79
|
-
</form>
|
80
|
-
}
|
63
|
+
if block_given?
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
end
|
81
67
|
end
|
68
|
+
|
69
|
+
|
82
70
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "rexml"
|
2
|
-
|
3
1
|
# Produces `<brut-i18n-translation>` entries for the given values. This is used for client-side constraint violation messaging with
|
4
2
|
# JavaScript. The `<brut-constraint-violation-message>` tag uses these keys to produce messages on the client.
|
5
3
|
#
|
@@ -39,13 +37,13 @@ class Brut::FrontEnd::Components::I18nTranslations < Brut::FrontEnd::Component
|
|
39
37
|
end
|
40
38
|
|
41
39
|
# @!visibility private
|
42
|
-
def
|
40
|
+
def view_template
|
43
41
|
values = ::I18n.t(@i18n_key_root)
|
44
42
|
if values.kind_of?(String)
|
45
43
|
values = { "" => values }
|
46
44
|
end
|
47
45
|
|
48
|
-
values.
|
46
|
+
values.each do |key,value|
|
49
47
|
if !value.kind_of?(String)
|
50
48
|
raise "Key #{key} under #{@i18n_key_root} maps to a #{value.class} instead of a String. For #{self.class} to work, the value must be a String"
|
51
49
|
end
|
@@ -54,16 +52,17 @@ class Brut::FrontEnd::Components::I18nTranslations < Brut::FrontEnd::Component
|
|
54
52
|
else
|
55
53
|
"#{@i18n_key_root}.#{key}"
|
56
54
|
end
|
57
|
-
attributes =
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
attributes = {
|
56
|
+
key: i18n_key,
|
57
|
+
value: value.to_s,
|
58
|
+
}
|
61
59
|
if !Brut.container.project_env.production?
|
62
|
-
attributes
|
63
|
-
attributes
|
60
|
+
attributes[:show_warnings] = true
|
61
|
+
attributes[:id] = "brut-18n-#{key}"
|
64
62
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
|
64
|
+
brut_i18n_translation(**attributes)
|
65
|
+
|
66
|
+
end
|
68
67
|
end
|
69
68
|
end
|
@@ -4,7 +4,7 @@ class Brut::FrontEnd::Components::Inputs::CsrfToken < Brut::FrontEnd::Components
|
|
4
4
|
def initialize(csrf_token:)
|
5
5
|
@csrf_token = csrf_token
|
6
6
|
end
|
7
|
-
def
|
8
|
-
|
7
|
+
def view_template
|
8
|
+
input(type: "hidden", name: "authenticity_token", value: @csrf_token)
|
9
9
|
end
|
10
10
|
end
|
@@ -14,18 +14,18 @@ class Brut::FrontEnd::Components::Inputs::RadioButton < Brut::FrontEnd::Componen
|
|
14
14
|
# @param [Hash] html_attributes any additional HTML attributes to include on the `<input>` element.
|
15
15
|
def self.for_form_input(form:, input_name:, value:, html_attributes: {})
|
16
16
|
default_html_attributes = {}
|
17
|
-
html_attributes = html_attributes.map { |key,value| [ key.
|
17
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
18
18
|
input = form.input(input_name)
|
19
19
|
|
20
|
-
default_html_attributes[
|
21
|
-
default_html_attributes[
|
22
|
-
default_html_attributes[
|
23
|
-
default_html_attributes[
|
20
|
+
default_html_attributes[:required] = input.required
|
21
|
+
default_html_attributes[:type] = "radio"
|
22
|
+
default_html_attributes[:name] = input.name
|
23
|
+
default_html_attributes[:value] = value
|
24
24
|
|
25
25
|
selected_value = input.value
|
26
26
|
|
27
27
|
if selected_value == value
|
28
|
-
default_html_attributes[
|
28
|
+
default_html_attributes[:checked] = true
|
29
29
|
end
|
30
30
|
|
31
31
|
if !form.new? && !input.valid?
|
@@ -21,10 +21,11 @@ class Brut::FrontEnd::Components::Inputs::Select < Brut::FrontEnd::Components::I
|
|
21
21
|
option_text_attribute:,
|
22
22
|
index: nil,
|
23
23
|
html_attributes: {})
|
24
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
24
25
|
default_html_attributes = {}
|
25
26
|
index ||= 0
|
26
27
|
input = form.input(input_name, index:)
|
27
|
-
default_html_attributes[
|
28
|
+
default_html_attributes[:required] = input.required
|
28
29
|
if !form.new? && !input.valid?
|
29
30
|
default_html_attributes["data-invalid"] = true
|
30
31
|
input.validity_state.each do |constraint,violated|
|
@@ -61,36 +62,28 @@ class Brut::FrontEnd::Components::Inputs::Select < Brut::FrontEnd::Components::I
|
|
61
62
|
@selected_value = selected_value
|
62
63
|
@value_attribute = value_attribute
|
63
64
|
@option_text_attribute = option_text_attribute
|
65
|
+
@html_attributes = html_attributes
|
64
66
|
|
65
|
-
html_attributes[
|
66
|
-
@sanitized_attributes = html_attributes.map { |key,value|
|
67
|
-
[
|
68
|
-
key.to_s.gsub(/[\s\"\'>\/=]/,"-"),
|
69
|
-
value
|
70
|
-
]
|
71
|
-
}.select { |key,value|
|
72
|
-
!value.nil?
|
73
|
-
}.to_h
|
67
|
+
@html_attributes[:name] = name
|
74
68
|
end
|
75
69
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
70
|
+
def view_template
|
71
|
+
select(**@html_attributes) {
|
72
|
+
if @include_blank
|
73
|
+
option(**@include_blank.option_attributes) {
|
74
|
+
@include_blank.text_content
|
75
|
+
}
|
76
|
+
end
|
77
|
+
options = @options.each do |option|
|
79
78
|
value = option.send(@value_attribute)
|
80
79
|
option_attributes = { value: value }
|
81
80
|
if value == @selected_value
|
82
81
|
option_attributes[:selected] = true
|
83
82
|
end
|
84
|
-
|
83
|
+
option(**option_attributes) {
|
85
84
|
option.send(@option_text_attribute)
|
86
85
|
}
|
87
|
-
}
|
88
|
-
if @include_blank
|
89
|
-
options.unshift(html_tag(:option,**@include_blank.option_attributes) {
|
90
|
-
@include_blank.text_content
|
91
|
-
})
|
92
86
|
end
|
93
|
-
options.join("\n")
|
94
87
|
}
|
95
88
|
end
|
96
89
|
private
|
@@ -9,40 +9,39 @@ class Brut::FrontEnd::Components::Inputs::TextField < Brut::FrontEnd::Components
|
|
9
9
|
# @param [Hash] html_attributes any additional HTML attributes to include on the `<input>` element.
|
10
10
|
def self.for_form_input(form:, input_name:, index: nil, html_attributes: {})
|
11
11
|
default_html_attributes = {}
|
12
|
-
html_attributes = html_attributes.map { |key,value| [ key.
|
12
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
13
13
|
input = form.input(input_name, index:)
|
14
14
|
|
15
|
-
default_html_attributes[
|
16
|
-
default_html_attributes[
|
17
|
-
default_html_attributes[
|
18
|
-
default_html_attributes[
|
15
|
+
default_html_attributes[:required] = input.required
|
16
|
+
default_html_attributes[:pattern] = input.pattern
|
17
|
+
default_html_attributes[:type] = input.type
|
18
|
+
default_html_attributes[:name] = if input.array?
|
19
19
|
"#{input.name}[]"
|
20
20
|
else
|
21
21
|
input.name
|
22
22
|
end
|
23
23
|
|
24
24
|
if input.max
|
25
|
-
default_html_attributes[
|
25
|
+
default_html_attributes[:max] = input.max
|
26
26
|
end
|
27
27
|
if input.maxlength
|
28
|
-
default_html_attributes[
|
28
|
+
default_html_attributes[:maxlength] = input.maxlength
|
29
29
|
end
|
30
30
|
if input.min
|
31
|
-
default_html_attributes[
|
31
|
+
default_html_attributes[:min] = input.min
|
32
32
|
end
|
33
33
|
if input.minlength
|
34
|
-
default_html_attributes[
|
35
|
-
end
|
34
|
+
default_html_attributes[:minlength] = input.minlength end
|
36
35
|
if input.step
|
37
|
-
default_html_attributes[
|
36
|
+
default_html_attributes[:step] = input.step
|
38
37
|
end
|
39
38
|
value = input.value
|
40
39
|
|
41
40
|
if input.type == "checkbox"
|
42
|
-
default_html_attributes[
|
43
|
-
default_html_attributes[
|
41
|
+
default_html_attributes[:value] = (index || true).to_s
|
42
|
+
default_html_attributes[:checked] = value == "true"
|
44
43
|
else
|
45
|
-
default_html_attributes[
|
44
|
+
default_html_attributes[:value] = value.to_s
|
46
45
|
end
|
47
46
|
if !form.new? && !input.valid?
|
48
47
|
default_html_attributes["data-invalid"] = true
|
@@ -55,30 +54,16 @@ class Brut::FrontEnd::Components::Inputs::TextField < Brut::FrontEnd::Components
|
|
55
54
|
Brut::FrontEnd::Components::Inputs::TextField.new(default_html_attributes.merge(html_attributes))
|
56
55
|
end
|
57
56
|
|
57
|
+
def invalid? = @attributes["data-invalid"] == true
|
58
|
+
|
58
59
|
# Create an instance
|
59
60
|
#
|
60
61
|
# @param [Hash] attributes HTML attributes to put on the element.
|
61
62
|
def initialize(attributes)
|
62
|
-
@
|
63
|
-
[
|
64
|
-
key.to_s.gsub(/[\s\"\'>\/=]/,"-"),
|
65
|
-
value
|
66
|
-
]
|
67
|
-
}.select { |key,value|
|
68
|
-
!value.nil?
|
69
|
-
}.to_h
|
63
|
+
@attributes = attributes
|
70
64
|
end
|
71
65
|
|
72
|
-
def
|
73
|
-
|
74
|
-
if value == true
|
75
|
-
key
|
76
|
-
elsif value == false
|
77
|
-
""
|
78
|
-
else
|
79
|
-
REXML::Attribute.new(key,value).to_string
|
80
|
-
end
|
81
|
-
}.join(" ")
|
82
|
-
"<input #{attribute_string}>"
|
66
|
+
def view_template
|
67
|
+
input(**@attributes)
|
83
68
|
end
|
84
69
|
end
|
@@ -8,22 +8,23 @@ class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components:
|
|
8
8
|
# @param [Integer] index if this input is part of an array, this is the index into that array. This is used to get the input's value.
|
9
9
|
# @param [Hash] html_attributes any additional HTML attributes to include on the `<textarea>` element.
|
10
10
|
def self.for_form_input(form:, input_name:, index: nil, html_attributes: {})
|
11
|
+
html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
|
11
12
|
default_html_attributes = {}
|
12
13
|
|
13
14
|
index ||= 0
|
14
15
|
input = form.input(input_name, index:)
|
15
16
|
|
16
|
-
default_html_attributes[
|
17
|
-
default_html_attributes[
|
17
|
+
default_html_attributes[:required] = input.required
|
18
|
+
default_html_attributes[:name] = if input.array?
|
18
19
|
"#{input.name}[]"
|
19
20
|
else
|
20
21
|
input.name
|
21
22
|
end
|
22
23
|
if input.maxlength
|
23
|
-
default_html_attributes[
|
24
|
+
default_html_attributes[:maxlength] = input.maxlength
|
24
25
|
end
|
25
26
|
if input.minlength
|
26
|
-
default_html_attributes[
|
27
|
+
default_html_attributes[:minlength] = input.minlength
|
27
28
|
end
|
28
29
|
if !form.new? && !input.valid?
|
29
30
|
default_html_attributes["data-invalid"] = true
|
@@ -41,31 +42,15 @@ class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components:
|
|
41
42
|
# @param [Hash] attributes HTML attributes to put on the element.
|
42
43
|
# @param [String] value the value to place inside the text area
|
43
44
|
def initialize(attributes, value)
|
44
|
-
@
|
45
|
-
|
46
|
-
key.to_s.gsub(/[\s\"\'>\/=]/,"-"),
|
47
|
-
value
|
48
|
-
]
|
49
|
-
}.select { |key,value|
|
50
|
-
!value.nil?
|
51
|
-
}.to_h
|
52
|
-
@value = value
|
45
|
+
@attributes = attributes
|
46
|
+
@value = value
|
53
47
|
end
|
54
48
|
|
55
|
-
def
|
49
|
+
def invalid? = @attributes["data-invalid"] == true
|
56
50
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
key
|
61
|
-
elsif value == false
|
62
|
-
""
|
63
|
-
else
|
64
|
-
REXML::Attribute.new(key,value).to_string
|
65
|
-
end
|
66
|
-
}.join(" ")
|
67
|
-
%{
|
68
|
-
<textarea #{attribute_string}>#{ @value }</textarea>
|
51
|
+
def view_template
|
52
|
+
textarea(**@attributes) {
|
53
|
+
@value
|
69
54
|
}
|
70
55
|
end
|
71
56
|
end
|
@@ -13,7 +13,7 @@ class Brut::FrontEnd::Components::LocaleDetection < Brut::FrontEnd::Component
|
|
13
13
|
@url = Brut::FrontEnd::Handlers::LocaleDetectionHandler.routing
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def view_template
|
17
17
|
attributes = {
|
18
18
|
"url" => @url,
|
19
19
|
}
|
@@ -27,6 +27,6 @@ class Brut::FrontEnd::Components::LocaleDetection < Brut::FrontEnd::Component
|
|
27
27
|
attributes["show-warnings"] = true
|
28
28
|
end
|
29
29
|
|
30
|
-
|
30
|
+
brut_locale_detection(**attributes)
|
31
31
|
end
|
32
32
|
end
|
@@ -1,15 +1,13 @@
|
|
1
|
-
require "rexml"
|
2
|
-
|
3
1
|
# Renders a `<meta>` tag that contains the name of the page. This is useful for end to end tests to assert that they are on a specific page before continuing with the test. It can eliminate a lot of confusion when a test fails.
|
4
2
|
class Brut::FrontEnd::Components::PageIdentifier < Brut::FrontEnd::Component
|
5
3
|
def initialize(page_name)
|
6
4
|
@page_name = page_name
|
7
5
|
end
|
8
6
|
|
9
|
-
def
|
7
|
+
def view_template
|
10
8
|
if Brut.container.project_env.production?
|
11
|
-
return
|
9
|
+
return nil
|
12
10
|
end
|
13
|
-
|
11
|
+
meta(name: "class", content: @page_name)
|
14
12
|
end
|
15
13
|
end
|
@@ -1,6 +1,5 @@
|
|
1
|
-
require "rexml"
|
2
1
|
# Renders a date or timestamp accessibly, using the `<time>` element. Likely you will use this via the {Brut::FrontEnd::Component::Helpers#time_tag} method. This will account for the current request's time zone. See {Clock}.
|
3
|
-
class Brut::FrontEnd::Components::
|
2
|
+
class Brut::FrontEnd::Components::TimeTag < Brut::FrontEnd::Component
|
4
3
|
include Brut::I18n::ForHTML
|
5
4
|
# Creates the component
|
6
5
|
# @param timestamp [Time] the timestamp you wish to render. Mutually exclusive with `date`.
|
@@ -21,13 +20,18 @@ class Brut::FrontEnd::Components::Time < Brut::FrontEnd::Component
|
|
21
20
|
skip_year_if_same: true,
|
22
21
|
skip_dow_if_not_this_week: true,
|
23
22
|
attribute_format: :iso_8601,
|
24
|
-
|
25
|
-
|
23
|
+
clock: :from_request_context,
|
24
|
+
**only_contains_class
|
26
25
|
)
|
27
26
|
require_exactly_one!(timestamp:,date:)
|
28
27
|
|
29
28
|
@date_only = timestamp.nil?
|
30
29
|
@timestamp = timestamp || date
|
30
|
+
@clock = if clock == :from_request_context
|
31
|
+
Brut::FrontEnd::RequestContext.current[:clock]
|
32
|
+
else
|
33
|
+
clock
|
34
|
+
end
|
31
35
|
|
32
36
|
formats = [ format ]
|
33
37
|
use_no_year = skip_year_if_same && @timestamp.year == Time.now.year
|
@@ -69,21 +73,20 @@ class Brut::FrontEnd::Components::Time < Brut::FrontEnd::Component
|
|
69
73
|
@format = found_format.to_sym
|
70
74
|
@attribute_format = attribute_format.to_sym
|
71
75
|
@class_attribute = only_contains_class[:class] || ""
|
72
|
-
@contents = contents
|
73
76
|
end
|
74
77
|
|
75
|
-
def
|
78
|
+
def view_template
|
76
79
|
adjusted_value = if @date_only
|
77
80
|
@timestamp
|
78
81
|
else
|
79
|
-
clock.in_time_zone(@timestamp)
|
82
|
+
@clock.in_time_zone(@timestamp)
|
80
83
|
end
|
81
84
|
|
82
85
|
datetime_attribute = ::I18n.l(adjusted_value,format: @attribute_format)
|
83
86
|
|
84
|
-
|
85
|
-
if
|
86
|
-
|
87
|
+
time(class: @class_attribute, datetime: datetime_attribute) do
|
88
|
+
if block_given?
|
89
|
+
yield
|
87
90
|
else
|
88
91
|
::I18n.l(adjusted_value,format: @format)
|
89
92
|
end
|
@@ -8,15 +8,14 @@ class Brut::FrontEnd::Components::Traceparent < Brut::FrontEnd::Component
|
|
8
8
|
@traceparent = carrier["traceparent"]
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
11
|
+
def view_template
|
12
12
|
attributes = {
|
13
|
-
name: "traceparent"
|
13
|
+
name: "traceparent",
|
14
|
+
content: @traceparent,
|
14
15
|
}
|
15
|
-
if
|
16
|
-
attributes[:content] = @traceparent
|
17
|
-
else
|
16
|
+
if !@traceparent
|
18
17
|
attributes["data-no-traceparent"] = "no traceparent was available - this component may have been rendered outside of an existing OpenTelemetry context"
|
19
18
|
end
|
20
|
-
|
19
|
+
meta(**attributes)
|
21
20
|
end
|
22
21
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# A generic response to use when no built-in Brut construct will suffice.
|
2
|
+
# This mirrors the response a Rack server is intended to produce, and serves
|
3
|
+
# as merely a typed wrapper around that kind of response for the purposes of
|
4
|
+
# understanding the intention of whoever is returning this.
|
5
|
+
#
|
6
|
+
# Once created, {#to_ary} will convert this into the array that Rack requires.
|
7
|
+
class Brut::FrontEnd::GenericResponse
|
8
|
+
# Create a generic response.
|
9
|
+
#
|
10
|
+
# @param [Brut::FrontEnd::HttpStatus|number] http_status the status to send. If omitted, a 200 is used.
|
11
|
+
# Note that this value must be a valid HTTP status.
|
12
|
+
# @param [Hash<String|String>] headers hash of headers to send with the response.
|
13
|
+
# @param [String|Array<String>|IO|Enumerable] response_body the body of the response. This is passed
|
14
|
+
# through directly to the underlying Rack server so it should be whatever Rack expects, which is generally
|
15
|
+
# something that responds to `each`.
|
16
|
+
def initialize(http_status: 200, headers: {}, response_body:)
|
17
|
+
@response_body = response_body
|
18
|
+
@headers = headers
|
19
|
+
@http_status = Brut::FrontEnd::HttpStatus.new(http_status.to_i)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return this as an array suitable for use as a Rack response.
|
23
|
+
#
|
24
|
+
# @return [Array{String,3}] the response_body, headers, and http_status
|
25
|
+
def to_ary
|
26
|
+
[
|
27
|
+
@http_status.to_i,
|
28
|
+
@headers,
|
29
|
+
@response_body,
|
30
|
+
]
|
31
|
+
end
|
32
|
+
alias deconstruct to_ary
|
33
|
+
end
|
@@ -19,6 +19,8 @@ module Brut::FrontEnd
|
|
19
19
|
# * Array of two items, with the first being an Instance of {Brut::FrontEnd::Component} and the second being an {Brut::FrontEnd::HttpStatus} - renders that component or page, but responds with the given HTTP status. Useful for Ajax requests that don't return 200, but do return useful content.
|
20
20
|
# * Instance of {Brut::FrontEnd::HttpStatus} - returns just that status code. Typically you would do this by calling {Brut::FrontEnd::HandlingResults#http_status}
|
21
21
|
# * Instance of {Brut::FrontEnd::Download} - sends a file download to the browser.
|
22
|
+
# * Instance of {Brut::FrontEnd::GenericResponse} - sends itself as the rack response. Use this only if
|
23
|
+
# you cannot use one of the other options
|
22
24
|
#
|
23
25
|
# @return [URI|Brut::FrontEnd::Component,Array,Brut::FrontEnd::HttpStatus,Brut::FrontEnd::Download]
|
24
26
|
def handle(**)
|
@@ -1,5 +1,9 @@
|
|
1
|
+
require "phlex"
|
2
|
+
|
1
3
|
# Wrapper around an HTTP Method, ensuring it contains only a valid value.
|
2
4
|
class Brut::FrontEnd::HttpMethod
|
5
|
+
include Phlex::SGML::SafeObject
|
6
|
+
|
3
7
|
# Create an HTTP method from a string.
|
4
8
|
#
|
5
9
|
# @param [String|Symbol] string a string containing an HTTP method name. Case insensitive, and can be a symbol.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Brut::FrontEnd::InlineSvgLocator
|
2
|
+
def initialize(paths:)
|
3
|
+
@paths = Array(paths).map { |path| Pathname(path) }
|
4
|
+
end
|
5
|
+
|
6
|
+
def locate(base_name)
|
7
|
+
paths_to_try = @paths.map { |path|
|
8
|
+
path / "#{base_name}.svg"
|
9
|
+
}
|
10
|
+
paths_found = paths_to_try.select { |path|
|
11
|
+
path.exist?
|
12
|
+
}
|
13
|
+
if paths_found.empty?
|
14
|
+
raise "Could not locate SVG for #{base_name}. Tried: #{paths_to_try.map(&:to_s).join(', ')}"
|
15
|
+
end
|
16
|
+
if paths_found.length > 1
|
17
|
+
raise "Found more than one valid path for #{base_name}. You must rename your files to disambiguate them. These paths were all found: #{paths_found.map(&:to_s).join(', ')}"
|
18
|
+
end
|
19
|
+
return paths_found[0]
|
20
|
+
end
|
21
|
+
end
|