hanami-helpers 0.0.0 → 0.3.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.
@@ -0,0 +1,45 @@
1
+ module Hanami
2
+ module Helpers
3
+ module HtmlHelper
4
+ # HTML Fragment
5
+ #
6
+ # @since 0.2.6
7
+ # @api private
8
+ #
9
+ # @see Hanami::Helpers::HtmlHelper::HtmlFragment
10
+ class HtmlFragment
11
+ # Initialize a HTML Fragment
12
+ #
13
+ # @param blk [Proc,Hanami::Helpers::HtmlHelper::HtmlBuilder,NilClass] the content block
14
+ #
15
+ # @return [Hanami::Helpers::HtmlHelper::HtmlFragment]
16
+ def initialize(&blk)
17
+ @builder = HtmlBuilder.new
18
+ @blk = blk
19
+ end
20
+
21
+ # Resolve and return the output
22
+ #
23
+ # @return [String] the output
24
+ #
25
+ # @since 0.2.6
26
+ # @api private
27
+ #
28
+ # @see Hanami::Helpers::HtmlHelper::EmptyHtmlNode#to_s
29
+ def to_s
30
+ content.to_s
31
+ end
32
+
33
+ def content
34
+ result = @builder.resolve(&@blk)
35
+
36
+ if @builder.nested?
37
+ @builder.to_s
38
+ else
39
+ "#{ Utils::Escape.html(result) }"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,70 @@
1
+ require 'hanami/helpers/html_helper/empty_html_node'
2
+
3
+ module Hanami
4
+ module Helpers
5
+ module HtmlHelper
6
+ # HTML node
7
+ #
8
+ # @since 0.1.0
9
+ # @api private
10
+ #
11
+ # @see Hanami::Helpers::HtmlHelper::EmptyHtmlNode
12
+ class HtmlNode < EmptyHtmlNode
13
+ # Initialize a new HTML node
14
+ #
15
+ # @param name [Symbol,String] the name of the tag
16
+ # @param content [String,Proc,Hanami::Helpers::HtmlHelper::HtmlBuilder,NilClass] the optional content
17
+ # @param attributes [Hash,NilClass] the optional tag attributes
18
+ # @param options [Hash] a optional set of data
19
+ #
20
+ # @return [Hanami::Helpers::HtmlHelper::HtmlNode]
21
+ def initialize(name, content, attributes, options = {})
22
+ @builder = HtmlBuilder.new
23
+ @name = name
24
+ @content = case content
25
+ when Hash
26
+ @attributes = content
27
+ nil
28
+ else
29
+ @attributes = attributes.to_h if attributes.respond_to?(:to_h)
30
+ content
31
+ end
32
+ end
33
+
34
+ # Resolve and return the output
35
+ #
36
+ # @return [String] the output
37
+ #
38
+ # @since 0.1.0
39
+ # @api private
40
+ #
41
+ # @see Hanami::Helpers::HtmlHelper::EmptyHtmlNode#to_s
42
+ def to_s
43
+ %(#{ super }#{ content }</#{ @name }>)
44
+ end
45
+
46
+ private
47
+ # Resolve the (nested) content
48
+ #
49
+ # @return [String] the content
50
+ #
51
+ # @since 0.1.0
52
+ # @api private
53
+ def content
54
+ case @content
55
+ when Proc
56
+ result = @builder.resolve(&@content)
57
+
58
+ if @builder.nested?
59
+ "\n#{ @builder }\n"
60
+ else
61
+ "\n#{ Utils::Escape.html(result) }\n"
62
+ end
63
+ else
64
+ Utils::Escape.html(@content)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,33 @@
1
+ module Hanami
2
+ module Helpers
3
+ module HtmlHelper
4
+ # Text node. Allows for text to be inserted between HTML tags.
5
+ #
6
+ # @since 0.2.5
7
+ # @api private
8
+ class TextNode
9
+ # Initialize a new text node
10
+ #
11
+ # @param content [String,#to_s] The content to be added.
12
+ #
13
+ # @return [Hanami::Helpers::HtmlHelper::TextNode]
14
+ #
15
+ # @since 0.2.5
16
+ # @api private
17
+ def initialize(content)
18
+ @content = content.to_s
19
+ end
20
+
21
+ # Resolve and return the output
22
+ #
23
+ # @return [String] the output
24
+ #
25
+ # @since 0.2.5
26
+ # @api private
27
+ def to_s
28
+ Utils::Escape.html(@content)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,135 @@
1
+ require 'hanami/helpers/html_helper'
2
+
3
+ module Hanami
4
+ module Helpers
5
+ # LinkTo Helper
6
+ #
7
+ # Including <tt>Hanami::Helpers::LinkTo</tt> will include the
8
+ # <tt>link_to</tt> public method.
9
+ #
10
+ # This helper can be used both in views and templates.
11
+ #
12
+ # @since 0.2.0
13
+ module LinkToHelper
14
+ include Hanami::Helpers::HtmlHelper
15
+
16
+ # Generates an anchor tag for the given arguments.
17
+ #
18
+ # Contents are automatically escaped.
19
+ #
20
+ # @overload link_to(content, url, options)
21
+ # Use string as content
22
+ # @param content [String] content used in the a tag
23
+ # @param url [String] url used in href attribute
24
+ # @param options [Hash] HTML attributes to pass to the a tag
25
+ #
26
+ # @overload link_to(url, options, &blk)
27
+ # Use block as content
28
+ # @param url [String] url used in href attribute
29
+ # @param options [Hash] HTML attributes to pass to the a tag
30
+ # @param blk [Proc] A block that describes the contents of the a tag
31
+ #
32
+ # @return [String] HTML markup for the link
33
+ #
34
+ # @raise [ArgumentError] if the signature isn't respected
35
+ #
36
+ # @since 0.2.0
37
+ #
38
+ # @see Hanami::Helpers::HtmlHelper#html
39
+ #
40
+ # @example Both content and URL are strings
41
+ # <%= link_to('Home', '/') %>
42
+ # # => <a href="/">Home</a>
43
+ #
44
+ # @example Content string with route helper
45
+ # <%= link_to('Home', routes.path(:home)) %>
46
+ # # => <a href="/">Home</a>
47
+ #
48
+ # @example HTML attributes
49
+ # <%= link_to('Home', routes.path(:home), class: 'button') %>
50
+ # # => <a href="/" class="button">Home</a>
51
+ #
52
+ # @example Automatic content escape (XSS protection)
53
+ # <%= link_to(%(<script>alert('xss')</script>), '/') %>
54
+ # # => <a href="/">&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</a>
55
+ #
56
+ # @example Automatic content block escape (XSS protection)
57
+ # <%=
58
+ # link_to('/') do
59
+ # %(<script>alert('xss')</script>)
60
+ # end
61
+ # %>
62
+ # # => <a href="/">\n&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;\n</a>
63
+ #
64
+ # @example Content as block with string URL
65
+ # <%=
66
+ # link_to('/') do
67
+ # 'Home'
68
+ # end
69
+ # %>
70
+ # # => <a href="/">Home</a>
71
+ #
72
+ # @example Content as block
73
+ # <%=
74
+ # link_to(routes.path(:home)) do
75
+ # 'Home'
76
+ # end
77
+ # %>
78
+ # # => <a href="/">Home</a>
79
+ #
80
+ # @example Content as block with HTML attributes
81
+ # <%=
82
+ # link_to(routes.path(:home), id: 'home-link') do
83
+ # 'Home'
84
+ # end
85
+ # %>
86
+ # # => <a href="/" id: 'home-link'>Home</a>
87
+ #
88
+ # @example Content as HTML builder block
89
+ # <%=
90
+ # link_to(routes.path(:home)) do
91
+ # strong 'Home'
92
+ # end
93
+ # %>
94
+ # # => <a href="/"><strong>Home</strong></a>
95
+ #
96
+ # @example Provides both content as first argument and block
97
+ # <%=
98
+ # link_to('Home', routes.path(:home)) do
99
+ # strong 'Home'
100
+ # end
101
+ # %>
102
+ # # => ArgumentError
103
+ #
104
+ # @example Without any argument
105
+ # <%= link_to %>
106
+ # # => ArgumentError
107
+ #
108
+ # @example Without any argument and empty block
109
+ # <%=
110
+ # link_to do
111
+ # end
112
+ # %>
113
+ # # => ArgumentError
114
+ #
115
+ # @example With only content
116
+ # <%= link_to 'Home' %>
117
+ # # => ArgumentError
118
+ def link_to(content, url = nil, options = {}, &blk)
119
+ if block_given?
120
+ options = url || {}
121
+ url = content
122
+ content = nil
123
+ end
124
+
125
+ begin
126
+ options[:href] = url or raise ArgumentError
127
+ rescue TypeError
128
+ raise ArgumentError
129
+ end
130
+
131
+ html.a(blk || content, options).to_s
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,220 @@
1
+ module Hanami
2
+ module Helpers
3
+ # Number formatter
4
+ #
5
+ # You can include this module inside your view and
6
+ # the view will have access all methods.
7
+ #
8
+ # By including <tt>Hanami::Helpers::NumberFormattingHelper</tt> it will
9
+ # inject private method: <tt>format_number</tt>.
10
+ #
11
+ # @since 0.2.0
12
+ module NumberFormattingHelper
13
+ private
14
+ # Format the given number, according to the options
15
+ #
16
+ # It accepts a number (<tt>Numeric</tt>) or a string representation.
17
+ #
18
+ # If an integer is given, no precision is applied.
19
+ # For the rest of the numbers, it will format as a float representation.
20
+ # This is the case of: <tt>Float</tt>, <tt>BigDecimal</tt>,
21
+ # <tt>Complex</tt>, <tt>Rational</tt>.
22
+ #
23
+ # If the argument cannot be coerced into a number, it will raise a
24
+ # <tt>TypeError</tt>.
25
+ #
26
+ # @param number [Numeric,String] the number to be formatted
27
+ #
28
+ # @return [String] formatted number
29
+ #
30
+ # @raise [TypeError] if number can't be formatted
31
+ #
32
+ # @since 0.2.0
33
+ #
34
+ # @example
35
+ # require 'hanami/helpers/number_formatting_helper'
36
+ #
37
+ # class Checkout
38
+ # include Hanami::Helpers::NumberFormattingHelper
39
+ #
40
+ # def total
41
+ # format_number 1999.99
42
+ # end
43
+ #
44
+ # def euros
45
+ # format_number 1256.95, delimiter: '.', separator: ','
46
+ # end
47
+ #
48
+ # def visitors_count
49
+ # format_number '1000'
50
+ # end
51
+ # end
52
+ #
53
+ # view = Checkout.new
54
+ #
55
+ # view.total
56
+ # # => "1,999.99"
57
+ #
58
+ # view.euros
59
+ # # => "1.256,95"
60
+ #
61
+ # view.visitors_count
62
+ # # => "1,000"
63
+ def format_number(number, options = {})
64
+ Formatter.new(number, options).format
65
+ end
66
+
67
+ # Formatter
68
+ #
69
+ # @since 0.2.0
70
+ # @api private
71
+ class Formatter
72
+ # Regex to delimitate integer part of a number
73
+ #
74
+ # @return [Regexp] the delimitation regex
75
+ #
76
+ # @since 0.2.0
77
+ # @api private
78
+ #
79
+ # @see Hanami::Helpers::NumberFormatter::Formatter#delimitate
80
+ DELIMITATION_REGEX = /(\d)(?=(\d{3})+$)/
81
+
82
+ # Regex to guess if the number is a integer
83
+ #
84
+ # @return [Regexp] the guessing regex
85
+ #
86
+ # @since 0.2.0
87
+ # @api private
88
+ #
89
+ # @see Hanami::Helpers::NumberFormatter::Formatter#to_number
90
+ INTEGER_REGEXP = /\A[\d]+\z/
91
+
92
+ # Default separator
93
+ #
94
+ # @return [String] default separator
95
+ #
96
+ # @since 0.2.0
97
+ # @api private
98
+ DEFAULT_SEPARATOR = '.'.freeze
99
+
100
+ # Default delimiter
101
+ #
102
+ # @return [String] default delimiter
103
+ #
104
+ # @since 0.2.0
105
+ # @api private
106
+ DEFAULT_DELIMITER = ','.freeze
107
+
108
+ # Default precision
109
+ #
110
+ # @return [String] default precision
111
+ #
112
+ # @since 0.2.0
113
+ # @api private
114
+ DEFAULT_PRECISION = 2
115
+
116
+ # Initialize a new formatter
117
+ #
118
+ # @param number [Numeric,String] the number to format
119
+ # @param options [Hash] options for number formatting
120
+ # @option options [String] :delimiter hundred delimiter
121
+ # @option options [String] :separator fractional part delimiter
122
+ # @option options [Integer] :precision rounding precision
123
+ #
124
+ # @since 0.2.0
125
+ # @api private
126
+ #
127
+ # @see Hanami::Helpers::NumberFormatter::Formatter::DEFAULT_DELIMITER
128
+ # @see Hanami::Helpers::NumberFormatter::Formatter::DEFAULT_SEPARATOR
129
+ # @see Hanami::Helpers::NumberFormatter::Formatter::DEFAULT_PRECISION
130
+ def initialize(number, options)
131
+ @number = number
132
+ @delimiter = options.fetch(:delimiter, DEFAULT_DELIMITER)
133
+ @separator = options.fetch(:separator, DEFAULT_SEPARATOR)
134
+ @precision = options.fetch(:precision, DEFAULT_PRECISION)
135
+ end
136
+
137
+ # Format number according to the specified options
138
+ #
139
+ # @return [String] formatted number
140
+ #
141
+ # @raise [TypeError] if number can't be formatted
142
+ #
143
+ # @since 0.2.0
144
+ # @api private
145
+ def format
146
+ parts.join(@separator)
147
+ end
148
+
149
+ private
150
+
151
+ # Return integer and fractional parts
152
+ #
153
+ # @return [Array] parts
154
+ #
155
+ # @since 0.2.0
156
+ # @api private
157
+ def parts
158
+ integer_part, fractional_part = to_str.split(DEFAULT_SEPARATOR)
159
+ [delimitate(integer_part), fractional_part].compact
160
+ end
161
+
162
+ # Delimitate the given part
163
+ #
164
+ # @return [String] delimitated string
165
+ #
166
+ # @since 0.2.0
167
+ # @api private
168
+ def delimitate(part)
169
+ part.gsub(DELIMITATION_REGEX) { |digit| "#{digit}#{@delimiter}" }
170
+ end
171
+
172
+ # String coercion
173
+ #
174
+ # @return [String] coerced number
175
+ #
176
+ # @raise [TypeError] if number can't be formatted
177
+ #
178
+ # @since 0.2.0
179
+ # @api private
180
+ def to_str
181
+ to_number.to_s
182
+ end
183
+
184
+ # Numeric coercion
185
+ #
186
+ # @return [Numeric] coerced number
187
+ #
188
+ # @raise [TypeError] if number can't be formatted
189
+ #
190
+ # @since 0.2.0
191
+ # @api private
192
+ def to_number
193
+ case @number
194
+ when NilClass
195
+ raise TypeError
196
+ when ->(n) { n.to_s.match(INTEGER_REGEXP) }
197
+ Utils::Kernel.Integer(@number)
198
+ else
199
+ Utils::Kernel.Float(rounded_number)
200
+ end
201
+ end
202
+
203
+ # Round number in case we need to return a <tt>Float</tt> representation.
204
+ # If <tt>@number</tt> doesn't respond to <tt>#round</tt> return the number as it is.
205
+ #
206
+ # @return [Float,Complex,Rational,BigDecimal] rounded number, if applicable
207
+ #
208
+ # @since 0.2.0
209
+ # @api private
210
+ def rounded_number
211
+ if @number.respond_to?(:round)
212
+ @number.round(@precision)
213
+ else
214
+ @number
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end