hoodie 0.5.5 → 1.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +19 -0
- data/Rakefile +8 -23
- data/hoodie.gemspec +0 -23
- data/lib/hoodie.rb +40 -48
- data/lib/hoodie/configuration.rb +39 -2
- data/lib/hoodie/core_ext/blank.rb +6 -6
- data/lib/hoodie/core_ext/hash.rb +108 -13
- data/lib/hoodie/core_ext/string.rb +5 -3
- data/lib/hoodie/core_ext/try.rb +4 -3
- data/lib/hoodie/inflections.rb +18 -0
- data/lib/hoodie/inflections/defaults.rb +17 -0
- data/lib/hoodie/inflections/inflections.rb +17 -0
- data/lib/hoodie/inflections/rules_collection.rb +17 -0
- data/lib/hoodie/logging.rb +22 -4
- data/lib/hoodie/stash.rb +83 -80
- data/lib/hoodie/stash/disk_store.rb +142 -118
- data/lib/hoodie/stash/mem_store.rb +10 -9
- data/lib/hoodie/stash/memoizable.rb +46 -0
- data/lib/hoodie/utils.rb +13 -183
- data/lib/hoodie/utils/ansi.rb +199 -0
- data/lib/hoodie/utils/crypto.rb +288 -0
- data/lib/hoodie/utils/equalizer.rb +146 -0
- data/lib/hoodie/utils/file_helper.rb +225 -0
- data/lib/hoodie/utils/konstruktor.rb +77 -0
- data/lib/hoodie/utils/machine.rb +83 -0
- data/lib/hoodie/utils/os.rb +56 -0
- data/lib/hoodie/utils/retry.rb +235 -0
- data/lib/hoodie/utils/timeout.rb +54 -0
- data/lib/hoodie/utils/url_helper.rb +104 -0
- data/lib/hoodie/version.rb +4 -4
- metadata +13 -234
- data/lib/hoodie/identity_map.rb +0 -96
- data/lib/hoodie/memoizable.rb +0 -43
- data/lib/hoodie/obfuscate.rb +0 -121
- data/lib/hoodie/observable.rb +0 -309
- data/lib/hoodie/os.rb +0 -43
- data/lib/hoodie/proxy.rb +0 -68
- data/lib/hoodie/rash.rb +0 -125
- data/lib/hoodie/timers.rb +0 -355
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
# Konstruktor is a set of helpers that makes constructor definition less
|
21
|
+
# verbose.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# Turn this:
|
25
|
+
# class Foo
|
26
|
+
# attr_reader :foo, :bar, :baz
|
27
|
+
#
|
28
|
+
# def initialize(foo, bar, baz)
|
29
|
+
# @foo = foo
|
30
|
+
# @bar = bar
|
31
|
+
# @baz = baz
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def hello
|
35
|
+
# 'world'
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Into this:
|
40
|
+
# class Foo
|
41
|
+
# include Hoodie::Konstruktor
|
42
|
+
#
|
43
|
+
# takes :foo, :bar, :baz
|
44
|
+
# let(:hello) { 'world' }
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
module Hoodie
|
48
|
+
module Konstruktor
|
49
|
+
def takes(*names)
|
50
|
+
attr_reader *names
|
51
|
+
include Konstruktor::Constructor(*names)
|
52
|
+
extend Konstruktor::Let
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.Constructor(*names)
|
57
|
+
eval <<-RUBY
|
58
|
+
|
59
|
+
Module.new do
|
60
|
+
def initialize(#{names.join(', ')})
|
61
|
+
#{names.map{ |name| "@#{name} = #{name}" }.join("\n") }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
|
68
|
+
module Let
|
69
|
+
def let(name, &block)
|
70
|
+
define_method(name, &block)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Object
|
76
|
+
extend Hoodie::Konstruktor
|
77
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
# Super simple state machine.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# class MyModel
|
24
|
+
# STATE_TRANSITIONS = TransitionTable.new(
|
25
|
+
# # State Input Next state Output
|
26
|
+
# [:awaiting_foo, :foo] => [:awaiting_bar, :do_stuff],
|
27
|
+
# [:awaiting_foo, :bar] => [:awaiting_foo, nil],
|
28
|
+
# [:awaiting_bar, :bar] => [:all_done, :do_other_stuff]
|
29
|
+
# )
|
30
|
+
#
|
31
|
+
# def initialize
|
32
|
+
# @state_machine = StateMachine.new(STATE_TRANSITIONS, :awaiting_foo)
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# def handle_event(event)
|
36
|
+
# action = @state_machine.send_input(event)
|
37
|
+
# send(action) unless action.nil?
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# def do_stuff
|
41
|
+
# # ...
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# def do_other_stuff
|
45
|
+
# # ...
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
module Hoodie
|
50
|
+
class Machine
|
51
|
+
def initialize(transition_function, initial_state)
|
52
|
+
@transition_function = transition_function
|
53
|
+
@state = initial_state
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :state
|
57
|
+
|
58
|
+
def send_input(input)
|
59
|
+
@state, output = @transition_function.call(@state, input)
|
60
|
+
output
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class TransitionTable
|
65
|
+
class TransitionError < RuntimeError
|
66
|
+
def initialize(state, input)
|
67
|
+
super
|
68
|
+
"No transition from state #{state.inspect} for input #{input.inspect}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(transitions)
|
73
|
+
@transitions = transitions
|
74
|
+
end
|
75
|
+
|
76
|
+
def call(state, input)
|
77
|
+
@transitions.fetch([state, input])
|
78
|
+
rescue KeyError
|
79
|
+
raise TransitionError.new(state, input)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'rbconfig'
|
21
|
+
|
22
|
+
module Hoodie
|
23
|
+
# Finds out the current Operating System.
|
24
|
+
#
|
25
|
+
module OS
|
26
|
+
extend self
|
27
|
+
|
28
|
+
# @return [Boolean]
|
29
|
+
# Returns true if OS is Windows.
|
30
|
+
def windows?
|
31
|
+
windows = /cygwin|mswin|mingw|bccwin|wince|emx/i
|
32
|
+
(RbConfig::CONFIG['host_os'] =~ windows) != nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Boolean]
|
36
|
+
# Returns true if OS is Mac.
|
37
|
+
def mac?
|
38
|
+
mac = /darwin|mac os/i
|
39
|
+
(RbConfig::CONFIG['host_os'] =~ mac) != nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Boolean]
|
43
|
+
# Returns true if OS is Unix.
|
44
|
+
def unix?
|
45
|
+
unix = /solaris|bsd/i
|
46
|
+
(RbConfig::CONFIG['host_os'] =~ unix) != nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Boolean]
|
50
|
+
# Returns true if OS is Linux.
|
51
|
+
def linux?
|
52
|
+
linux = /linux/i
|
53
|
+
(RbConfig::CONFIG['host_os'] =~ linux) != nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require_relative 'timeout'
|
21
|
+
|
22
|
+
module Hoodie
|
23
|
+
# Class methods that are added when you include Hoodie::Retry
|
24
|
+
#
|
25
|
+
module Retry
|
26
|
+
# Methods are also available as module-level methods as well as a mixin.
|
27
|
+
extend self
|
28
|
+
|
29
|
+
# Runs a code block, and retries it when an exception occurs. It is
|
30
|
+
# configured using four optional parameters `:tries`, `:on`, `:sleep`,
|
31
|
+
# `:match`, `:ensure` and runs the passed block. Should an exception
|
32
|
+
# occur, it'll retry for (n-1) times. Should the number of retries be
|
33
|
+
# reached without success, the last exception will be raised.
|
34
|
+
#
|
35
|
+
# @example open an URL, retry up to two times when an OpenURI::HTTPError
|
36
|
+
# occurs.
|
37
|
+
# retrier(tries: 3, on: OpenURI::HTTPError) do
|
38
|
+
# xml = open('http://example.com/test.html').read
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# @example do _something_, retry up to four times for either ArgumentErro
|
42
|
+
# or TimeoutError exceptions.
|
43
|
+
# retrier(tries: 5, on: [ArgumentError, TimeoutError]) do
|
44
|
+
# # _something_ code
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# @example ensure that block of code is executed, regardless of whether an
|
48
|
+
# exception was raised. It doesn't matter if the block exits normally,
|
49
|
+
# if it retries to execute block of code, or if it is terminated by an
|
50
|
+
# uncaught exception -- the ensure block will get run.
|
51
|
+
# f = File.open('testfile')
|
52
|
+
# ensure_cb = Proc.new do |retries|
|
53
|
+
# puts "total retry attempts: #{retries}"
|
54
|
+
# f.close
|
55
|
+
# end
|
56
|
+
# retrier(insure: ensure_cb) do
|
57
|
+
# # process file
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# @example sleeping: by default Retrier waits for one second between
|
61
|
+
# retries. You can change this and even provide your own exponential
|
62
|
+
# backoff scheme.
|
63
|
+
# retrier(sleep: 0) { } # don't pause between retries
|
64
|
+
# retrier(sleep: 10) { } # sleep 10s between retries
|
65
|
+
# retrier(sleep: ->(n) { 4**n }) { } # sleep 1, 4, 16, etc. each try
|
66
|
+
#
|
67
|
+
# @example matching error messages: you can also retry based on the
|
68
|
+
# exception message:
|
69
|
+
# retrier(matching: /IO timeout/) do |retries, exception|
|
70
|
+
# raise "yo, IO timeout!" if retries == 0
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# @example block parameters: your block is called with two optional
|
74
|
+
# parameters; the number of tries until now, and the most recent
|
75
|
+
# exception.
|
76
|
+
# retrier do |tries, exception|
|
77
|
+
# puts "try #{tries} failed with error: #{exception}" if retries > 0
|
78
|
+
# # keep trying...
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# @param opts [Hash]
|
82
|
+
#
|
83
|
+
# @option opts [Fixnum] :tries
|
84
|
+
# Number of attempts to retry before raising the last exception
|
85
|
+
#
|
86
|
+
# @option opts [Fixnum] :sleep
|
87
|
+
# Number of seconds to wait between retries, use lambda to exponentially
|
88
|
+
# increasing delay between retries.
|
89
|
+
#
|
90
|
+
# @option opts [Array(Exception)] :on
|
91
|
+
# The type of exception(s) to catch and retry on
|
92
|
+
#
|
93
|
+
# @option opts [Regex] :matching
|
94
|
+
# Match based on the exception message
|
95
|
+
#
|
96
|
+
# @option opts [Block] :ensure
|
97
|
+
# Ensure a block of code is executed, regardless of whether an exception
|
98
|
+
# is raised
|
99
|
+
#
|
100
|
+
# @yield [Proc]
|
101
|
+
# A block that will be run, and if it raises an error, re-run until
|
102
|
+
# success, or timeout is finally reached.
|
103
|
+
#
|
104
|
+
# @raise [Exception]
|
105
|
+
# Last Exception that caused the loop to retry before giving up.
|
106
|
+
#
|
107
|
+
# @return [Proc]
|
108
|
+
# The value of the passed block.
|
109
|
+
#
|
110
|
+
# @api public
|
111
|
+
def retrier(opts = {}, &_block)
|
112
|
+
tries = opts.fetch(:tries, 4)
|
113
|
+
wait = opts.fetch(:sleep, 1)
|
114
|
+
on = opts.fetch(:on, StandardError)
|
115
|
+
match = opts.fetch(:match, /.*/)
|
116
|
+
insure = opts.fetch(:ensure, Proc.new {})
|
117
|
+
|
118
|
+
retries = 0
|
119
|
+
retry_exception = nil
|
120
|
+
|
121
|
+
begin
|
122
|
+
yield retries, retry_exception
|
123
|
+
rescue *[on] => exception
|
124
|
+
raise unless exception.message =~ match
|
125
|
+
raise if retries + 1 >= tries
|
126
|
+
|
127
|
+
begin
|
128
|
+
sleep wait.respond_to?(:call) ? wait.call(retries) : wait
|
129
|
+
rescue *[on]
|
130
|
+
end
|
131
|
+
|
132
|
+
retries += 1
|
133
|
+
retry_exception = exception
|
134
|
+
retry
|
135
|
+
ensure
|
136
|
+
insure.call(retries)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# `#poll` is a method for knowing when something is ready. When your
|
141
|
+
# block yields true, execution continues. When your block yields false,
|
142
|
+
# poll keeps trying until it gives up and raises an error.
|
143
|
+
#
|
144
|
+
# @example wait up to 30 seconds for the TCP socket to respond.
|
145
|
+
# def wait_for_server
|
146
|
+
# poll(30) do
|
147
|
+
# begin
|
148
|
+
# TCPSocket.new(SERVER_IP, SERVER_PORT)
|
149
|
+
# true
|
150
|
+
# rescue Exception
|
151
|
+
# false
|
152
|
+
# end
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# @param [Integer] wait
|
157
|
+
# The number of seconds seconds to poll.
|
158
|
+
#
|
159
|
+
# @param [Integer] delay
|
160
|
+
# Number of seconds to wait after encountering a failure, default is
|
161
|
+
# 0.1 seconds
|
162
|
+
#
|
163
|
+
# @yield [Proc]
|
164
|
+
# A block that determines whether polling should continue. Return
|
165
|
+
# `true` if the polling is complete. Return `false` if polling should
|
166
|
+
# continue.
|
167
|
+
#
|
168
|
+
# @raise [Hoodie::PollingError]
|
169
|
+
# Raised after too many failed attempts.
|
170
|
+
#
|
171
|
+
# @return [Proc]
|
172
|
+
# The value of the passed block.
|
173
|
+
#
|
174
|
+
# @api public
|
175
|
+
def poll(wait = 8, delay = 0.1)
|
176
|
+
try_until = Time.now + wait
|
177
|
+
|
178
|
+
while Time.now < try_until do
|
179
|
+
result = yield
|
180
|
+
return result if result
|
181
|
+
sleep delay
|
182
|
+
end
|
183
|
+
raise TimeoutError
|
184
|
+
end
|
185
|
+
|
186
|
+
# Similar to `#poll`, `#patiently` also executes an arbitrary code block.
|
187
|
+
# If the passed block runs without raising an error, execution proceeds
|
188
|
+
# normally. If an error is raised, the block is rerun after a brief
|
189
|
+
# delay, until the block can be run without exceptions. If exceptions
|
190
|
+
# continue to raise, `#patiently` gives up after a bit (default 8
|
191
|
+
# seconds) by re-raising the most recent exception raised by the block.
|
192
|
+
#
|
193
|
+
# @example
|
194
|
+
# Returns immedialtely if no errors or as soon as error stops.
|
195
|
+
# patiently { ... }
|
196
|
+
#
|
197
|
+
# Increase patience to 10 seconds.
|
198
|
+
# patiently(10) { ... }
|
199
|
+
#
|
200
|
+
# Increase patience to 20 seconds, and delay for 3 seconds before retry.
|
201
|
+
# patiently(20, 3) { ... }
|
202
|
+
#
|
203
|
+
# @param [Integer] seconds
|
204
|
+
# number of seconds to be patient, default is 8 seconds
|
205
|
+
#
|
206
|
+
# @param [Integer] delay
|
207
|
+
# seconds to wait after encountering a failure, default is 0.1 seconds
|
208
|
+
#
|
209
|
+
# @yield [Proc]
|
210
|
+
# A block that will be run, and if it raises an error, re-run until
|
211
|
+
# success, or patience runs out.
|
212
|
+
#
|
213
|
+
# @raise [Exception] the most recent Exception that caused the loop to
|
214
|
+
# retry before giving up.
|
215
|
+
#
|
216
|
+
# @return [Proc]
|
217
|
+
# the value of the passed block.
|
218
|
+
#
|
219
|
+
# @api public
|
220
|
+
def patiently(wait = 8, delay = 0.1)
|
221
|
+
try_until = Time.now + wait
|
222
|
+
failure = nil
|
223
|
+
|
224
|
+
while Time.now < try_until do
|
225
|
+
begin
|
226
|
+
return yield
|
227
|
+
rescue Exception => e
|
228
|
+
failure = e
|
229
|
+
sleep delay
|
230
|
+
end
|
231
|
+
end
|
232
|
+
failure ? (raise failure) : (raise TimeoutError)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|