hanami-utils 0.0.0 → 0.7.0

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