hanami-utils 0.0.0 → 0.7.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,141 @@
1
+ require 'logger'
2
+ require 'hanami/utils/string'
3
+
4
+ module Hanami
5
+ # Hanami logger
6
+ #
7
+ # Implement with the same interface of Ruby std lib `Logger`.
8
+ # It uses `STDOUT` as output device.
9
+ #
10
+ #
11
+ #
12
+ # When a Hanami application is initialized, it creates a logger for that specific application.
13
+ # For instance for a `Bookshelf::Application` a `Bookshelf::Logger` will be available.
14
+ #
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
+ #
19
+ #
20
+ #
21
+ # The available severity levels are the same of `Logger`:
22
+ #
23
+ # * debug
24
+ # * error
25
+ # * fatal
26
+ # * info
27
+ # * unknown
28
+ # * warn
29
+ #
30
+ # Those levels are available both as class and instance methods.
31
+ #
32
+ # @since 0.5.0
33
+ #
34
+ # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html
35
+ # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Severity.html
36
+ #
37
+ # @example Basic usage
38
+ # require 'hanami'
39
+ #
40
+ # module Bookshelf
41
+ # class Application < Hanami::Application
42
+ # end
43
+ # end
44
+ #
45
+ # # Initialize the application with the following code:
46
+ # Bookshelf::Application.load!
47
+ # # or
48
+ # Bookshelf::Application.new
49
+ #
50
+ # Bookshelf::Logger.info('Hello')
51
+ # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Bookshelf] : Hello
52
+ #
53
+ # Bookshelf::Logger.new.info('Hello')
54
+ # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Bookshelf] : Hello
55
+ #
56
+ # @example Standalone usage
57
+ # require 'hanami'
58
+ #
59
+ # Hanami::Logger.info('Hello')
60
+ # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Hanami] : Hello
61
+ #
62
+ # Hanami::Logger.new.info('Hello')
63
+ # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [Hanami] : Hello
64
+ #
65
+ # @example Custom tagging
66
+ # require 'hanami'
67
+ #
68
+ # Hanami::Logger.new('FOO').info('Hello')
69
+ # # => I, [2015-01-10T21:55:12.727259 #80487] INFO -- [FOO] : Hello
70
+ class Logger < ::Logger
71
+ # Hanami::Logger default formatter
72
+ #
73
+ # @since 0.5.0
74
+ # @api private
75
+ #
76
+ # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Formatter.html
77
+ class Formatter < ::Logger::Formatter
78
+ # @since 0.5.0
79
+ # @api private
80
+ attr_writer :application_name
81
+
82
+ # @since 0.5.0
83
+ # @api private
84
+ #
85
+ # @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)
89
+ end
90
+ end
91
+
92
+ # Default application name.
93
+ # This is used as a fallback for tagging purposes.
94
+ #
95
+ # @since 0.5.0
96
+ # @api private
97
+ DEFAULT_APPLICATION_NAME = 'Hanami'.freeze
98
+
99
+ # @since 0.5.0
100
+ # @api private
101
+ attr_writer :application_name
102
+
103
+ # Initialize a logger
104
+ #
105
+ # @param application_name [String] an optional application name used for
106
+ # tagging purposes
107
+ #
108
+ # @since 0.5.0
109
+ def initialize(application_name = nil)
110
+ super(STDOUT)
111
+
112
+ @application_name = application_name
113
+ @formatter = Hanami::Logger::Formatter.new.tap { |f| f.application_name = self.application_name }
114
+ end
115
+
116
+ # Returns the current application name, this is used for tagging purposes
117
+ #
118
+ # @return [String] the application name
119
+ #
120
+ # @since 0.5.0
121
+ def application_name
122
+ @application_name || _application_name_from_namespace || _default_application_name
123
+ end
124
+
125
+ private
126
+ # @since 0.5.0
127
+ # @api private
128
+ def _application_name_from_namespace
129
+ class_name = self.class.name
130
+ namespace = Utils::String.new(class_name).namespace
131
+
132
+ class_name != namespace and return namespace
133
+ end
134
+
135
+ # @since 0.5.0
136
+ # @api private
137
+ def _default_application_name
138
+ DEFAULT_APPLICATION_NAME
139
+ end
140
+ end
141
+ end
@@ -1,7 +1,36 @@
1
- require "hanami/utils/version"
1
+ require 'hanami/utils/version'
2
2
 
3
3
  module Hanami
4
+ # Ruby core extentions and Hanami utilities
5
+ #
6
+ # @since 0.1.0
4
7
  module Utils
5
- # Your code goes here...
8
+ # @since 0.3.1
9
+ # @api private
10
+ HANAMI_JRUBY = 'java'.freeze
11
+
12
+ # @since 0.3.1
13
+ # @api private
14
+ HANAMI_RUBINIUS = 'rbx'.freeze
15
+
16
+ # Checks if the current VM is JRuby
17
+ #
18
+ # @return [TrueClass,FalseClass] return if the VM is JRuby or not
19
+ #
20
+ # @since 0.3.1
21
+ # @api private
22
+ def self.jruby?
23
+ RUBY_PLATFORM == HANAMI_JRUBY
24
+ end
25
+
26
+ # Checks if the current VM is Rubinius
27
+ #
28
+ # @return [TrueClass,FalseClass] return if the VM is Rubinius or not
29
+ #
30
+ # @since 0.3.1
31
+ # @api private
32
+ def self.rubinius?
33
+ RUBY_ENGINE == HANAMI_RUBINIUS
34
+ end
6
35
  end
7
36
  end
@@ -0,0 +1,132 @@
1
+ require 'hanami/utils/hash'
2
+
3
+ module Hanami
4
+ module Utils
5
+ # A set of attributes.
6
+ #
7
+ # It internally stores the data as a Hash.
8
+ #
9
+ # All the operations convert keys to strings.
10
+ # This strategy avoids memory attacks due to Symbol abuses when parsing
11
+ # untrusted input.
12
+ #
13
+ # At the same time, this allows to get/set data with the original key or
14
+ # with the string representation. See the examples below.
15
+ #
16
+ # @since 0.3.2
17
+ class Attributes
18
+ # Initialize a set of attributes
19
+ # All the keys of the given Hash are recursively converted to strings.
20
+ #
21
+ # @param hash [#to_h] a Hash or any object that implements #to_h
22
+ #
23
+ # @return [Hanami::Utils::Attributes] self
24
+ #
25
+ # @since 0.3.2
26
+ #
27
+ # @example
28
+ # require 'hanami/utils/attributes'
29
+ #
30
+ # attributes = Hanami::Utils::Attributes.new(a: 1, b: { 2 => [3, 4] })
31
+ # attributes.to_h # => { "a" => 1, "b" => { "2" => [3, 4] } }
32
+ def initialize(hash = {})
33
+ @attributes = Utils::Hash.new(hash, &nil).stringify!
34
+ end
35
+
36
+ # Get the value associated with the given attribute
37
+ #
38
+ # @param attribute [#to_s] a String or any object that implements #to_s
39
+ #
40
+ # @return [Object,NilClass] the associated value, if present
41
+ #
42
+ # @since 0.3.2
43
+ #
44
+ # @example
45
+ # require 'hanami/utils/attributes'
46
+ #
47
+ # attributes = Hanami::Utils::Attributes.new(a: 1, 'b' => 2, 23 => 'foo')
48
+ #
49
+ # attributes.get(:a) # => 1
50
+ # attributes.get('a') # => 1
51
+ # attributes[:a] # => 1
52
+ # attributes['a'] # => 1
53
+ #
54
+ # attributes.get(:b) # => 2
55
+ # attributes.get('b') # => 2
56
+ # attributes[:b] # => 2
57
+ # attributes['b'] # => 2
58
+ #
59
+ # attributes.get(23) # => "foo"
60
+ # attributes.get('23') # => "foo"
61
+ # attributes[23] # => "foo"
62
+ # attributes['23'] # => "foo"
63
+ #
64
+ # attributes.get(:unknown) # => nil
65
+ # attributes.get('unknown') # => nil
66
+ # attributes[:unknown] # => nil
67
+ # attributes['unknown'] # => nil
68
+ def get(attribute)
69
+ @attributes[attribute.to_s]
70
+ end
71
+
72
+ # @since 0.3.4
73
+ alias_method :[], :get
74
+
75
+ # Set the given value for the given attribute
76
+ #
77
+ # @param attribute [#to_s] a String or any object that implements #to_s
78
+ # @param value [Object] any value
79
+ #
80
+ # @return [NilClass]
81
+ #
82
+ # @since 0.3.2
83
+ #
84
+ # @example
85
+ # require 'hanami/utils/attributes'
86
+ #
87
+ # attributes = Hanami::Utils::Attributes.new
88
+ #
89
+ # attributes.set(:a, 1)
90
+ # attributes.get(:a) # => 1
91
+ # attributes.get('a') # => 1
92
+ #
93
+ # attributes.set('b', 2)
94
+ # attributes.get(:b) # => 2
95
+ # attributes.get('b') # => 2
96
+ #
97
+ # attributes.set(23, 'foo')
98
+ # attributes.get(23) # => "foo"
99
+ # attributes.get('23') # => "foo"
100
+ def set(attribute, value)
101
+ @attributes[attribute.to_s] = value
102
+ nil
103
+ end
104
+
105
+ # Returns a deep duplicated copy of the attributes as a Hash
106
+ #
107
+ # @return [::Hash]
108
+ #
109
+ # @since 0.3.2
110
+ def to_h
111
+ ::Hash[].tap do |result|
112
+ @attributes.each do |k, v|
113
+ result[k] = _read_value(v)
114
+ end
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ # @since 0.4.1
121
+ # @api private
122
+ def _read_value(value)
123
+ case val = value
124
+ when ::Hash, ::Hanami::Utils::Hash, ->(v) { v.respond_to?(:hanami_nested_attributes?) }
125
+ val.to_h
126
+ else
127
+ val
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,53 @@
1
+ module Hanami
2
+ module Utils
3
+ # BasicObject
4
+ #
5
+ # @since 0.3.5
6
+ class BasicObject < ::BasicObject
7
+ # Return the class for debugging purposes.
8
+ #
9
+ # @since 0.3.5
10
+ #
11
+ # @see http://ruby-doc.org/core/Object.html#method-i-class
12
+ def class
13
+ (class << self; self end).superclass
14
+ end
15
+
16
+ # Bare minimum inspect for debugging purposes.
17
+ #
18
+ # @return [String] the inspect string
19
+ #
20
+ # @since 0.3.5
21
+ #
22
+ # @see http://ruby-doc.org/core/Object.html#method-i-inspect
23
+ def inspect
24
+ "#<#{ self.class }:#{'%x' % (__id__ << 1)}#{ __inspect }>"
25
+ end
26
+
27
+ # Returns true if responds to the given method.
28
+ #
29
+ # @return [TrueClass,FalseClass] the result of the check
30
+ #
31
+ # @since 0.3.5
32
+ #
33
+ # @see http://ruby-doc.org/core-2.2.1/Object.html#method-i-respond_to-3F
34
+ def respond_to?(method_name, include_all = false)
35
+ respond_to_missing?(method_name, include_all)
36
+ end
37
+
38
+ private
39
+ # Must be overridden by descendants
40
+ #
41
+ # @since 0.3.5
42
+ # @api private
43
+ def respond_to_missing?(method_name, include_all)
44
+ ::Kernel.raise ::NotImplementedError
45
+ end
46
+
47
+ # @since 0.3.5
48
+ # @api private
49
+ def __inspect
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,286 @@
1
+ module Hanami
2
+ module Utils
3
+ # Before and After callbacks
4
+ #
5
+ # @since 0.1.0
6
+ # @private
7
+ module Callbacks
8
+ # Series of callbacks to be executed
9
+ #
10
+ # @since 0.1.0
11
+ # @private
12
+ class Chain
13
+ # Return a new chain
14
+ #
15
+ # @return [Hanami::Utils::Callbacks::Chain]
16
+ #
17
+ # @since 0.2.0
18
+ def initialize
19
+ @chain = Array.new
20
+ end
21
+
22
+ # Appends the given callbacks to the end of the chain.
23
+ #
24
+ # @param callbacks [Array] one or multiple callbacks to append
25
+ # @param block [Proc] an optional block to be appended
26
+ #
27
+ # @return [void]
28
+ #
29
+ # @raise [RuntimeError] if the object was previously frozen
30
+ #
31
+ # @see #prepend
32
+ # @see #run
33
+ # @see Hanami::Utils::Callbacks::Callback
34
+ # @see Hanami::Utils::Callbacks::MethodCallback
35
+ # @see Hanami::Utils::Callbacks::Chain#freeze
36
+ #
37
+ # @since 0.3.4
38
+ #
39
+ # @example
40
+ # require 'hanami/utils/callbacks'
41
+ #
42
+ # chain = Hanami::Utils::Callbacks::Chain.new
43
+ #
44
+ # # Append a Proc to be used as a callback, it will be wrapped by `Callback`
45
+ # # The optional argument(s) correspond to the one passed when invoked the chain with `run`.
46
+ # chain.append { Authenticator.authenticate! }
47
+ # chain.append { |params| ArticleRepository.find(params[:id]) }
48
+ #
49
+ # # Append a Symbol as a reference to a method name that will be used as a callback.
50
+ # # It will wrapped by `MethodCallback`
51
+ # # If the #notificate method accepts some argument(s) they should be passed when `run` is invoked.
52
+ # chain.append :notificate
53
+ def append(*callbacks, &block)
54
+ callables(callbacks, block).each do |c|
55
+ @chain.push(c)
56
+ end
57
+
58
+ @chain.uniq!
59
+ end
60
+
61
+ # Prepends the given callbacks to the beginning of the chain.
62
+ #
63
+ # @param callbacks [Array] one or multiple callbacks to add
64
+ # @param block [Proc] an optional block to be added
65
+ #
66
+ # @return [void]
67
+ #
68
+ # @raise [RuntimeError] if the object was previously frozen
69
+ #
70
+ # @see #append
71
+ # @see #run
72
+ # @see Hanami::Utils::Callbacks::Callback
73
+ # @see Hanami::Utils::Callbacks::MethodCallback
74
+ # @see Hanami::Utils::Callbacks::Chain#freeze
75
+ #
76
+ # @since 0.3.4
77
+ #
78
+ # @example
79
+ # require 'hanami/utils/callbacks'
80
+ #
81
+ # chain = Hanami::Utils::Callbacks::Chain.new
82
+ #
83
+ # # Add a Proc to be used as a callback, it will be wrapped by `Callback`
84
+ # # The optional argument(s) correspond to the one passed when invoked the chain with `run`.
85
+ # chain.prepend { Authenticator.authenticate! }
86
+ # chain.prepend { |params| ArticleRepository.find(params[:id]) }
87
+ #
88
+ # # Add a Symbol as a reference to a method name that will be used as a callback.
89
+ # # It will wrapped by `MethodCallback`
90
+ # # If the #notificate method accepts some argument(s) they should be passed when `run` is invoked.
91
+ # chain.prepend :notificate
92
+ def prepend(*callbacks, &block)
93
+ callables(callbacks, block).each do |c|
94
+ @chain.unshift(c)
95
+ end
96
+
97
+ @chain.uniq!
98
+ end
99
+
100
+ # Runs all the callbacks in the chain.
101
+ # The only two ways to stop the execution are: `raise` or `throw`.
102
+ #
103
+ # @param context [Object] the context where we want the chain to be invoked.
104
+ # @param args [Array] the arguments that we want to pass to each single callback.
105
+ #
106
+ # @since 0.1.0
107
+ #
108
+ # @example
109
+ # require 'hanami/utils/callbacks'
110
+ #
111
+ # class Action
112
+ # private
113
+ # def authenticate!
114
+ # end
115
+ #
116
+ # def set_article(params)
117
+ # end
118
+ # end
119
+ #
120
+ # action = Action.new
121
+ # params = Hash[id: 23]
122
+ #
123
+ # chain = Hanami::Utils::Callbacks::Chain.new
124
+ # chain.append :authenticate!, :set_article
125
+ #
126
+ # chain.run(action, params)
127
+ #
128
+ # # `params` will only be passed as #set_article argument, because it has an arity greater than zero
129
+ #
130
+ #
131
+ #
132
+ # chain = Hanami::Utils::Callbacks::Chain.new
133
+ #
134
+ # chain.append do
135
+ # # some authentication logic
136
+ # end
137
+ #
138
+ # chain.append do |params|
139
+ # # some other logic that requires `params`
140
+ # end
141
+ #
142
+ # chain.run(action, params)
143
+ #
144
+ # Those callbacks will be invoked within the context of `action`.
145
+ def run(context, *args)
146
+ @chain.each do |callback|
147
+ callback.call(context, *args)
148
+ end
149
+ end
150
+
151
+ # It freezes the object by preventing further modifications.
152
+ #
153
+ # @since 0.2.0
154
+ #
155
+ # @see http://ruby-doc.org/core/Object.html#method-i-freeze
156
+ #
157
+ # @example
158
+ # require 'hanami/utils/callbacks'
159
+ #
160
+ # chain = Hanami::Utils::Callbacks::Chain.new
161
+ # chain.freeze
162
+ #
163
+ # chain.frozen? # => true
164
+ #
165
+ # chain.append :authenticate! # => RuntimeError
166
+ def freeze
167
+ super
168
+ @chain.freeze
169
+ end
170
+
171
+
172
+ private
173
+
174
+ def callables(callbacks, block)
175
+ callbacks.push(block) if block
176
+ callbacks.map { |c| Factory.fabricate(c) }
177
+ end
178
+
179
+ end
180
+
181
+ # Callback factory
182
+ #
183
+ # @since 0.1.0
184
+ # @private
185
+ class Factory
186
+ # Instantiates a `Callback` according to if it responds to #call.
187
+ #
188
+ # @param callback [Object] the object that needs to be wrapped
189
+ #
190
+ # @return [Callback, MethodCallback]
191
+ #
192
+ # @since 0.1.0
193
+ #
194
+ # @example
195
+ # require 'hanami/utils/callbacks'
196
+ #
197
+ # callable = Proc.new{} # it responds to #call
198
+ # method = :upcase # it doesn't responds to #call
199
+ #
200
+ # Hanami::Utils::Callbacks::Factory.fabricate(callable).class
201
+ # # => Hanami::Utils::Callbacks::Callback
202
+ #
203
+ # Hanami::Utils::Callbacks::Factory.fabricate(method).class
204
+ # # => Hanami::Utils::Callbacks::MethodCallback
205
+ def self.fabricate(callback)
206
+ if callback.respond_to?(:call)
207
+ Callback.new(callback)
208
+ else
209
+ MethodCallback.new(callback)
210
+ end
211
+ end
212
+ end
213
+
214
+ # Proc callback
215
+ # It wraps an object that responds to #call
216
+ #
217
+ # @since 0.1.0
218
+ # @private
219
+ class Callback
220
+ attr_reader :callback
221
+
222
+ # Initialize by wrapping the given callback
223
+ #
224
+ # @param callback [Object] the original callback that needs to be wrapped
225
+ #
226
+ # @return [Callback] self
227
+ #
228
+ # @since 0.1.0
229
+ #
230
+ def initialize(callback)
231
+ @callback = callback
232
+ end
233
+
234
+ # Executes the callback within the given context and passing the given arguments.
235
+ #
236
+ # @param context [Object] the context within we want to execute the callback.
237
+ # @param args [Array] an array of arguments that will be available within the execution.
238
+ #
239
+ # @return [void, Object] It may return a value, it depends on the callback.
240
+ #
241
+ # @since 0.1.0
242
+ #
243
+ # @see Hanami::Utils::Callbacks::Chain#run
244
+ def call(context, *args)
245
+ context.instance_exec(*args, &callback)
246
+ end
247
+ end
248
+
249
+ # Method callback
250
+ # It wraps a symbol or a string representing a method name that is implemented by the context within it will be called.
251
+ #
252
+ # @since 0.1.0
253
+ # @private
254
+ class MethodCallback < Callback
255
+ # Executes the callback within the given context and eventually passing the given arguments.
256
+ # Those arguments will be passed according to the arity of the target method.
257
+ #
258
+ # @param context [Object] the context within we want to execute the callback.
259
+ # @param args [Array] an array of arguments that will be available within the execution.
260
+ #
261
+ # @return [void, Object] It may return a value, it depends on the callback.
262
+ #
263
+ # @since 0.1.0
264
+ #
265
+ # @see Hanami::Utils::Callbacks::Chain#run
266
+ def call(context, *args)
267
+ method = context.method(callback)
268
+
269
+ if method.parameters.any?
270
+ method.call(*args)
271
+ else
272
+ method.call
273
+ end
274
+ end
275
+
276
+ def hash
277
+ callback.hash
278
+ end
279
+
280
+ def eql?(other)
281
+ hash == other.hash
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end