innate 2009.04
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2981 -0
- data/COPYING +18 -0
- data/MANIFEST +127 -0
- data/README.md +563 -0
- data/Rakefile +35 -0
- data/example/app/retro_games.rb +60 -0
- data/example/app/todo/layout/default.xhtml +11 -0
- data/example/app/todo/spec/todo.rb +63 -0
- data/example/app/todo/start.rb +51 -0
- data/example/app/todo/view/index.xhtml +39 -0
- data/example/app/whywiki_erb/layout/wiki.html.erb +15 -0
- data/example/app/whywiki_erb/spec/wiki.rb +19 -0
- data/example/app/whywiki_erb/start.rb +42 -0
- data/example/app/whywiki_erb/view/edit.erb +6 -0
- data/example/app/whywiki_erb/view/index.erb +12 -0
- data/example/custom_middleware.rb +35 -0
- data/example/hello.rb +11 -0
- data/example/howto_spec.rb +35 -0
- data/example/link.rb +27 -0
- data/example/provides.rb +31 -0
- data/example/session.rb +38 -0
- data/innate.gemspec +29 -0
- data/lib/innate.rb +269 -0
- data/lib/innate/action.rb +150 -0
- data/lib/innate/adapter.rb +76 -0
- data/lib/innate/cache.rb +134 -0
- data/lib/innate/cache/api.rb +128 -0
- data/lib/innate/cache/drb.rb +58 -0
- data/lib/innate/cache/file_based.rb +41 -0
- data/lib/innate/cache/marshal.rb +17 -0
- data/lib/innate/cache/memory.rb +22 -0
- data/lib/innate/cache/yaml.rb +17 -0
- data/lib/innate/current.rb +37 -0
- data/lib/innate/dynamap.rb +96 -0
- data/lib/innate/helper.rb +183 -0
- data/lib/innate/helper/aspect.rb +124 -0
- data/lib/innate/helper/cgi.rb +54 -0
- data/lib/innate/helper/flash.rb +36 -0
- data/lib/innate/helper/link.rb +94 -0
- data/lib/innate/helper/redirect.rb +85 -0
- data/lib/innate/helper/render.rb +87 -0
- data/lib/innate/helper/send_file.rb +26 -0
- data/lib/innate/log.rb +20 -0
- data/lib/innate/log/color_formatter.rb +43 -0
- data/lib/innate/log/hub.rb +73 -0
- data/lib/innate/middleware_compiler.rb +65 -0
- data/lib/innate/mock.rb +49 -0
- data/lib/innate/node.rb +1025 -0
- data/lib/innate/options.rb +37 -0
- data/lib/innate/options/dsl.rb +202 -0
- data/lib/innate/options/stub.rb +7 -0
- data/lib/innate/request.rb +141 -0
- data/lib/innate/response.rb +23 -0
- data/lib/innate/route.rb +110 -0
- data/lib/innate/session.rb +121 -0
- data/lib/innate/session/flash.rb +94 -0
- data/lib/innate/spec.rb +23 -0
- data/lib/innate/state.rb +27 -0
- data/lib/innate/state/accessor.rb +130 -0
- data/lib/innate/state/fiber.rb +74 -0
- data/lib/innate/state/thread.rb +47 -0
- data/lib/innate/traited.rb +85 -0
- data/lib/innate/trinity.rb +18 -0
- data/lib/innate/version.rb +3 -0
- data/lib/innate/view.rb +60 -0
- data/lib/innate/view/erb.rb +15 -0
- data/lib/innate/view/etanni.rb +36 -0
- data/lib/innate/view/none.rb +9 -0
- data/spec/example/app/retro_games.rb +30 -0
- data/spec/example/hello.rb +13 -0
- data/spec/example/link.rb +25 -0
- data/spec/example/provides.rb +16 -0
- data/spec/example/session.rb +22 -0
- data/spec/helper.rb +10 -0
- data/spec/innate/action/layout.rb +107 -0
- data/spec/innate/action/layout/file_layout.xhtml +1 -0
- data/spec/innate/cache/common.rb +47 -0
- data/spec/innate/cache/marshal.rb +5 -0
- data/spec/innate/cache/memory.rb +5 -0
- data/spec/innate/cache/yaml.rb +5 -0
- data/spec/innate/dynamap.rb +22 -0
- data/spec/innate/helper.rb +86 -0
- data/spec/innate/helper/aspect.rb +75 -0
- data/spec/innate/helper/cgi.rb +37 -0
- data/spec/innate/helper/flash.rb +118 -0
- data/spec/innate/helper/link.rb +139 -0
- data/spec/innate/helper/redirect.rb +160 -0
- data/spec/innate/helper/render.rb +133 -0
- data/spec/innate/helper/send_file.rb +21 -0
- data/spec/innate/helper/view/aspect_hello.xhtml +1 -0
- data/spec/innate/helper/view/locals.xhtml +1 -0
- data/spec/innate/helper/view/loop.xhtml +4 -0
- data/spec/innate/helper/view/num.xhtml +1 -0
- data/spec/innate/helper/view/partial.xhtml +1 -0
- data/spec/innate/helper/view/recursive.xhtml +7 -0
- data/spec/innate/mock.rb +84 -0
- data/spec/innate/node/mapping.rb +37 -0
- data/spec/innate/node/node.rb +134 -0
- data/spec/innate/node/resolve.rb +82 -0
- data/spec/innate/node/view/another_layout/another_layout.xhtml +3 -0
- data/spec/innate/node/view/bar.xhtml +1 -0
- data/spec/innate/node/view/foo.html.xhtml +1 -0
- data/spec/innate/node/view/only_view.xhtml +1 -0
- data/spec/innate/node/view/with_layout.xhtml +1 -0
- data/spec/innate/node/wrap_action_call.rb +83 -0
- data/spec/innate/options.rb +115 -0
- data/spec/innate/parameter.rb +154 -0
- data/spec/innate/provides.rb +99 -0
- data/spec/innate/provides/list.html.xhtml +1 -0
- data/spec/innate/provides/list.txt.xhtml +1 -0
- data/spec/innate/request.rb +77 -0
- data/spec/innate/route.rb +135 -0
- data/spec/innate/session.rb +54 -0
- data/spec/innate/state/fiber.rb +58 -0
- data/spec/innate/state/thread.rb +40 -0
- data/spec/innate/traited.rb +55 -0
- data/tasks/bacon.rake +66 -0
- data/tasks/changelog.rake +18 -0
- data/tasks/gem.rake +22 -0
- data/tasks/gem_installer.rake +76 -0
- data/tasks/grancher.rake +12 -0
- data/tasks/install_dependencies.rake +4 -0
- data/tasks/manifest.rake +4 -0
- data/tasks/rcov.rake +19 -0
- data/tasks/release.rake +51 -0
- data/tasks/reversion.rake +8 -0
- data/tasks/setup.rake +28 -0
- metadata +181 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
module Innate
|
2
|
+
|
3
|
+
# Mostly ported from Ramaze, but behaves lazy, no session will be created if
|
4
|
+
# no session is used.
|
5
|
+
#
|
6
|
+
# We keep session data in memory until #flush is called, at which point it
|
7
|
+
# will be persisted completely into the cache, no question asked.
|
8
|
+
#
|
9
|
+
# You may store anything in here that you may also store in the corresponding
|
10
|
+
# store, usually it's best to keep it to things that are safe to Marshal.
|
11
|
+
#
|
12
|
+
# The default time of expiration is *
|
13
|
+
#
|
14
|
+
# Time.at(2147483647) # => Tue Jan 19 12:14:07 +0900 2038
|
15
|
+
#
|
16
|
+
# Hopefully we all have 64bit systems by then.
|
17
|
+
|
18
|
+
class Session
|
19
|
+
include Optioned
|
20
|
+
|
21
|
+
options.dsl do
|
22
|
+
o "Key for the session cookie",
|
23
|
+
:key, 'innate.sid'
|
24
|
+
o "Domain the cookie relates to, unspecified if false",
|
25
|
+
:domain, false
|
26
|
+
o "Path the cookie relates to",
|
27
|
+
:path, '/'
|
28
|
+
o "Use secure cookie",
|
29
|
+
:secure, false
|
30
|
+
o "Time of cookie expiration",
|
31
|
+
:expires, Time.at((1 << 31) - 1)
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :cookie_set, :request, :response, :flash
|
35
|
+
|
36
|
+
def initialize(request, response)
|
37
|
+
@request, @response = request, response
|
38
|
+
@cookie_set = false
|
39
|
+
@cache_sid = nil
|
40
|
+
@flash = Flash.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(key, value)
|
44
|
+
cache_sid[key] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](key)
|
48
|
+
cache_sid[key]
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete(key)
|
52
|
+
cache_sid.delete(key)
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear
|
56
|
+
cache.delete(sid)
|
57
|
+
@cache_sid = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def cache_sid
|
61
|
+
@cache_sid ||= cache[sid] || {}
|
62
|
+
end
|
63
|
+
|
64
|
+
def flush(response = @response)
|
65
|
+
return if !@cache_sid or @cache_sid.empty?
|
66
|
+
|
67
|
+
flash.rotate!
|
68
|
+
ttl = (Time.at(cookie_value[:expires]) - Time.now).to_i
|
69
|
+
cache.store(sid, cache_sid, :ttl => ttl)
|
70
|
+
set_cookie(response)
|
71
|
+
end
|
72
|
+
|
73
|
+
def sid
|
74
|
+
@sid ||= cookie || generate_sid
|
75
|
+
end
|
76
|
+
|
77
|
+
def cookie
|
78
|
+
@request.cookies[options.key]
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
cache.inspect
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def cache
|
88
|
+
Innate::Cache.session
|
89
|
+
end
|
90
|
+
|
91
|
+
def set_cookie(response)
|
92
|
+
return if @cookie_set || cookie
|
93
|
+
|
94
|
+
@cookie_set = true
|
95
|
+
response.set_cookie(options.key, cookie_value)
|
96
|
+
end
|
97
|
+
|
98
|
+
def cookie_value
|
99
|
+
{ :value => sid,
|
100
|
+
:domain => options.domain,
|
101
|
+
:path => options.path,
|
102
|
+
:secure => options.secure,
|
103
|
+
:expires => options.expires }
|
104
|
+
end
|
105
|
+
|
106
|
+
def generate_sid
|
107
|
+
begin sid = sid_algorithm end while cache[sid]
|
108
|
+
sid
|
109
|
+
end
|
110
|
+
|
111
|
+
begin
|
112
|
+
require 'securerandom'
|
113
|
+
def sid_algorithm; SecureRandom.hex(32); end
|
114
|
+
rescue LoadError
|
115
|
+
def sid_algorithm
|
116
|
+
entropy = [ srand, rand, Time.now.to_f, rand, $$, rand, object_id ]
|
117
|
+
Digest::SHA2.hexdigest(entropy.join)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Innate
|
2
|
+
class Session
|
3
|
+
|
4
|
+
# The purpose of this class is to act as a unifier of the previous
|
5
|
+
# and current flash.
|
6
|
+
#
|
7
|
+
# Flash means pairs of keys and values that are held only over one
|
8
|
+
# request/response cycle. So you can assign a key/value in the current
|
9
|
+
# session and retrieve it in the current and following request.
|
10
|
+
#
|
11
|
+
# Please see the Innate::Helper::Flash for details on the usage in your
|
12
|
+
# application.
|
13
|
+
class Flash
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
def initialize(session)
|
17
|
+
@session = session
|
18
|
+
end
|
19
|
+
|
20
|
+
# iterate over the combined session
|
21
|
+
def each(&block)
|
22
|
+
combined.each(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
# the current session[:FLASH_PREVIOUS]
|
26
|
+
def previous
|
27
|
+
session[:FLASH_PREVIOUS] || {}
|
28
|
+
end
|
29
|
+
|
30
|
+
# the current session[:FLASH]
|
31
|
+
def current
|
32
|
+
session[:FLASH] ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# combined key/value pairs of previous and current
|
36
|
+
# current keys overshadow the old ones.
|
37
|
+
def combined
|
38
|
+
previous.merge(current)
|
39
|
+
end
|
40
|
+
|
41
|
+
# flash[key] in your Controller
|
42
|
+
def [](key)
|
43
|
+
combined[key]
|
44
|
+
end
|
45
|
+
|
46
|
+
# flash[key] = value in your Controller
|
47
|
+
def []=(key, value)
|
48
|
+
prev = session[:FLASH] || {}
|
49
|
+
prev[key] = value
|
50
|
+
session[:FLASH] = prev
|
51
|
+
end
|
52
|
+
|
53
|
+
# Inspects combined
|
54
|
+
def inspect
|
55
|
+
combined.inspect
|
56
|
+
end
|
57
|
+
|
58
|
+
# Delete a key
|
59
|
+
def delete(key)
|
60
|
+
previous.delete(key)
|
61
|
+
current.delete(key)
|
62
|
+
end
|
63
|
+
|
64
|
+
# check if combined is empty
|
65
|
+
def empty?
|
66
|
+
combined.empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
# merge into current
|
70
|
+
def merge!(hash)
|
71
|
+
current.merge!(hash)
|
72
|
+
end
|
73
|
+
|
74
|
+
# merge on current
|
75
|
+
def merge(hash)
|
76
|
+
current.merge(hash)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Rotation means current values are assigned as old values for the next
|
80
|
+
# request.
|
81
|
+
def rotate!
|
82
|
+
old = session.delete(:FLASH)
|
83
|
+
session[:FLASH_PREVIOUS] = old if old
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Associated session object
|
89
|
+
def session
|
90
|
+
@session
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/innate/spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
begin; require 'rubygems'; rescue LoadError; end
|
2
|
+
|
3
|
+
require 'bacon'
|
4
|
+
require 'rack/test'
|
5
|
+
require(File.expand_path("#{__FILE__}/../")) unless defined?(Innate)
|
6
|
+
|
7
|
+
Bacon.summary_on_exit
|
8
|
+
|
9
|
+
module Innate
|
10
|
+
# minimal middleware, no exception handling
|
11
|
+
middleware(:spec){|m| m.innate }
|
12
|
+
|
13
|
+
# skip starting adapter
|
14
|
+
options.started = true
|
15
|
+
options.mode = :spec
|
16
|
+
end
|
17
|
+
|
18
|
+
shared :mock do
|
19
|
+
Innate.setup_dependencies
|
20
|
+
extend Rack::Test::Methods
|
21
|
+
|
22
|
+
def app; Innate.middleware; end
|
23
|
+
end
|
data/lib/innate/state.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Innate
|
2
|
+
begin
|
3
|
+
require 'innate/state/fiber'
|
4
|
+
STATE = State::Fiber.new
|
5
|
+
rescue LoadError
|
6
|
+
require 'innate/state/thread'
|
7
|
+
STATE = State::Thread.new
|
8
|
+
end
|
9
|
+
|
10
|
+
module SingletonMethods
|
11
|
+
# Use this method to achieve thread-safety for sensitive operations.
|
12
|
+
#
|
13
|
+
# This should be of most use when manipulating files to prevent other
|
14
|
+
# threads from doing the same, no other code will be scheduled during
|
15
|
+
# execution of this method.
|
16
|
+
#
|
17
|
+
# @param [Proc] block the things you want to execute
|
18
|
+
# @see State::Thread#sync State::Fiber#sync
|
19
|
+
def sync(&block)
|
20
|
+
STATE.sync(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def defer(&block)
|
24
|
+
STATE.defer(&block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Innate
|
2
|
+
# Simplify accessing STATE variables.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# class Foo
|
7
|
+
# include Innate::StateAccessor
|
8
|
+
# state_accessor :session
|
9
|
+
#
|
10
|
+
# def calculate
|
11
|
+
# session[:num1] + session[:num2]
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Foo#calculate can now be called from anywhere in your application and it
|
16
|
+
# will have direct access to the session in the current request/response
|
17
|
+
# cycle in a thread-safe way without the need to explicitly pass the session
|
18
|
+
# along.
|
19
|
+
|
20
|
+
module StateAccessor
|
21
|
+
|
22
|
+
# Iterate over the names and yield accordingly.
|
23
|
+
# names are either objects responding to #to_sym or hashes.
|
24
|
+
#
|
25
|
+
# It's only used within this module to make the code readable.
|
26
|
+
#
|
27
|
+
# Used below.
|
28
|
+
|
29
|
+
def self.each(*names)
|
30
|
+
names.each do |name|
|
31
|
+
if name.respond_to?(:to_hash)
|
32
|
+
name.to_hash.each do |key, meth|
|
33
|
+
yield(key.to_sym, meth.to_sym)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
key = meth = name.to_sym
|
37
|
+
yield(key, meth)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Combined state_writer and state_reader.
|
43
|
+
# +initializer+ is a block that may be given and its result will be the new
|
44
|
+
# value in case the method created by state_reader was never called before
|
45
|
+
# and the value wasn't set before.
|
46
|
+
#
|
47
|
+
# Example:
|
48
|
+
#
|
49
|
+
# state_accessor(:session)
|
50
|
+
# state_accessor(:user){ session[:user] }
|
51
|
+
|
52
|
+
def state_accessor(*names, &initializer)
|
53
|
+
state_writer(*names)
|
54
|
+
state_reader(*names, &initializer)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Writer accessor to STATE[key]=
|
58
|
+
#
|
59
|
+
# Example:
|
60
|
+
#
|
61
|
+
# class Foo
|
62
|
+
# include Innate::StateAccessor
|
63
|
+
# state_writer(:result)
|
64
|
+
#
|
65
|
+
# def calculate
|
66
|
+
# self.result = 42
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# class Bar
|
71
|
+
# include Innate::StateAccessor
|
72
|
+
# state_reader(:result)
|
73
|
+
#
|
74
|
+
# def calculcate
|
75
|
+
# result * result
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# Foo.new.calculate # => 42
|
80
|
+
# Bar.new.calculate # => 1764
|
81
|
+
|
82
|
+
def state_writer(*names)
|
83
|
+
StateAccessor.each(*names) do |key, meth|
|
84
|
+
class_eval("def %s=(obj) STATE[%p] = obj; end" % [meth, key])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Reader accessor for STATE[key]
|
89
|
+
#
|
90
|
+
# Example:
|
91
|
+
#
|
92
|
+
# class Foo
|
93
|
+
# include Innate::StateAccessor
|
94
|
+
# state_reader(:session)
|
95
|
+
# state_reader(:random){ rand(100_000) }
|
96
|
+
#
|
97
|
+
# def calculate
|
98
|
+
# val1 = session[:num1] + session[:num2] + random
|
99
|
+
# val2 = session[:num1] + session[:num2] + random
|
100
|
+
# val1 == val2 # => true
|
101
|
+
# end
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# NOTE:
|
105
|
+
# If given +initializer+, there will be some performance impact since we
|
106
|
+
# cannot use class_eval and have to use define_method instead, we also
|
107
|
+
# have to check every time whether the initializer was executed already.
|
108
|
+
#
|
109
|
+
# In 1.8.x the overhead of define_method is 3x that of class_eval/def
|
110
|
+
# In 1.9.1 the overhead of define_method is 1.5x that of class_eval/def
|
111
|
+
#
|
112
|
+
# This may only be an issue for readers that are called a lot of times.
|
113
|
+
|
114
|
+
def state_reader(*names, &initializer)
|
115
|
+
StateAccessor.each(*names) do |key, meth|
|
116
|
+
if initializer
|
117
|
+
define_method(meth) do
|
118
|
+
unless STATE.key?(key)
|
119
|
+
STATE[key] = instance_eval(&initializer)
|
120
|
+
else
|
121
|
+
STATE[key]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
else
|
125
|
+
class_eval("def %s; STATE[%p]; end" % [meth, key])
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'fiber'
|
2
|
+
|
3
|
+
module Innate
|
4
|
+
# Innate subclasses Fiber to enable lightweight request/respose-cycle local
|
5
|
+
# variables.
|
6
|
+
#
|
7
|
+
# We do that by adding a state Hash to the Fiber instance on initalization
|
8
|
+
# which can be accessed by #[], #[]= and #key?. Other Hash methods are not
|
9
|
+
# necessary right now but may be added.
|
10
|
+
#
|
11
|
+
# We subclass to keep your Ruby clean and polished.
|
12
|
+
class Fiber < ::Fiber
|
13
|
+
attr_accessor :state
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
super
|
17
|
+
@state = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](key)
|
21
|
+
state[key]
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(key, value)
|
25
|
+
state[key] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def key?(key)
|
29
|
+
state.key?(key)
|
30
|
+
end
|
31
|
+
|
32
|
+
def keys
|
33
|
+
state.keys
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module State
|
38
|
+
# Our accessor to the currently active Fiber, an instance of
|
39
|
+
# Innate::State::Fiber will be assigned to Innate::STATE if fibers are
|
40
|
+
# available.
|
41
|
+
class Fiber
|
42
|
+
def [](key)
|
43
|
+
::Fiber.current[key]
|
44
|
+
end
|
45
|
+
|
46
|
+
def []=(key, value)
|
47
|
+
::Fiber.current[key] = value
|
48
|
+
end
|
49
|
+
|
50
|
+
# We don't use Fiber in a concurrent manner and they don't run
|
51
|
+
# concurrently by themselves, so we directly #resume the Fiber to get the
|
52
|
+
# return value of +block+.
|
53
|
+
|
54
|
+
def wrap(&block)
|
55
|
+
Innate::Fiber.new(&block).resume
|
56
|
+
end
|
57
|
+
|
58
|
+
# In an environment where only Fiber is used there is no concurrency, so
|
59
|
+
# we don't have to lock with a Mutex.
|
60
|
+
|
61
|
+
def sync
|
62
|
+
yield
|
63
|
+
end
|
64
|
+
|
65
|
+
def defer
|
66
|
+
a = Innate::Fiber.current
|
67
|
+
::Thread.new do
|
68
|
+
b = Innate::Fiber.new{ a.keys.each{|k| b[k] = a[k] }; yield }
|
69
|
+
b.resume
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|