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.
- 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/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 +13 -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 +12 -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 -46
- metadata +13 -41
- 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
@@ -1,84 +0,0 @@
|
|
1
|
-
# Almost verbatim copy of Hanami's parser:
|
2
|
-
#
|
3
|
-
# https://github.com/hanami/view/blob/main/lib/hanami/view/erb/parser.rb
|
4
|
-
#
|
5
|
-
# That is licensed MIT and thus so is this file.
|
6
|
-
#
|
7
|
-
# Avoid changes to this file so it can be kept updated with Hanami.
|
8
|
-
class Brut::FrontEnd::Templates::ERBParser < Temple::Parser
|
9
|
-
ERB_PATTERN = /(\n|<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m
|
10
|
-
|
11
|
-
IF_UNLESS_CASE_LINE_RE = /\A\s*(if|unless|case)\b/
|
12
|
-
BLOCK_LINE_RE = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
13
|
-
END_LINE_RE = /\bend\b/
|
14
|
-
|
15
|
-
def call(input)
|
16
|
-
results = [[:multi]]
|
17
|
-
pos = 0
|
18
|
-
|
19
|
-
input.scan(ERB_PATTERN) do |token, indicator, code|
|
20
|
-
# Capture any text between the last ERB tag and the current one, and update the position
|
21
|
-
# to match the end of the current tag for the next iteration of text collection.
|
22
|
-
text = input[pos...$~.begin(0)]
|
23
|
-
pos = $~.end(0)
|
24
|
-
|
25
|
-
if token
|
26
|
-
# First, handle certain static tokens picked up by our ERB_PATTERN regexp. These are
|
27
|
-
# newlines as well as the special codes for literal `<%` and `%>` values.
|
28
|
-
case token
|
29
|
-
when "\n"
|
30
|
-
results.last << [:static, "#{text}\n"] << [:newline]
|
31
|
-
when "<%%", "%%>"
|
32
|
-
results.last << [:static, text] unless text.empty?
|
33
|
-
token.slice!(1)
|
34
|
-
results.last << [:static, token]
|
35
|
-
end
|
36
|
-
else
|
37
|
-
# Next, handle actual ERB tags. Start by adding any static text between this match and
|
38
|
-
# the last.
|
39
|
-
results.last << [:static, text] unless text.empty?
|
40
|
-
|
41
|
-
case indicator
|
42
|
-
when "#"
|
43
|
-
# Comment tags: <%# this is a comment %>
|
44
|
-
results.last << [:code, "\n" * code.count("\n")]
|
45
|
-
when %r{=}
|
46
|
-
# Expression tags: <%= "hello (auto-escaped)" %> or <%== "hello (not escaped)" %>
|
47
|
-
if code =~ BLOCK_LINE_RE
|
48
|
-
# See Hanami::View::Erb::Filters::Block for the processing of `:erb, :block` sexps
|
49
|
-
block_node = [:erb, :block, indicator.size == 1, code, (block_content = [:multi])]
|
50
|
-
results.last << block_node
|
51
|
-
|
52
|
-
# For blocks opened in ERB expression tags, push this `[:multi]` sexp
|
53
|
-
# (representing the content of the block) onto the stack of resuts. This allows
|
54
|
-
# subsequent results to be appropriately added inside the block, until its closing
|
55
|
-
# tag is encountered, and this `block_content` multi is subsequently popped off
|
56
|
-
# the results stack.
|
57
|
-
results << block_content
|
58
|
-
else
|
59
|
-
results.last << [:escape, indicator.size == 1, [:dynamic, code]]
|
60
|
-
end
|
61
|
-
else
|
62
|
-
# Code tags: <% if some_cond %>
|
63
|
-
if code =~ BLOCK_LINE_RE || code =~ IF_UNLESS_CASE_LINE_RE
|
64
|
-
results.last << [:code, code]
|
65
|
-
|
66
|
-
# For ERB code tags that will result in a matching `end`, push the last result
|
67
|
-
# back onto the stack of results. This might seem redundant, but it allows
|
68
|
-
# subsequent sexps to continue to be pushed onto the same result while also
|
69
|
-
# allowing it to be safely popped again when the matching `end` is encountered.
|
70
|
-
results << results.last
|
71
|
-
elsif code =~ END_LINE_RE
|
72
|
-
results.last << [:code, code]
|
73
|
-
results.pop
|
74
|
-
else
|
75
|
-
results.last << [:code, code]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Add any text after the final ERB tag
|
82
|
-
results.last << [:static, input[pos..-1]]
|
83
|
-
end
|
84
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# A temple filter that handles escaping HTML unless it's been wrapped in
|
2
|
-
# an HTMLSafeString.
|
3
|
-
class Brut::FrontEnd::Templates::EscapableFilter < Temple::Filters::Escapable
|
4
|
-
using Brut::FrontEnd::Templates::HTMLSafeString::Refinement
|
5
|
-
|
6
|
-
# @!visibility private
|
7
|
-
def initialize(opts = {})
|
8
|
-
opts[:escape_code] ||= "::Brut::FrontEnd::Templates::EscapableFilter.escape_html((%s))"
|
9
|
-
super(opts)
|
10
|
-
end
|
11
|
-
|
12
|
-
# @!visibility private
|
13
|
-
def self.escape_html(html)
|
14
|
-
if html.kind_of?(Brut::FrontEnd::Templates::HTMLSafeString)
|
15
|
-
html.string
|
16
|
-
else
|
17
|
-
Temple::Utils.escape_html(html)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
# A wrapper around a string to indicate it is HTML-safe and
|
2
|
-
# can be rendered directly without escaping. This was done to avoid adding methods on `String` and the internal state
|
3
|
-
# required to make something like `"foo".html_safe!` work.
|
4
|
-
class Brut::FrontEnd::Templates::HTMLSafeString
|
5
|
-
# This can be used via `using` to add `html_safe!` and `html_safe?` method to `String` when they might be more convienient
|
6
|
-
# than using {Brut::FrontEnd::Templates::HTMLSafeString} directly.
|
7
|
-
module Refinement
|
8
|
-
refine String do
|
9
|
-
def html_safe! = Brut::FrontEnd::Templates::HTMLSafeString.from_string(self)
|
10
|
-
def html_safe? = false
|
11
|
-
end
|
12
|
-
end
|
13
|
-
using Refinement
|
14
|
-
|
15
|
-
# @return [String] the underlying string being wrapped
|
16
|
-
attr_reader :string
|
17
|
-
|
18
|
-
# Create an HTML safe string based on the parameter. It's recommended to use {.from_string} instead.
|
19
|
-
#
|
20
|
-
# @param [String] string A string that is considered safe to put directly into a web page without escaping.
|
21
|
-
def initialize(string)
|
22
|
-
@string = string
|
23
|
-
end
|
24
|
-
|
25
|
-
# Creates an HTML Safe string based on the parameter, properly handling if a HTML safe string is being passed.
|
26
|
-
#
|
27
|
-
# @param [String|Brut::FrontEnd::Templates::HTMLSafeString] string_or_html_safe_string the value to turn into an HTML safe string.
|
28
|
-
#
|
29
|
-
# @return [Brut::FrontEnd::Templates::HTMLSafeString] if `string_or_html_safe_string` is already HTML safe, returns it. Otherwise,
|
30
|
-
# wraps the string as HTML safe.
|
31
|
-
def self.from_string(string_or_html_safe_string)
|
32
|
-
if string_or_html_safe_string.kind_of?(self)
|
33
|
-
string_or_html_safe_string
|
34
|
-
else
|
35
|
-
self.new(string_or_html_safe_string)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# This must be convertible to a string
|
40
|
-
def to_s = @string
|
41
|
-
def to_str = @string
|
42
|
-
# Matches the protocol in {Brut::FrontEnd::Templates::HTMLSafeString::Refinement}
|
43
|
-
# @return [Brut::FrontEnd::Templates::HTMLSafeString] self
|
44
|
-
def html_safe! = self
|
45
|
-
# Matches the protocol in {Brut::FrontEnd::Templates::HTMLSafeString::Refinement}
|
46
|
-
# @return [true|false] true
|
47
|
-
def html_safe? = true
|
48
|
-
|
49
|
-
# Return a new instance that has called `capitalize` on the underlying string
|
50
|
-
def capitalize = self.class.new(@string.capitalize)
|
51
|
-
# Return a new instance that has called `downcase` on the underlying string
|
52
|
-
def downcase = self.class.new(@string.downcase)
|
53
|
-
# Return a new instance that has called `upcase` on the underlying string
|
54
|
-
def upcase = self.class.new(@string.upcase)
|
55
|
-
|
56
|
-
# Returns the concatenation of two strings. If the other is HTML safe, then this returns an HTML safe string.
|
57
|
-
# If the other is not, this returns a normal unsafe string.
|
58
|
-
#
|
59
|
-
# @param [String|Brut::FrontEnd::Templates::HTMLSafeString] other
|
60
|
-
# @return [String|Brut::FrontEnd::Templates::HTMLSafeString] A safe or unsafe string, depending on what was passed.
|
61
|
-
def +(other)
|
62
|
-
if other.html_safe?
|
63
|
-
self.class.new(@string + other.to_s)
|
64
|
-
else
|
65
|
-
@string + other.to_s
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# Locates a template, based on a name, configured paths, and an extension. This class forms both an API
|
2
|
-
# for template location ({#locate}) as well as an implementation that is conventional with Brut apps.
|
3
|
-
class Brut::FrontEnd::Templates::Locator
|
4
|
-
# Create a locator that will search the given paths and require that template
|
5
|
-
# files have the given extension
|
6
|
-
#
|
7
|
-
# @param [Pathname|String|Array<Pathname|String>] paths one or more paths that will be searched for templates
|
8
|
-
# @param [String] extension file extension, without the dot, of the name of files that are considered templates
|
9
|
-
def initialize(paths:, extension:)
|
10
|
-
@paths = Array(paths).map { |path| Pathname(path) }
|
11
|
-
@extension = extension
|
12
|
-
end
|
13
|
-
|
14
|
-
# Given a base name, which may or may not be nested paths, returns the path to the template
|
15
|
-
# for this file. There must be exactly one template that matches.
|
16
|
-
#
|
17
|
-
# @example
|
18
|
-
#
|
19
|
-
# locator = Locator.new(
|
20
|
-
# paths: [
|
21
|
-
# Brut.container.app_src_dir / "front_end" / "components",
|
22
|
-
# Brut.container.app_src_dir / "front_end" / "other_components",
|
23
|
-
# ],
|
24
|
-
# extension: "html.erb"
|
25
|
-
# )
|
26
|
-
#
|
27
|
-
# # Suppose app/src/front_end/components/foo.html.erb exists
|
28
|
-
# path = locator.locate("foo")
|
29
|
-
# # => "app/src/front_end/components/foo.html.erb"
|
30
|
-
#
|
31
|
-
# # Suppose app/src/front_end/components/bar/blah.html.erb exists
|
32
|
-
# path = locator.locate("bar/blah")
|
33
|
-
# # => "app/src/front_end/components/bar/blah.html.erb"
|
34
|
-
#
|
35
|
-
# # Suppose both app/src/front_end/components/bar/blah.html.erb and
|
36
|
-
# # app/src/front_end/other_components/bar/blah.html.erb
|
37
|
-
# # both exist
|
38
|
-
# path = locator.locate("bar/blah")
|
39
|
-
# # => raises an error since there are two matches
|
40
|
-
#
|
41
|
-
# @param [String] base_name the base name of a file that is expected to have a template. This is searched relative to the paths
|
42
|
-
# provided to the constructor, so it may have nested paths
|
43
|
-
# @return [String] path to the template for the given `base_name`
|
44
|
-
# @raise StandardError if zero or more than one templates are found
|
45
|
-
def locate(base_name)
|
46
|
-
paths_to_try = @paths.map { |path|
|
47
|
-
path / "#{base_name}.#{@extension}"
|
48
|
-
}
|
49
|
-
paths_found = paths_to_try.select { |path|
|
50
|
-
path.exist?
|
51
|
-
}
|
52
|
-
if paths_found.empty?
|
53
|
-
raise "Could not locate template for #{base_name}. Tried: #{paths_to_try.map(&:to_s).join(', ')}"
|
54
|
-
end
|
55
|
-
if paths_found.length > 1
|
56
|
-
raise "Found more than one valid pat for #{base_name}. You must rename your files to disambiguate them. These paths were all found: #{paths_found.map(&:to_s).join(', ')}"
|
57
|
-
end
|
58
|
-
return paths_found[0]
|
59
|
-
end
|
60
|
-
end
|