core_ext 0.0.1

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 (175) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3 -0
  3. data/lib/core_ext/array/access.rb +76 -0
  4. data/lib/core_ext/array/conversions.rb +211 -0
  5. data/lib/core_ext/array/extract_options.rb +29 -0
  6. data/lib/core_ext/array/grouping.rb +116 -0
  7. data/lib/core_ext/array/inquiry.rb +17 -0
  8. data/lib/core_ext/array/prepend_and_append.rb +7 -0
  9. data/lib/core_ext/array/wrap.rb +46 -0
  10. data/lib/core_ext/array.rb +7 -0
  11. data/lib/core_ext/array_inquirer.rb +44 -0
  12. data/lib/core_ext/benchmark.rb +14 -0
  13. data/lib/core_ext/benchmarkable.rb +49 -0
  14. data/lib/core_ext/big_decimal/conversions.rb +14 -0
  15. data/lib/core_ext/big_decimal.rb +1 -0
  16. data/lib/core_ext/builder.rb +6 -0
  17. data/lib/core_ext/callbacks.rb +770 -0
  18. data/lib/core_ext/class/attribute.rb +128 -0
  19. data/lib/core_ext/class/attribute_accessors.rb +4 -0
  20. data/lib/core_ext/class/subclasses.rb +42 -0
  21. data/lib/core_ext/class.rb +2 -0
  22. data/lib/core_ext/concern.rb +142 -0
  23. data/lib/core_ext/configurable.rb +148 -0
  24. data/lib/core_ext/date/acts_like.rb +8 -0
  25. data/lib/core_ext/date/blank.rb +12 -0
  26. data/lib/core_ext/date/calculations.rb +143 -0
  27. data/lib/core_ext/date/conversions.rb +93 -0
  28. data/lib/core_ext/date/zones.rb +6 -0
  29. data/lib/core_ext/date.rb +5 -0
  30. data/lib/core_ext/date_and_time/calculations.rb +328 -0
  31. data/lib/core_ext/date_and_time/zones.rb +40 -0
  32. data/lib/core_ext/date_time/acts_like.rb +14 -0
  33. data/lib/core_ext/date_time/blank.rb +12 -0
  34. data/lib/core_ext/date_time/calculations.rb +177 -0
  35. data/lib/core_ext/date_time/conversions.rb +104 -0
  36. data/lib/core_ext/date_time/zones.rb +6 -0
  37. data/lib/core_ext/date_time.rb +5 -0
  38. data/lib/core_ext/deprecation/behaviors.rb +86 -0
  39. data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
  40. data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
  41. data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
  42. data/lib/core_ext/deprecation/reporting.rb +105 -0
  43. data/lib/core_ext/deprecation.rb +43 -0
  44. data/lib/core_ext/digest/uuid.rb +51 -0
  45. data/lib/core_ext/duration.rb +157 -0
  46. data/lib/core_ext/enumerable.rb +106 -0
  47. data/lib/core_ext/file/atomic.rb +68 -0
  48. data/lib/core_ext/file.rb +1 -0
  49. data/lib/core_ext/hash/compact.rb +20 -0
  50. data/lib/core_ext/hash/conversions.rb +261 -0
  51. data/lib/core_ext/hash/deep_merge.rb +38 -0
  52. data/lib/core_ext/hash/except.rb +22 -0
  53. data/lib/core_ext/hash/indifferent_access.rb +23 -0
  54. data/lib/core_ext/hash/keys.rb +170 -0
  55. data/lib/core_ext/hash/reverse_merge.rb +22 -0
  56. data/lib/core_ext/hash/slice.rb +48 -0
  57. data/lib/core_ext/hash/transform_values.rb +29 -0
  58. data/lib/core_ext/hash.rb +9 -0
  59. data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
  60. data/lib/core_ext/inflections.rb +70 -0
  61. data/lib/core_ext/inflector/inflections.rb +244 -0
  62. data/lib/core_ext/inflector/methods.rb +381 -0
  63. data/lib/core_ext/inflector/transliterate.rb +112 -0
  64. data/lib/core_ext/inflector.rb +7 -0
  65. data/lib/core_ext/integer/inflections.rb +29 -0
  66. data/lib/core_ext/integer/multiple.rb +10 -0
  67. data/lib/core_ext/integer/time.rb +29 -0
  68. data/lib/core_ext/integer.rb +3 -0
  69. data/lib/core_ext/json/decoding.rb +67 -0
  70. data/lib/core_ext/json/encoding.rb +127 -0
  71. data/lib/core_ext/json.rb +2 -0
  72. data/lib/core_ext/kernel/agnostics.rb +11 -0
  73. data/lib/core_ext/kernel/concern.rb +10 -0
  74. data/lib/core_ext/kernel/reporting.rb +41 -0
  75. data/lib/core_ext/kernel/singleton_class.rb +6 -0
  76. data/lib/core_ext/kernel.rb +4 -0
  77. data/lib/core_ext/load_error.rb +30 -0
  78. data/lib/core_ext/logger.rb +57 -0
  79. data/lib/core_ext/logger_silence.rb +24 -0
  80. data/lib/core_ext/marshal.rb +19 -0
  81. data/lib/core_ext/module/aliasing.rb +74 -0
  82. data/lib/core_ext/module/anonymous.rb +28 -0
  83. data/lib/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/core_ext/module/concerning.rb +135 -0
  86. data/lib/core_ext/module/delegation.rb +218 -0
  87. data/lib/core_ext/module/deprecation.rb +23 -0
  88. data/lib/core_ext/module/introspection.rb +62 -0
  89. data/lib/core_ext/module/method_transplanting.rb +3 -0
  90. data/lib/core_ext/module/qualified_const.rb +52 -0
  91. data/lib/core_ext/module/reachable.rb +8 -0
  92. data/lib/core_ext/module/remove_method.rb +35 -0
  93. data/lib/core_ext/module.rb +11 -0
  94. data/lib/core_ext/multibyte/chars.rb +231 -0
  95. data/lib/core_ext/multibyte/unicode.rb +388 -0
  96. data/lib/core_ext/multibyte.rb +21 -0
  97. data/lib/core_ext/name_error.rb +31 -0
  98. data/lib/core_ext/numeric/bytes.rb +64 -0
  99. data/lib/core_ext/numeric/conversions.rb +132 -0
  100. data/lib/core_ext/numeric/inquiry.rb +26 -0
  101. data/lib/core_ext/numeric/time.rb +74 -0
  102. data/lib/core_ext/numeric.rb +4 -0
  103. data/lib/core_ext/object/acts_like.rb +10 -0
  104. data/lib/core_ext/object/blank.rb +140 -0
  105. data/lib/core_ext/object/conversions.rb +4 -0
  106. data/lib/core_ext/object/deep_dup.rb +53 -0
  107. data/lib/core_ext/object/duplicable.rb +98 -0
  108. data/lib/core_ext/object/inclusion.rb +27 -0
  109. data/lib/core_ext/object/instance_variables.rb +28 -0
  110. data/lib/core_ext/object/json.rb +199 -0
  111. data/lib/core_ext/object/to_param.rb +1 -0
  112. data/lib/core_ext/object/to_query.rb +84 -0
  113. data/lib/core_ext/object/try.rb +146 -0
  114. data/lib/core_ext/object/with_options.rb +69 -0
  115. data/lib/core_ext/object.rb +14 -0
  116. data/lib/core_ext/option_merger.rb +25 -0
  117. data/lib/core_ext/ordered_hash.rb +48 -0
  118. data/lib/core_ext/ordered_options.rb +81 -0
  119. data/lib/core_ext/range/conversions.rb +34 -0
  120. data/lib/core_ext/range/each.rb +21 -0
  121. data/lib/core_ext/range/include_range.rb +23 -0
  122. data/lib/core_ext/range/overlaps.rb +8 -0
  123. data/lib/core_ext/range.rb +4 -0
  124. data/lib/core_ext/regexp.rb +5 -0
  125. data/lib/core_ext/rescuable.rb +119 -0
  126. data/lib/core_ext/securerandom.rb +23 -0
  127. data/lib/core_ext/security_utils.rb +20 -0
  128. data/lib/core_ext/string/access.rb +104 -0
  129. data/lib/core_ext/string/behavior.rb +6 -0
  130. data/lib/core_ext/string/conversions.rb +56 -0
  131. data/lib/core_ext/string/exclude.rb +11 -0
  132. data/lib/core_ext/string/filters.rb +102 -0
  133. data/lib/core_ext/string/indent.rb +43 -0
  134. data/lib/core_ext/string/inflections.rb +235 -0
  135. data/lib/core_ext/string/inquiry.rb +13 -0
  136. data/lib/core_ext/string/multibyte.rb +53 -0
  137. data/lib/core_ext/string/output_safety.rb +261 -0
  138. data/lib/core_ext/string/starts_ends_with.rb +4 -0
  139. data/lib/core_ext/string/strip.rb +23 -0
  140. data/lib/core_ext/string/zones.rb +14 -0
  141. data/lib/core_ext/string.rb +13 -0
  142. data/lib/core_ext/string_inquirer.rb +26 -0
  143. data/lib/core_ext/tagged_logging.rb +78 -0
  144. data/lib/core_ext/test_case.rb +88 -0
  145. data/lib/core_ext/testing/assertions.rb +99 -0
  146. data/lib/core_ext/testing/autorun.rb +12 -0
  147. data/lib/core_ext/testing/composite_filter.rb +54 -0
  148. data/lib/core_ext/testing/constant_lookup.rb +50 -0
  149. data/lib/core_ext/testing/declarative.rb +26 -0
  150. data/lib/core_ext/testing/deprecation.rb +36 -0
  151. data/lib/core_ext/testing/file_fixtures.rb +34 -0
  152. data/lib/core_ext/testing/isolation.rb +115 -0
  153. data/lib/core_ext/testing/method_call_assertions.rb +41 -0
  154. data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
  155. data/lib/core_ext/testing/stream.rb +42 -0
  156. data/lib/core_ext/testing/tagged_logging.rb +25 -0
  157. data/lib/core_ext/testing/time_helpers.rb +134 -0
  158. data/lib/core_ext/time/acts_like.rb +8 -0
  159. data/lib/core_ext/time/calculations.rb +284 -0
  160. data/lib/core_ext/time/conversions.rb +66 -0
  161. data/lib/core_ext/time/zones.rb +95 -0
  162. data/lib/core_ext/time.rb +20 -0
  163. data/lib/core_ext/time_with_zone.rb +503 -0
  164. data/lib/core_ext/time_zone.rb +464 -0
  165. data/lib/core_ext/uri.rb +25 -0
  166. data/lib/core_ext/version.rb +3 -0
  167. data/lib/core_ext/xml_mini/jdom.rb +181 -0
  168. data/lib/core_ext/xml_mini/libxml.rb +79 -0
  169. data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
  170. data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
  171. data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
  172. data/lib/core_ext/xml_mini/rexml.rb +130 -0
  173. data/lib/core_ext/xml_mini.rb +194 -0
  174. data/lib/core_ext.rb +3 -0
  175. metadata +310 -0
@@ -0,0 +1,261 @@
1
+ require 'erb'
2
+ require 'core_ext/kernel/singleton_class'
3
+
4
+ class ERB
5
+ module Util
6
+ HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
7
+ JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
8
+ HTML_ESCAPE_REGEXP = /[&"'><]/
9
+ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
10
+ JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
11
+
12
+ # A utility method for escaping HTML tag characters.
13
+ # This method is also aliased as <tt>h</tt>.
14
+ #
15
+ # In your ERB templates, use this method to escape any unsafe content. For example:
16
+ # <%= h @person.name %>
17
+ #
18
+ # puts html_escape('is a > 0 & a < 10?')
19
+ # # => is a &gt; 0 &amp; a &lt; 10?
20
+ def html_escape(s)
21
+ unwrapped_html_escape(s).html_safe
22
+ end
23
+
24
+ # Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
25
+ remove_method(:h)
26
+ alias h html_escape
27
+
28
+ module_function :h
29
+
30
+ singleton_class.send(:remove_method, :html_escape)
31
+ module_function :html_escape
32
+
33
+ # HTML escapes strings but doesn't wrap them with an CoreExt::SafeBuffer.
34
+ # This method is not for public consumption! Seriously!
35
+ def unwrapped_html_escape(s) # :nodoc:
36
+ s = s.to_s
37
+ if s.html_safe?
38
+ s
39
+ else
40
+ CoreExt::Multibyte::Unicode.tidy_bytes(s).gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
41
+ end
42
+ end
43
+ module_function :unwrapped_html_escape
44
+
45
+ # A utility method for escaping HTML without affecting existing escaped entities.
46
+ #
47
+ # html_escape_once('1 < 2 &amp; 3')
48
+ # # => "1 &lt; 2 &amp; 3"
49
+ #
50
+ # html_escape_once('&lt;&lt; Accept & Checkout')
51
+ # # => "&lt;&lt; Accept &amp; Checkout"
52
+ def html_escape_once(s)
53
+ result = CoreExt::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
54
+ s.html_safe? ? result.html_safe : result
55
+ end
56
+
57
+ module_function :html_escape_once
58
+
59
+ # A utility method for escaping HTML entities in JSON strings. Specifically, the
60
+ # &, > and < characters are replaced with their equivalent unicode escaped form -
61
+ # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
62
+ # escaped as they are treated as newline characters in some JavaScript engines.
63
+ # These sequences have identical meaning as the original characters inside the
64
+ # context of a JSON string, so assuming the input is a valid and well-formed
65
+ # JSON value, the output will have equivalent meaning when parsed:
66
+ #
67
+ # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
68
+ # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
69
+ #
70
+ # json_escape(json)
71
+ # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
72
+ #
73
+ # JSON.parse(json) == JSON.parse(json_escape(json))
74
+ # # => true
75
+ #
76
+ # The intended use case for this method is to escape JSON strings before including
77
+ # them inside a script tag to avoid XSS vulnerability:
78
+ #
79
+ # <script>
80
+ # var currentUser = <%= raw json_escape(current_user.to_json) %>;
81
+ # </script>
82
+ #
83
+ # It is necessary to +raw+ the result of +json_escape+, so that quotation marks
84
+ # don't get converted to <tt>&quot;</tt> entities. +json_escape+ doesn't
85
+ # automatically flag the result as HTML safe, since the raw value is unsafe to
86
+ # use inside HTML attributes.
87
+ #
88
+ # If your JSON is being used downstream for insertion into the DOM, be aware of
89
+ # whether or not it is being inserted via +html()+. Most jQuery plugins do this.
90
+ # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
91
+ # content returned by your JSON.
92
+ #
93
+ # If you need to output JSON elsewhere in your HTML, you can just do something
94
+ # like this, as any unsafe characters (including quotation marks) will be
95
+ # automatically escaped for you:
96
+ #
97
+ # <div data-user-info="<%= current_user.to_json %>">...</div>
98
+ #
99
+ # WARNING: this helper only works with valid JSON. Using this on non-JSON values
100
+ # will open up serious XSS vulnerabilities. For example, if you replace the
101
+ # +current_user.to_json+ in the example above with user input instead, the browser
102
+ # will happily eval() that string as JavaScript.
103
+ #
104
+ # The escaping performed in this method is identical to those performed in the
105
+ # Active Support JSON encoder when +CoreExt.escape_html_entities_in_json+ is
106
+ # set to true. Because this transformation is idempotent, this helper can be
107
+ # applied even if +CoreExt.escape_html_entities_in_json+ is already true.
108
+ #
109
+ # Therefore, when you are unsure if +CoreExt.escape_html_entities_in_json+
110
+ # is enabled, or if you are unsure where your JSON string originated from, it
111
+ # is recommended that you always apply this helper (other libraries, such as the
112
+ # JSON gem, do not provide this kind of protection by default; also some gems
113
+ # might override +to_json+ to bypass Active Support's encoder).
114
+ def json_escape(s)
115
+ result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
116
+ s.html_safe? ? result.html_safe : result
117
+ end
118
+
119
+ module_function :json_escape
120
+ end
121
+ end
122
+
123
+ class Object
124
+ def html_safe?
125
+ false
126
+ end
127
+ end
128
+
129
+ class Numeric
130
+ def html_safe?
131
+ true
132
+ end
133
+ end
134
+
135
+ module CoreExt #:nodoc:
136
+ class SafeBuffer < String
137
+ UNSAFE_STRING_METHODS = %w(
138
+ capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
139
+ slice squeeze strip sub succ swapcase tr tr_s upcase
140
+ )
141
+
142
+ alias_method :original_concat, :concat
143
+ private :original_concat
144
+
145
+ class SafeConcatError < StandardError
146
+ def initialize
147
+ super 'Could not concatenate to the buffer because it is not html safe.'
148
+ end
149
+ end
150
+
151
+ def [](*args)
152
+ if args.size < 2
153
+ super
154
+ else
155
+ if html_safe?
156
+ new_safe_buffer = super
157
+
158
+ if new_safe_buffer
159
+ new_safe_buffer.instance_variable_set :@html_safe, true
160
+ end
161
+
162
+ new_safe_buffer
163
+ else
164
+ to_str[*args]
165
+ end
166
+ end
167
+ end
168
+
169
+ def safe_concat(value)
170
+ raise SafeConcatError unless html_safe?
171
+ original_concat(value)
172
+ end
173
+
174
+ def initialize(*)
175
+ @html_safe = true
176
+ super
177
+ end
178
+
179
+ def initialize_copy(other)
180
+ super
181
+ @html_safe = other.html_safe?
182
+ end
183
+
184
+ def clone_empty
185
+ self[0, 0]
186
+ end
187
+
188
+ def concat(value)
189
+ super(html_escape_interpolated_argument(value))
190
+ end
191
+ alias << concat
192
+
193
+ def prepend(value)
194
+ super(html_escape_interpolated_argument(value))
195
+ end
196
+
197
+ def +(other)
198
+ dup.concat(other)
199
+ end
200
+
201
+ def %(args)
202
+ case args
203
+ when Hash
204
+ escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }]
205
+ else
206
+ escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
207
+ end
208
+
209
+ self.class.new(super(escaped_args))
210
+ end
211
+
212
+ def html_safe?
213
+ defined?(@html_safe) && @html_safe
214
+ end
215
+
216
+ def to_s
217
+ self
218
+ end
219
+
220
+ def to_param
221
+ to_str
222
+ end
223
+
224
+ def encode_with(coder)
225
+ coder.represent_object nil, to_str
226
+ end
227
+
228
+ UNSAFE_STRING_METHODS.each do |unsafe_method|
229
+ if unsafe_method.respond_to?(unsafe_method)
230
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
231
+ def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
232
+ to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
233
+ end # end
234
+
235
+ def #{unsafe_method}!(*args) # def capitalize!(*args)
236
+ @html_safe = false # @html_safe = false
237
+ super # super
238
+ end # end
239
+ EOT
240
+ end
241
+ end
242
+
243
+ private
244
+
245
+ def html_escape_interpolated_argument(arg)
246
+ (!html_safe? || arg.html_safe?) ? arg :
247
+ arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE)
248
+ end
249
+ end
250
+ end
251
+
252
+ class String
253
+ # Marks a string as trusted safe. It will be inserted into HTML with no
254
+ # additional escaping performed. It is your responsibilty to ensure that the
255
+ # string contains no malicious content. This method is equivalent to the
256
+ # `raw` helper in views. It is recommended that you use `sanitize` instead of
257
+ # this method. It should never be called on user input.
258
+ def html_safe
259
+ CoreExt::SafeBuffer.new(self)
260
+ end
261
+ end
@@ -0,0 +1,4 @@
1
+ class String
2
+ alias_method :starts_with?, :start_with?
3
+ alias_method :ends_with?, :end_with?
4
+ end
@@ -0,0 +1,23 @@
1
+ class String
2
+ # Strips indentation in heredocs.
3
+ #
4
+ # For example in
5
+ #
6
+ # if options[:usage]
7
+ # puts <<-USAGE.strip_heredoc
8
+ # This command does such and such.
9
+ #
10
+ # Supported options are:
11
+ # -h This message
12
+ # ...
13
+ # USAGE
14
+ # end
15
+ #
16
+ # the user would see the usage message aligned against the left margin.
17
+ #
18
+ # Technically, it looks for the least indented non-empty line
19
+ # in the whole string, and removes that amount of leading whitespace.
20
+ def strip_heredoc
21
+ gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ require 'core_ext/string/conversions'
2
+ require 'core_ext/time/zones'
3
+
4
+ class String
5
+ # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default
6
+ # is set, otherwise converts String to a Time via String#to_time
7
+ def in_time_zone(zone = ::Time.zone)
8
+ if zone
9
+ ::Time.find_zone!(zone).parse(self)
10
+ else
11
+ to_time
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ require 'core_ext/string/conversions'
2
+ require 'core_ext/string/filters'
3
+ require 'core_ext/string/multibyte'
4
+ require 'core_ext/string/starts_ends_with'
5
+ require 'core_ext/string/inflections'
6
+ require 'core_ext/string/access'
7
+ require 'core_ext/string/behavior'
8
+ require 'core_ext/string/output_safety'
9
+ require 'core_ext/string/exclude'
10
+ require 'core_ext/string/strip'
11
+ require 'core_ext/string/inquiry'
12
+ require 'core_ext/string/indent'
13
+ require 'core_ext/string/zones'
@@ -0,0 +1,26 @@
1
+ module CoreExt
2
+ # Wrapping a string in this class gives you a prettier way to test
3
+ # for equality. The value returned by <tt>Rails.env</tt> is wrapped
4
+ # in a StringInquirer object, so instead of calling this:
5
+ #
6
+ # Rails.env == 'production'
7
+ #
8
+ # you can call this:
9
+ #
10
+ # Rails.env.production?
11
+ class StringInquirer < String
12
+ private
13
+
14
+ def respond_to_missing?(method_name, include_private = false)
15
+ method_name[-1] == '?'
16
+ end
17
+
18
+ def method_missing(method_name, *arguments)
19
+ if method_name[-1] == '?'
20
+ self == method_name[0..-2]
21
+ else
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,78 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'logger'
4
+ require 'active_support/logger'
5
+
6
+ module ActiveSupport
7
+ # Wraps any standard Logger object to provide tagging capabilities.
8
+ #
9
+ # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
10
+ # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
11
+ # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
12
+ # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
13
+ #
14
+ # This is used by the default Rails.logger as configured by Railties to make
15
+ # it easy to stamp log lines with subdomains, request ids, and anything else
16
+ # to aid debugging of multi-user production applications.
17
+ module TaggedLogging
18
+ module Formatter # :nodoc:
19
+ # This method is invoked when a log event occurs.
20
+ def call(severity, timestamp, progname, msg)
21
+ super(severity, timestamp, progname, "#{tags_text}#{msg}")
22
+ end
23
+
24
+ def tagged(*tags)
25
+ new_tags = push_tags(*tags)
26
+ yield self
27
+ ensure
28
+ pop_tags(new_tags.size)
29
+ end
30
+
31
+ def push_tags(*tags)
32
+ tags.flatten.reject(&:blank?).tap do |new_tags|
33
+ current_tags.concat new_tags
34
+ end
35
+ end
36
+
37
+ def pop_tags(size = 1)
38
+ current_tags.pop size
39
+ end
40
+
41
+ def clear_tags!
42
+ current_tags.clear
43
+ end
44
+
45
+ def current_tags
46
+ # We use our object ID here to avoid conflicting with other instances
47
+ thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze
48
+ Thread.current[thread_key] ||= []
49
+ end
50
+
51
+ private
52
+ def tags_text
53
+ tags = current_tags
54
+ if tags.any?
55
+ tags.collect { |tag| "[#{tag}] " }.join
56
+ end
57
+ end
58
+ end
59
+
60
+ def self.new(logger)
61
+ # Ensure we set a default formatter so we aren't extending nil!
62
+ logger.formatter ||= ActiveSupport::Logger::SimpleFormatter.new
63
+ logger.formatter.extend Formatter
64
+ logger.extend(self)
65
+ end
66
+
67
+ delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter
68
+
69
+ def tagged(*tags)
70
+ formatter.tagged(*tags) { yield self }
71
+ end
72
+
73
+ def flush
74
+ clear_tags!
75
+ super if defined?(super)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,88 @@
1
+ gem 'minitest' # make sure we get the gem, not stdlib
2
+
3
+ require 'minitest'
4
+ require 'core_ext/testing/tagged_logging'
5
+ require 'core_ext/testing/setup_and_teardown'
6
+ require 'core_ext/testing/assertions'
7
+ require 'core_ext/testing/deprecation'
8
+ require 'core_ext/testing/declarative'
9
+ require 'core_ext/testing/isolation'
10
+ require 'core_ext/testing/constant_lookup'
11
+ require 'core_ext/testing/time_helpers'
12
+ require 'core_ext/testing/file_fixtures'
13
+ require 'core_ext/testing/composite_filter'
14
+ require 'core_ext/kernel/reporting'
15
+
16
+ module CoreExt
17
+ class TestCase < ::Minitest::Test
18
+ Assertion = Minitest::Assertion
19
+
20
+ class << self
21
+ # Sets the order in which test cases are run.
22
+ #
23
+ # CoreExt::TestCase.test_order = :random # => :random
24
+ #
25
+ # Valid values are:
26
+ # * +:random+ (to run tests in random order)
27
+ # * +:parallel+ (to run tests in parallel)
28
+ # * +:sorted+ (to run tests alphabetically by method name)
29
+ # * +:alpha+ (equivalent to +:sorted+)
30
+ def test_order=(new_order)
31
+ CoreExt.test_order = new_order
32
+ end
33
+
34
+ # Returns the order in which test cases are run.
35
+ #
36
+ # CoreExt::TestCase.test_order # => :random
37
+ #
38
+ # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
39
+ # Defaults to +:random+.
40
+ def test_order
41
+ CoreExt.test_order ||= :random
42
+ end
43
+
44
+ def run(reporter, options = {})
45
+ if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
46
+ options[:filter] = \
47
+ Testing::CompositeFilter.new(self, options[:filter], options[:patterns])
48
+ end
49
+
50
+ super
51
+ end
52
+ end
53
+
54
+ alias_method :method_name, :name
55
+
56
+ include CoreExt::Testing::TaggedLogging
57
+ include CoreExt::Testing::SetupAndTeardown
58
+ include CoreExt::Testing::Assertions
59
+ include CoreExt::Testing::TimeHelpers
60
+ include CoreExt::Testing::FileFixtures
61
+ extend CoreExt::Testing::Declarative
62
+
63
+ # test/unit backwards compatibility methods
64
+ alias :assert_raise :assert_raises
65
+ alias :assert_not_empty :refute_empty
66
+ alias :assert_not_equal :refute_equal
67
+ alias :assert_not_in_delta :refute_in_delta
68
+ alias :assert_not_in_epsilon :refute_in_epsilon
69
+ alias :assert_not_includes :refute_includes
70
+ alias :assert_not_instance_of :refute_instance_of
71
+ alias :assert_not_kind_of :refute_kind_of
72
+ alias :assert_no_match :refute_match
73
+ alias :assert_not_nil :refute_nil
74
+ alias :assert_not_operator :refute_operator
75
+ alias :assert_not_predicate :refute_predicate
76
+ alias :assert_not_respond_to :refute_respond_to
77
+ alias :assert_not_same :refute_same
78
+
79
+ # Reveals the intention that the block should not raise any exception.
80
+ #
81
+ # assert_nothing_raised do
82
+ # ...
83
+ # end
84
+ def assert_nothing_raised(*args)
85
+ yield
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,99 @@
1
+ require 'core_ext/object/blank'
2
+
3
+ module CoreExt
4
+ module Testing
5
+ module Assertions
6
+ # Asserts that an expression is not truthy. Passes if <tt>object</tt> is
7
+ # +nil+ or +false+. "Truthy" means "considered true in a conditional"
8
+ # like <tt>if foo</tt>.
9
+ #
10
+ # assert_not nil # => true
11
+ # assert_not false # => true
12
+ # assert_not 'foo' # => Expected "foo" to be nil or false
13
+ #
14
+ # An error message can be specified.
15
+ #
16
+ # assert_not foo, 'foo should be false'
17
+ def assert_not(object, message = nil)
18
+ message ||= "Expected #{mu_pp(object)} to be nil or false"
19
+ assert !object, message
20
+ end
21
+
22
+ # Test numeric difference between the return value of an expression as a
23
+ # result of what is evaluated in the yielded block.
24
+ #
25
+ # assert_difference 'Article.count' do
26
+ # post :create, params: { article: {...} }
27
+ # end
28
+ #
29
+ # An arbitrary expression is passed in and evaluated.
30
+ #
31
+ # assert_difference 'Article.last.comments(:reload).size' do
32
+ # post :create, params: { comment: {...} }
33
+ # end
34
+ #
35
+ # An arbitrary positive or negative difference can be specified.
36
+ # The default is <tt>1</tt>.
37
+ #
38
+ # assert_difference 'Article.count', -1 do
39
+ # post :delete, params: { id: ... }
40
+ # end
41
+ #
42
+ # An array of expressions can also be passed in and evaluated.
43
+ #
44
+ # assert_difference [ 'Article.count', 'Post.count' ], 2 do
45
+ # post :create, params: { article: {...} }
46
+ # end
47
+ #
48
+ # A lambda or a list of lambdas can be passed in and evaluated:
49
+ #
50
+ # assert_difference ->{ Article.count }, 2 do
51
+ # post :create, params: { article: {...} }
52
+ # end
53
+ #
54
+ # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
55
+ # post :create, params: { article: {...} }
56
+ # end
57
+ #
58
+ # An error message can be specified.
59
+ #
60
+ # assert_difference 'Article.count', -1, 'An Article should be destroyed' do
61
+ # post :delete, params: { id: ... }
62
+ # end
63
+ def assert_difference(expression, difference = 1, message = nil, &block)
64
+ expressions = Array(expression)
65
+
66
+ exps = expressions.map { |e|
67
+ e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
68
+ }
69
+ before = exps.map(&:call)
70
+
71
+ retval = yield
72
+
73
+ expressions.zip(exps).each_with_index do |(code, e), i|
74
+ error = "#{code.inspect} didn't change by #{difference}"
75
+ error = "#{message}.\n#{error}" if message
76
+ assert_equal(before[i] + difference, e.call, error)
77
+ end
78
+
79
+ retval
80
+ end
81
+
82
+ # Assertion that the numeric result of evaluating an expression is not
83
+ # changed before and after invoking the passed in block.
84
+ #
85
+ # assert_no_difference 'Article.count' do
86
+ # post :create, params: { article: invalid_attributes }
87
+ # end
88
+ #
89
+ # An error message can be specified.
90
+ #
91
+ # assert_no_difference 'Article.count', 'An Article should not be created' do
92
+ # post :create, params: { article: invalid_attributes }
93
+ # end
94
+ def assert_no_difference(expression, message = nil, &block)
95
+ assert_difference expression, 0, message, &block
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,12 @@
1
+ gem 'minitest'
2
+
3
+ require 'minitest'
4
+
5
+ if Minitest.respond_to?(:run_with_rails_extension)
6
+ unless Minitest.run_with_rails_extension
7
+ Minitest.run_with_autorun = true
8
+ Minitest.autorun
9
+ end
10
+ else
11
+ Minitest.autorun
12
+ end
@@ -0,0 +1,54 @@
1
+ require 'method_source'
2
+
3
+ module CoreExt
4
+ module Testing
5
+ class CompositeFilter # :nodoc:
6
+ def initialize(runnable, filter, patterns)
7
+ @runnable = runnable
8
+ @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
9
+ end
10
+
11
+ def ===(method)
12
+ @filters.any? { |filter| filter === method }
13
+ end
14
+
15
+ private
16
+ def derive_regexp(filter)
17
+ filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
18
+ end
19
+
20
+ def derive_line_filters(patterns)
21
+ patterns.map do |file_and_line|
22
+ file, line = file_and_line.split(':')
23
+ Filter.new(@runnable, file, line) if file
24
+ end
25
+ end
26
+
27
+ class Filter # :nodoc:
28
+ def initialize(runnable, file, line)
29
+ @runnable, @file = runnable, File.expand_path(file)
30
+ @line = line.to_i if line
31
+ end
32
+
33
+ def ===(method)
34
+ return unless @runnable.method_defined?(method)
35
+
36
+ if @line
37
+ test_file, test_range = definition_for(@runnable.instance_method(method))
38
+ test_file == @file && test_range.include?(@line)
39
+ else
40
+ @runnable.instance_method(method).source_location.first == @file
41
+ end
42
+ end
43
+
44
+ private
45
+ def definition_for(method)
46
+ file, start_line = method.source_location
47
+ end_line = method.source.count("\n") + start_line - 1
48
+
49
+ return file, start_line..end_line
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end