rjspotter-innate 2009.06.29

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.
Files changed (128) hide show
  1. data/AUTHORS +10 -0
  2. data/CHANGELOG +3261 -0
  3. data/COPYING +18 -0
  4. data/MANIFEST +127 -0
  5. data/README.md +563 -0
  6. data/Rakefile +39 -0
  7. data/example/app/retro_games.rb +60 -0
  8. data/example/app/todo/layout/default.xhtml +11 -0
  9. data/example/app/todo/spec/todo.rb +63 -0
  10. data/example/app/todo/start.rb +51 -0
  11. data/example/app/todo/view/index.xhtml +39 -0
  12. data/example/app/whywiki_erb/layout/wiki.html.erb +15 -0
  13. data/example/app/whywiki_erb/spec/wiki.rb +19 -0
  14. data/example/app/whywiki_erb/start.rb +42 -0
  15. data/example/app/whywiki_erb/view/edit.erb +6 -0
  16. data/example/app/whywiki_erb/view/index.erb +12 -0
  17. data/example/custom_middleware.rb +35 -0
  18. data/example/hello.rb +11 -0
  19. data/example/howto_spec.rb +35 -0
  20. data/example/link.rb +27 -0
  21. data/example/provides.rb +31 -0
  22. data/example/session.rb +38 -0
  23. data/innate.gemspec +41 -0
  24. data/lib/innate.rb +269 -0
  25. data/lib/innate/action.rb +137 -0
  26. data/lib/innate/adapter.rb +76 -0
  27. data/lib/innate/cache.rb +134 -0
  28. data/lib/innate/cache/api.rb +128 -0
  29. data/lib/innate/cache/drb.rb +58 -0
  30. data/lib/innate/cache/file_based.rb +44 -0
  31. data/lib/innate/cache/marshal.rb +20 -0
  32. data/lib/innate/cache/memory.rb +21 -0
  33. data/lib/innate/cache/yaml.rb +20 -0
  34. data/lib/innate/current.rb +35 -0
  35. data/lib/innate/dynamap.rb +96 -0
  36. data/lib/innate/helper.rb +185 -0
  37. data/lib/innate/helper/aspect.rb +124 -0
  38. data/lib/innate/helper/cgi.rb +54 -0
  39. data/lib/innate/helper/flash.rb +36 -0
  40. data/lib/innate/helper/link.rb +94 -0
  41. data/lib/innate/helper/redirect.rb +85 -0
  42. data/lib/innate/helper/render.rb +152 -0
  43. data/lib/innate/helper/send_file.rb +26 -0
  44. data/lib/innate/log.rb +20 -0
  45. data/lib/innate/log/color_formatter.rb +49 -0
  46. data/lib/innate/log/hub.rb +77 -0
  47. data/lib/innate/middleware_compiler.rb +65 -0
  48. data/lib/innate/mock.rb +49 -0
  49. data/lib/innate/node.rb +1029 -0
  50. data/lib/innate/options.rb +37 -0
  51. data/lib/innate/options/dsl.rb +205 -0
  52. data/lib/innate/options/stub.rb +7 -0
  53. data/lib/innate/request.rb +141 -0
  54. data/lib/innate/response.rb +24 -0
  55. data/lib/innate/route.rb +114 -0
  56. data/lib/innate/session.rb +133 -0
  57. data/lib/innate/session/flash.rb +94 -0
  58. data/lib/innate/spec.rb +1 -0
  59. data/lib/innate/spec/bacon.rb +28 -0
  60. data/lib/innate/state.rb +26 -0
  61. data/lib/innate/state/accessor.rb +130 -0
  62. data/lib/innate/traited.rb +90 -0
  63. data/lib/innate/trinity.rb +18 -0
  64. data/lib/innate/version.rb +3 -0
  65. data/lib/innate/view.rb +97 -0
  66. data/lib/innate/view/erb.rb +14 -0
  67. data/lib/innate/view/etanni.rb +33 -0
  68. data/lib/innate/view/none.rb +9 -0
  69. data/spec/example/app/retro_games.rb +30 -0
  70. data/spec/example/hello.rb +13 -0
  71. data/spec/example/link.rb +25 -0
  72. data/spec/example/provides.rb +16 -0
  73. data/spec/example/session.rb +22 -0
  74. data/spec/helper.rb +10 -0
  75. data/spec/innate/action/layout.rb +121 -0
  76. data/spec/innate/action/layout/file_layout.xhtml +1 -0
  77. data/spec/innate/cache/common.rb +47 -0
  78. data/spec/innate/cache/marshal.rb +5 -0
  79. data/spec/innate/cache/memory.rb +5 -0
  80. data/spec/innate/cache/yaml.rb +5 -0
  81. data/spec/innate/dynamap.rb +22 -0
  82. data/spec/innate/helper.rb +86 -0
  83. data/spec/innate/helper/aspect.rb +75 -0
  84. data/spec/innate/helper/cgi.rb +37 -0
  85. data/spec/innate/helper/flash.rb +115 -0
  86. data/spec/innate/helper/link.rb +139 -0
  87. data/spec/innate/helper/redirect.rb +171 -0
  88. data/spec/innate/helper/render.rb +165 -0
  89. data/spec/innate/helper/send_file.rb +21 -0
  90. data/spec/innate/helper/view/aspect_hello.xhtml +1 -0
  91. data/spec/innate/helper/view/locals.xhtml +1 -0
  92. data/spec/innate/helper/view/loop.xhtml +4 -0
  93. data/spec/innate/helper/view/num.xhtml +1 -0
  94. data/spec/innate/helper/view/partial.xhtml +1 -0
  95. data/spec/innate/helper/view/recursive.xhtml +7 -0
  96. data/spec/innate/mock.rb +84 -0
  97. data/spec/innate/modes.rb +61 -0
  98. data/spec/innate/node/mapping.rb +37 -0
  99. data/spec/innate/node/node.rb +135 -0
  100. data/spec/innate/node/resolve.rb +82 -0
  101. data/spec/innate/node/view/another_layout/another_layout.xhtml +3 -0
  102. data/spec/innate/node/view/bar.xhtml +1 -0
  103. data/spec/innate/node/view/foo.html.xhtml +1 -0
  104. data/spec/innate/node/view/only_view.xhtml +1 -0
  105. data/spec/innate/node/view/with_layout.xhtml +1 -0
  106. data/spec/innate/node/wrap_action_call.rb +83 -0
  107. data/spec/innate/options.rb +123 -0
  108. data/spec/innate/parameter.rb +154 -0
  109. data/spec/innate/provides.rb +99 -0
  110. data/spec/innate/provides/list.html.xhtml +1 -0
  111. data/spec/innate/provides/list.txt.xhtml +1 -0
  112. data/spec/innate/request.rb +79 -0
  113. data/spec/innate/route.rb +135 -0
  114. data/spec/innate/session.rb +58 -0
  115. data/spec/innate/traited.rb +55 -0
  116. data/tasks/authors.rake +30 -0
  117. data/tasks/bacon.rake +66 -0
  118. data/tasks/changelog.rake +18 -0
  119. data/tasks/gem.rake +22 -0
  120. data/tasks/gem_setup.rake +99 -0
  121. data/tasks/grancher.rake +12 -0
  122. data/tasks/manifest.rake +4 -0
  123. data/tasks/rcov.rake +19 -0
  124. data/tasks/release.rake +53 -0
  125. data/tasks/reversion.rake +8 -0
  126. data/tasks/setup.rake +6 -0
  127. data/tasks/ycov.rake +84 -0
  128. metadata +218 -0
@@ -0,0 +1,133 @@
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 Session instance is compatible with the specification of rack.session.
13
+ #
14
+ # Since the Time class is used to create the cookie expiration timestamp, you
15
+ # will have to keep the ttl in a reasonable range.
16
+ # The maximum value that Time can store on a 32bit system is:
17
+ # Time.at(2147483647) # => Tue Jan 19 12:14:07 +0900 2038
18
+ #
19
+ # The default expiration time for cookies and the session cache was reduced
20
+ # to a default of 30 days.
21
+ # This was done to be compatible with the maximum ttl of MemCache. You may
22
+ # increase this value if you do not use MemCache to persist your sessions.
23
+ class Session
24
+ include Optioned
25
+
26
+ options.dsl do
27
+ o "Key for the session cookie",
28
+ :key, 'innate.sid'
29
+ o "Domain the cookie relates to, unspecified if false",
30
+ :domain, false
31
+ o "Path the cookie relates to",
32
+ :path, '/'
33
+ o "Use secure cookie",
34
+ :secure, false
35
+ o "Time of cookie expiration",
36
+ :expires, nil
37
+ o "Time to live for session cookies and cache, nil/false will prevent setting",
38
+ :ttl, (60 * 60 * 24 * 30) # 30 days
39
+
40
+ trigger(:expires){|v|
41
+ self.ttl = v - Time.now.to_i
42
+ Log.warn("Innate::Session.options.expires is deprecated, use #ttl instead")
43
+ }
44
+ end
45
+
46
+ attr_reader :cookie_set, :request, :response, :flash
47
+
48
+ def initialize(request, response)
49
+ @request, @response = request, response
50
+ @cookie_set = false
51
+ @cache_sid = nil
52
+ @flash = Flash.new(self)
53
+ end
54
+
55
+ # Rack interface
56
+
57
+ def store(key, value)
58
+ cache_sid[key] = value
59
+ end
60
+ alias []= store
61
+
62
+ def fetch(key, value = nil)
63
+ cache_sid[key]
64
+ end
65
+ alias [] fetch
66
+
67
+ def delete(key)
68
+ cache_sid.delete(key)
69
+ end
70
+
71
+ def clear
72
+ cache.delete(sid)
73
+ @cache_sid = nil
74
+ end
75
+
76
+ # Additional interface
77
+
78
+ def flush(response = @response)
79
+ return if !@cache_sid or @cache_sid.empty?
80
+
81
+ flash.rotate!
82
+ cache.store(sid, cache_sid, :ttl => options.ttl)
83
+ set_cookie(response)
84
+ end
85
+
86
+ def sid
87
+ @sid ||= cookie || generate_sid
88
+ end
89
+
90
+ private
91
+
92
+ def cache_sid
93
+ @cache_sid ||= cache[sid] || {}
94
+ end
95
+
96
+ def cookie
97
+ @request.cookies[options.key]
98
+ end
99
+
100
+ def cache
101
+ Innate::Cache.session
102
+ end
103
+
104
+ def set_cookie(response)
105
+ return if @cookie_set || cookie
106
+
107
+ @cookie_set = true
108
+ response.set_cookie(options.key, cookie_value)
109
+ end
110
+
111
+ def cookie_value
112
+ o = options
113
+ cookie = {:domain => o.domain, :path => o.path, :secure => o.secure}
114
+ cookie[:expires] = (Time.now + o.ttl) if o.ttl
115
+ cookie.merge!(:value => sid)
116
+ end
117
+
118
+ def generate_sid
119
+ begin sid = sid_algorithm end while cache[sid]
120
+ sid
121
+ end
122
+
123
+ begin
124
+ require 'securerandom'
125
+ def sid_algorithm; SecureRandom.hex(32); end
126
+ rescue LoadError
127
+ def sid_algorithm
128
+ entropy = [ srand, rand, Time.now.to_f, rand, $$, rand, object_id ]
129
+ Digest::SHA2.hexdigest(entropy.join)
130
+ end
131
+ end
132
+ end
133
+ 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
@@ -0,0 +1 @@
1
+ require 'innate/spec/bacon'
@@ -0,0 +1,28 @@
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 :rack_test do
19
+ Innate.setup_dependencies
20
+ extend Rack::Test::Methods
21
+
22
+ def app; Innate.middleware; end
23
+ end
24
+
25
+ shared :mock do
26
+ warn 'behaves_like(:mock) is deprecated, use behaves_like(:rack_test) instead'
27
+ behaves_like :rack_test
28
+ end
@@ -0,0 +1,26 @@
1
+ require 'thread'
2
+
3
+ module Innate
4
+ module SingletonMethods
5
+ # Use this method to achieve thread-safety for sensitive operations.
6
+ #
7
+ # This should be of most use when manipulating files to prevent other
8
+ # threads from doing the same, no other code will be scheduled during
9
+ # execution of this method.
10
+ #
11
+ # @param [Proc] block the things you want to execute
12
+ # @see State::Thread#sync State::Fiber#sync
13
+ def sync(&block)
14
+ Thread.exclusive(&block)
15
+ end
16
+
17
+ def defer
18
+ outer = ::Thread.current
19
+ ::Thread.new{
20
+ inner = ::Thread.current
21
+ outer.keys.each{|k| inner[k] = outer[k] }
22
+ yield
23
+ }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,130 @@
1
+ module Innate
2
+ # Simplify accessing Thread.current 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 Thread.current[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) Thread.current[%p] = obj; end" % [meth, key])
85
+ end
86
+ end
87
+
88
+ # Reader accessor for Thread.current[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 Thread.current.key?(key)
119
+ Thread.current[key] = instance_eval(&initializer)
120
+ else
121
+ Thread.current[key]
122
+ end
123
+ end
124
+ else
125
+ class_eval("def %s; Thread.current[%p]; end" % [meth, key])
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,90 @@
1
+ module Innate
2
+ # Traited helps you doing configuration similar to class variables.
3
+ #
4
+ # It's built on a simple Hash, where keys are objects and the values the
5
+ # configuration.
6
+ # By using {Traited#ancestral_trait} you will get nicely inherited
7
+ # configuration, where keys later in the ancestors will take precedence.
8
+ #
9
+ # @example usage
10
+ #
11
+ # class Foo
12
+ # include Innate::Traited
13
+ # trait :hello => 'Hello'
14
+ #
15
+ # def initialize
16
+ # trait :hello => 'World!'
17
+ # end
18
+ #
19
+ # def show
20
+ # [class_trait[:hello], trait[:hello], ancestral_trait[:hello]]
21
+ # end
22
+ # end
23
+ #
24
+ # Foo.trait[:hello] # => "Hello"
25
+ # foo = Foo.new
26
+ # foo.trait[:hello] # => "World!"
27
+ # foo.show # => ["Hello", "World!", "World!"]
28
+ module Traited
29
+ TRAITS, ANCESTRAL_TRAITS, ANCESTRAL_VALUES = {}, {}, {}
30
+
31
+ def self.included(into)
32
+ into.extend(self)
33
+ end
34
+
35
+ def trait(hash = nil)
36
+ if hash
37
+ TRAITS[self] ||= {}
38
+ result = TRAITS[self].merge!(hash)
39
+ ANCESTRAL_VALUES.clear
40
+ ANCESTRAL_TRAITS.clear
41
+ result
42
+ else
43
+ TRAITS[self] || {}
44
+ end
45
+ end
46
+
47
+ # Builds a trait from all the ancestors, closer ancestors overwrite distant
48
+ # ancestors
49
+ #
50
+ # class Foo
51
+ # include Innate::Traited
52
+ # trait :one => :eins, :first => :erstes
53
+ # end
54
+ #
55
+ # class Bar < Foo
56
+ # trait :two => :zwei
57
+ # end
58
+ #
59
+ # class Foobar < Bar
60
+ # trait :three => :drei, :first => :overwritten
61
+ # end
62
+ #
63
+ # Foobar.ancestral_trait
64
+ # # => {:three => :drei, :two => :zwei, :one => :eins, :first => :overwritten}
65
+ def ancestral_trait
66
+ klass = self.kind_of?(Module) ? self : self.class
67
+ ANCESTRAL_TRAITS[klass] ||=
68
+ each_ancestral_trait({}){|hash, trait| hash.update(trait) }
69
+ end
70
+
71
+ def ancestral_trait_values(key)
72
+ klass = self.kind_of?(Module) ? self : self.class
73
+ cache = ANCESTRAL_VALUES[klass] ||= {}
74
+ cache[key] ||= each_ancestral_trait([]){|array, trait|
75
+ array << trait[key] if trait.key?(key) }
76
+ end
77
+
78
+ def each_ancestral_trait(obj)
79
+ ancs = respond_to?(:ancestors) ? ancestors : self.class.ancestors
80
+ ancs.unshift(self)
81
+ ancs.reverse_each{|anc| yield(obj, TRAITS[anc]) if TRAITS.key?(anc) }
82
+ obj
83
+ end
84
+
85
+ # trait for self.class if we are an instance
86
+ def class_trait
87
+ respond_to?(:ancestors) ? trait : self.class.trait
88
+ end
89
+ end
90
+ end