hanami-helpers 0.0.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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