hanami-utils 0.7.2 → 0.8.0

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