hanami-utils 0.7.2 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4cbb5f4adac5f75b2e56d9cb8bc2f2a5b91cf5e5
4
- data.tar.gz: da1b1e8502a4b5fd322bfd9ee660d8b4a1c8d9c8
3
+ metadata.gz: e0ec07bde18ad072abdc5ba80712ccf9976d4139
4
+ data.tar.gz: 98916abdaf10d4a18848745fe00ba6b497a3e521
5
5
  SHA512:
6
- metadata.gz: 268645d9ea4cb481333cee4315e51e466c3d1900417622d69139f02111a7a0cde5a9e36714a69ddb5c942a51c903420793d7421018a46440c0f7b483563f43ca
7
- data.tar.gz: 3784235c1726f07be68e41a8f1d7ae67f0e0dbf4c760bb20f6dfaac994e2146c946042f903eb8549a742b0ce5ae94af77348bdac2dc7d86c59b12ee5089e0783
6
+ metadata.gz: 459b2bcf58fd4416d95421ed9d0af9995096caef9a32975a51ca69ebd771a8ba4352935f76f26702a1c248a7af256c01e0683101410800f02808761da548eaf7
7
+ data.tar.gz: 475a12099d5f21b95ccc7965571351e98f8b6eb55a46343952483eac7fdb3cb3f51daaa6818b99c5e991696caeda3a2bcb3910c078deb64e14a82e91f7612f0c
@@ -1,9 +1,22 @@
1
1
  # Hanami::Utils
2
2
  Ruby core extentions and class utilities for Hanami
3
3
 
4
- ## v0.7.2 - 2016-05-20
4
+ ## v0.8.0 - 2016-07-22
5
+ ### Added
6
+ - [Andrey Morskov] Introduced `Hanami::Utils::Blank`
7
+ - [Anton Davydov] Allow to specify a default log level for `Hanami::Logger`
8
+ - [Anton Davydov] Introduced default and JSON formatters for `Hanami::Logger`
9
+ - [Erol Fornoles] Allow deep indifferent access for `Hanami::Utils::Attributes`
10
+ - [Anton Davydov] Introduced `Hanami::Utils::Json` which is a proxy for `MultiJson` (from `multi_json` gem), or fallback to `JSON` from Ruby standard library.
11
+
5
12
  ### Fixed
6
- - [Luca Guidi] Make `Hanami::Utils::Kernel.BigDecimal` to convert negative `BigDecimal` or `BigDecimal` with negative exponent.
13
+
14
+ - [Hiếu Nguyễn] Ensure `Hanami::Utils::String#classify` to return already classified strings as they are. Eg. `"AwesomeProject"` should return `"AwesomeProject"`, not `"Awesomeproject"`.
15
+ - [TheSmartnik] Fix English pluralization for words ending with `"rses"`
16
+ - [Rogério Ramos] Fix English pluralization for words ending with `"ice"`
17
+
18
+ ### Changed
19
+ - [Luca Guidi] Drop support for Ruby 2.0, 2.1 and Rubinius. Official support for JRuby 9.0.5.0+.
7
20
 
8
21
  ## v0.7.1 - 2016-02-05
9
22
  ### Fixed
data/README.md CHANGED
@@ -22,7 +22,7 @@ Ruby core extentions and class utilities for [Hanami](http://hanamirb.org)
22
22
 
23
23
  ## Rubies
24
24
 
25
- __Hanami::Utils__ supports Ruby (MRI) 2.2+, JRuby 9k+
25
+ __Hanami::Utils__ supports Ruby (MRI) 2.2+, JRuby 9.0.5.0+
26
26
 
27
27
  ## Installation
28
28
 
@@ -63,6 +63,10 @@ Set of attributes with indifferent access. [[API doc](http://www.rubydoc.info/ge
63
63
 
64
64
  Enhanced version of Ruby's `BasicObject`. [[API doc](http://www.rubydoc.info/gems/hanami-utils/Hanami/Utils/BasicObject)]
65
65
 
66
+ ### Hanami::Utils::Blank
67
+
68
+ Checks for blank. [[API doc](http://www.rubydoc.info/gems/hanami-utils/Hanami/Utils/Blank)]
69
+
66
70
  ### Hanami::Utils::Callbacks
67
71
 
68
72
  Callbacks to decorate methods with `before` and `after` logic. It supports polymorphic callbacks (methods and procs). [[API doc](http://www.rubydoc.info/gems/hanami-utils/Hanami/Utils/Callbacks)]
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Hanami::Utils::VERSION
9
9
  spec.authors = ['Luca Guidi', 'Trung Lê', 'Alfonso Uceda']
10
10
  spec.email = ['me@lucaguidi.com', 'trung.le@ruby-journal.com', 'uceda73@gmail.com']
11
- spec.description = %q{Hanami utilities}
12
- spec.summary = %q{Ruby core extentions and Hanami utilities}
11
+ spec.description = 'Hanami utilities'
12
+ spec.summary = 'Ruby core extentions and Hanami utilities'
13
13
  spec.homepage = 'http://hanamirb.org'
14
14
  spec.license = 'MIT'
15
15
 
@@ -17,9 +17,8 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
- spec.required_ruby_version = '>= 2.0.0'
20
+ spec.required_ruby_version = '>= 2.2.0'
21
21
 
22
- spec.add_development_dependency 'bundler', '~> 1.6'
23
- spec.add_development_dependency 'rake', '~> 10'
24
- spec.add_development_dependency 'minitest', '~> 5.4'
22
+ spec.add_development_dependency 'bundler', '~> 1.6'
23
+ spec.add_development_dependency 'rake', '~> 11'
25
24
  end
@@ -1 +1 @@
1
- require 'hanami/utils'
1
+ require 'hanami/utils' # rubocop:disable Style/FileName
@@ -17,7 +17,7 @@ module Hanami
17
17
  # @api private
18
18
  #
19
19
  # @see Hanami::Interactor::Result#respond_to_missing?
20
- METHODS = {initialize: true, success?: true, fail!: true, prepare!: true, errors: true, error: true}.freeze
20
+ METHODS = ::Hash[initialize: true, success?: true, fail!: true, prepare!: true, errors: true, error: true].freeze
21
21
 
22
22
  # Initialize a new result
23
23
  #
@@ -97,6 +97,7 @@ module Hanami
97
97
  end
98
98
 
99
99
  protected
100
+
100
101
  # @since 0.3.5
101
102
  # @api private
102
103
  def method_missing(m, *)
@@ -105,7 +106,7 @@ module Hanami
105
106
 
106
107
  # @since 0.3.5
107
108
  # @api private
108
- def respond_to_missing?(method_name, include_all)
109
+ def respond_to_missing?(method_name, _include_all)
109
110
  method_name = method_name.to_sym
110
111
  METHODS[method_name] || @payload.key?(method_name)
111
112
  end
@@ -119,7 +120,7 @@ module Hanami
119
120
  # @since 0.3.5
120
121
  # @api private
121
122
  def __inspect
122
- " @success=#{ @success } @payload=#{ @payload.inspect }"
123
+ " @success=#{@success} @payload=#{@payload.inspect}"
123
124
  end
124
125
  end
125
126
 
@@ -251,6 +252,7 @@ module Hanami
251
252
  end
252
253
 
253
254
  private
255
+
254
256
  # Check if proceed with <tt>#call</tt> invokation.
255
257
  # By default it returns <tt>true</tt>.
256
258
  #
@@ -459,7 +461,7 @@ module Hanami
459
461
  end
460
462
  end
461
463
 
462
- # Expose local instance variables into the returing value of <tt>#call</tt>
464
+ # Expose local instance variables into the returning value of <tt>#call</tt>
463
465
  #
464
466
  # @param instance_variable_names [Symbol,Array<Symbol>] one or more instance
465
467
  # variable names
@@ -490,7 +492,7 @@ module Hanami
490
492
  # result.params # => NoMethodError
491
493
  def expose(*instance_variable_names)
492
494
  instance_variable_names.each do |name|
493
- exposures[name] = "@#{ name }"
495
+ exposures[name] = "@#{name}"
494
496
  end
495
497
  end
496
498
  end
@@ -1,34 +1,51 @@
1
+ require 'set'
2
+ require 'json'
1
3
  require 'logger'
2
4
  require 'hanami/utils/string'
5
+ require 'hanami/utils/json'
6
+ require 'hanami/utils/class_attribute'
3
7
 
4
8
  module Hanami
5
9
  # Hanami logger
6
10
  #
7
11
  # Implement with the same interface of Ruby std lib `Logger`.
8
- # It uses `STDOUT` as output device.
9
- #
12
+ # It uses `STDOUT`, `STDERR`, file name or open file as output stream.
10
13
  #
11
14
  #
12
15
  # When a Hanami application is initialized, it creates a logger for that specific application.
13
16
  # For instance for a `Bookshelf::Application` a `Bookshelf::Logger` will be available.
14
17
  #
15
- # This is useful for auto-tagging the output. Eg (`[Booshelf]`).
16
- #
17
- # When used stand alone (eg. `Hanami::Logger.info`), it tags lines with `[Shared]`.
18
+ # This is useful for auto-tagging the output. Eg (`app=Booshelf`).
18
19
  #
20
+ # When used stand alone (eg. `Hanami::Logger.info`), it tags lines with `app=Shared`.
19
21
  #
20
22
  #
21
23
  # The available severity levels are the same of `Logger`:
22
24
  #
23
- # * debug
24
- # * error
25
- # * fatal
26
- # * info
27
- # * unknown
28
- # * warn
25
+ # * DEBUG
26
+ # * INFO
27
+ # * WARN
28
+ # * ERROR
29
+ # * FATAL
30
+ # * UNKNOWN
29
31
  #
30
32
  # Those levels are available both as class and instance methods.
31
33
  #
34
+ # Also Hanami::Logger support different formatters. Now available only two:
35
+ #
36
+ # * Formatter (default)
37
+ # * JSONFormatter
38
+ #
39
+ # And if you want to use custom formatter you need create new class inherited from
40
+ # `Formatter` class and define `_format` private method like this:
41
+ #
42
+ # class CustomFormatter < Formatter
43
+ # private
44
+ # def _format(hash)
45
+ # # ...
46
+ # end
47
+ # end
48
+ #
32
49
  # @since 0.5.0
33
50
  #
34
51
  # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html
@@ -47,34 +64,75 @@ module Hanami
47
64
  # # or
48
65
  # Bookshelf::Application.new
49
66
  #
50
- # Bookshelf::Logger.info('Hello')
51
- # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Bookshelf] : Hello
52
- #
53
67
  # Bookshelf::Logger.new.info('Hello')
54
- # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Bookshelf] : Hello
68
+ # # => app=Bookshelf severity=INFO time=1988-09-01 00:00:00 UTC message=Hello
55
69
  #
56
70
  # @example Standalone usage
57
- # require 'hanami'
58
- #
59
- # Hanami::Logger.info('Hello')
60
- # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Hanami] : Hello
71
+ # require 'hanami/logger'
61
72
  #
62
73
  # Hanami::Logger.new.info('Hello')
63
- # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Hanami] : Hello
74
+ # # => app=Hanami severity=INFO time=2016-05-27 10:14:42 UTC message=Hello
64
75
  #
65
76
  # @example Custom tagging
66
- # require 'hanami'
77
+ # require 'hanami/logger'
67
78
  #
68
79
  # Hanami::Logger.new('FOO').info('Hello')
69
- # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [FOO] : Hello
80
+ # # => app=FOO severity=INFO time=2016-05-27 10:14:42 UTC message=Hello
81
+ #
82
+ # @example Write to file
83
+ # require 'hanami'
84
+ #
85
+ # Hanami::Logger.new(stream: 'logfile.log').info('Hello')
86
+ # # in logfile.log
87
+ # # => app=FOO severity=INFO time=2016-05-27 10:14:42 UTC message=Hello
88
+ #
89
+ # @example Use JSON formatter
90
+ # require 'hanami'
91
+ #
92
+ # Hanami::Logger.new(formatter: Hanami::Logger::JSONFormatter).info('Hello')
93
+ # # => "{\"app\":\"Hanami\",\"severity\":\"INFO\",\"time\":\"1988-09-01 00:00:00 UTC\",\"message\":\"Hello\"}"
70
94
  class Logger < ::Logger
71
- # Hanami::Logger default formatter
95
+ # Hanami::Logger default formatter.
96
+ # This formatter returns string in key=value format.
72
97
  #
73
98
  # @since 0.5.0
74
99
  # @api private
75
100
  #
76
101
  # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Formatter.html
77
102
  class Formatter < ::Logger::Formatter
103
+ # @since 0.8.0
104
+ # @api private
105
+ SEPARATOR = ' '.freeze
106
+
107
+ # @since 0.8.0
108
+ # @api private
109
+ NEW_LINE = $/
110
+
111
+ include Utils::ClassAttribute
112
+
113
+ class_attribute :subclasses
114
+ self.subclasses = Set.new
115
+
116
+ def self.fabricate(formatter, application_name)
117
+ case formatter
118
+ when Symbol
119
+ (subclasses.find { |s| s.eligible?(formatter) } || self).new
120
+ when nil
121
+ new
122
+ else
123
+ formatter
124
+ end.tap { |f| f.application_name = application_name }
125
+ end
126
+
127
+ def self.inherited(subclass)
128
+ super
129
+ subclasses << subclass
130
+ end
131
+
132
+ def self.eligible?(name)
133
+ name == :default
134
+ end
135
+
78
136
  # @since 0.5.0
79
137
  # @api private
80
138
  attr_writer :application_name
@@ -83,9 +141,58 @@ module Hanami
83
141
  # @api private
84
142
  #
85
143
  # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Formatter.html#method-i-call
86
- def call(severity, time, progname, msg)
87
- progname = "[#{@application_name}] #{progname}"
88
- super(severity, time.utc, progname, msg)
144
+ def call(severity, time, _progname, msg)
145
+ _format({
146
+ app: @application_name,
147
+ severity: severity,
148
+ time: time.utc
149
+ }.merge(
150
+ _message_hash(msg)
151
+ ))
152
+ end
153
+
154
+ private
155
+
156
+ # @since 0.8.0
157
+ # @api private
158
+ def _message_hash(message) # rubocop:disable Metrics/MethodLength
159
+ case message
160
+ when Hash
161
+ message
162
+ when Exception
163
+ Hash[
164
+ message: message.message,
165
+ backtrace: message.backtrace || [],
166
+ error: message.class
167
+ ]
168
+ else
169
+ Hash[message: message]
170
+ end
171
+ end
172
+
173
+ # @since 0.8.0
174
+ # @api private
175
+ def _format(hash)
176
+ hash.map { |k, v| "#{k}=#{v}" }.join(SEPARATOR) + NEW_LINE
177
+ end
178
+ end
179
+
180
+ # Hanami::Logger JSON formatter.
181
+ # This formatter returns string in JSON format.
182
+ #
183
+ # @since 0.5.0
184
+ # @api private
185
+ class JSONFormatter < Formatter
186
+ def self.eligible?(name)
187
+ name == :json
188
+ end
189
+
190
+ private
191
+
192
+ # @since 0.8.0
193
+ # @api private
194
+ def _format(hash)
195
+ Hanami::Utils::Json.dump(hash)
89
196
  end
90
197
  end
91
198
 
@@ -96,6 +203,17 @@ module Hanami
96
203
  # @api private
97
204
  DEFAULT_APPLICATION_NAME = 'Hanami'.freeze
98
205
 
206
+ # @since 0.8.0
207
+ # @api private
208
+ LEVELS = Hash[
209
+ 'debug' => DEBUG,
210
+ 'info' => INFO,
211
+ 'warn' => WARN,
212
+ 'error' => ERROR,
213
+ 'fatal' => FATAL,
214
+ 'unknown' => UNKNOWN
215
+ ].freeze
216
+
99
217
  # @since 0.5.0
100
218
  # @api private
101
219
  attr_writer :application_name
@@ -105,12 +223,17 @@ module Hanami
105
223
  # @param application_name [String] an optional application name used for
106
224
  # tagging purposes
107
225
  #
226
+ # @param stream [String, IO, StringIO, Pathname] an optional log stream. This is a filename
227
+ # (String) or IO object (typically STDOUT, STDERR, or an open file).
228
+ #
108
229
  # @since 0.5.0
109
- def initialize(application_name = nil)
110
- super(STDOUT)
230
+ def initialize(application_name = nil, stream: STDOUT, level: DEBUG, formatter: nil)
231
+ super(stream)
111
232
 
233
+ @level = _level(level)
234
+ @stream = stream
112
235
  @application_name = application_name
113
- @formatter = Hanami::Logger::Formatter.new.tap { |f| f.application_name = self.application_name }
236
+ @formatter = Formatter.fabricate(formatter, self.application_name)
114
237
  end
115
238
 
116
239
  # Returns the current application name, this is used for tagging purposes
@@ -122,14 +245,28 @@ module Hanami
122
245
  @application_name || _application_name_from_namespace || _default_application_name
123
246
  end
124
247
 
248
+ # @since 0.8.0
249
+ # @api private
250
+ def level=(value)
251
+ super _level(value)
252
+ end
253
+
254
+ # Close the logging stream if this stream isn't an STDOUT
255
+ #
256
+ # @since 0.8.0
257
+ def close
258
+ super unless [STDOUT, $stdout].include?(@stream)
259
+ end
260
+
125
261
  private
262
+
126
263
  # @since 0.5.0
127
264
  # @api private
128
265
  def _application_name_from_namespace
129
266
  class_name = self.class.name
130
267
  namespace = Utils::String.new(class_name).namespace
131
268
 
132
- class_name != namespace and return namespace
269
+ class_name != namespace and return namespace # rubocop:disable Style/AndOr
133
270
  end
134
271
 
135
272
  # @since 0.5.0
@@ -137,5 +274,16 @@ module Hanami
137
274
  def _default_application_name
138
275
  DEFAULT_APPLICATION_NAME
139
276
  end
277
+
278
+ # @since 0.8.0
279
+ # @api private
280
+ def _level(level)
281
+ case level
282
+ when DEBUG..UNKNOWN
283
+ level
284
+ else
285
+ LEVELS.fetch(level.to_s.downcase, DEBUG)
286
+ end
287
+ end
140
288
  end
141
289
  end
@@ -66,11 +66,20 @@ module Hanami
66
66
  # attributes[:unknown] # => nil
67
67
  # attributes['unknown'] # => nil
68
68
  def get(attribute)
69
- @attributes[attribute.to_s]
69
+ value = @attributes
70
+
71
+ keys = attribute.to_s.split('.')
72
+ keys.each do |key|
73
+ break unless value
74
+
75
+ value = value[key]
76
+ end
77
+
78
+ value.is_a?(Hash) ? self.class.new(value) : value
70
79
  end
71
80
 
72
81
  # @since 0.3.4
73
- alias_method :[], :get
82
+ alias [] get
74
83
 
75
84
  # Set the given value for the given attribute
76
85
  #