manveru-innate 2009.02.06

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 (101) hide show
  1. data/CHANGELOG +1409 -0
  2. data/COPYING +18 -0
  3. data/MANIFEST +100 -0
  4. data/README.md +485 -0
  5. data/Rakefile +139 -0
  6. data/example/app/retro_games.rb +57 -0
  7. data/example/app/whywiki_erb/layout/wiki.html.erb +15 -0
  8. data/example/app/whywiki_erb/spec/wiki.rb +19 -0
  9. data/example/app/whywiki_erb/start.rb +45 -0
  10. data/example/app/whywiki_erb/view/edit.html.erb +6 -0
  11. data/example/app/whywiki_erb/view/index.html.erb +10 -0
  12. data/example/custom_middleware.rb +43 -0
  13. data/example/error_handling.rb +31 -0
  14. data/example/hello.rb +12 -0
  15. data/example/howto_spec.rb +60 -0
  16. data/example/link.rb +35 -0
  17. data/example/providing_hash.rb +46 -0
  18. data/example/session.rb +42 -0
  19. data/innate.gemspec +118 -0
  20. data/lib/innate.rb +191 -0
  21. data/lib/innate/action.rb +156 -0
  22. data/lib/innate/adapter.rb +89 -0
  23. data/lib/innate/cache.rb +117 -0
  24. data/lib/innate/cache/api.rb +106 -0
  25. data/lib/innate/cache/drb.rb +58 -0
  26. data/lib/innate/cache/file_based.rb +39 -0
  27. data/lib/innate/cache/marshal.rb +17 -0
  28. data/lib/innate/cache/memory.rb +22 -0
  29. data/lib/innate/cache/yaml.rb +17 -0
  30. data/lib/innate/core_compatibility/basic_object.rb +9 -0
  31. data/lib/innate/core_compatibility/string.rb +3 -0
  32. data/lib/innate/current.rb +37 -0
  33. data/lib/innate/dynamap.rb +81 -0
  34. data/lib/innate/helper.rb +195 -0
  35. data/lib/innate/helper/aspect.rb +62 -0
  36. data/lib/innate/helper/cgi.rb +39 -0
  37. data/lib/innate/helper/flash.rb +36 -0
  38. data/lib/innate/helper/link.rb +55 -0
  39. data/lib/innate/helper/partial.rb +90 -0
  40. data/lib/innate/helper/redirect.rb +85 -0
  41. data/lib/innate/helper/send_file.rb +18 -0
  42. data/lib/innate/log.rb +23 -0
  43. data/lib/innate/log/color_formatter.rb +43 -0
  44. data/lib/innate/log/hub.rb +72 -0
  45. data/lib/innate/mock.rb +49 -0
  46. data/lib/innate/node.rb +471 -0
  47. data/lib/innate/options.rb +91 -0
  48. data/lib/innate/options/dsl.rb +155 -0
  49. data/lib/innate/request.rb +165 -0
  50. data/lib/innate/response.rb +18 -0
  51. data/lib/innate/route.rb +109 -0
  52. data/lib/innate/session.rb +104 -0
  53. data/lib/innate/session/flash.rb +94 -0
  54. data/lib/innate/setup.rb +23 -0
  55. data/lib/innate/spec.rb +42 -0
  56. data/lib/innate/state.rb +22 -0
  57. data/lib/innate/state/accessor.rb +130 -0
  58. data/lib/innate/state/fiber.rb +68 -0
  59. data/lib/innate/state/thread.rb +39 -0
  60. data/lib/innate/traited.rb +20 -0
  61. data/lib/innate/trinity.rb +22 -0
  62. data/lib/innate/version.rb +3 -0
  63. data/lib/innate/view.rb +67 -0
  64. data/lib/innate/view/erb.rb +17 -0
  65. data/lib/innate/view/none.rb +9 -0
  66. data/lib/rack/middleware_compiler.rb +62 -0
  67. data/lib/rack/reloader.rb +192 -0
  68. data/spec/example/hello.rb +14 -0
  69. data/spec/example/link.rb +29 -0
  70. data/spec/helper.rb +2 -0
  71. data/spec/innate/cache/common.rb +45 -0
  72. data/spec/innate/cache/marshal.rb +5 -0
  73. data/spec/innate/cache/memory.rb +5 -0
  74. data/spec/innate/cache/yaml.rb +5 -0
  75. data/spec/innate/dynamap.rb +22 -0
  76. data/spec/innate/helper.rb +66 -0
  77. data/spec/innate/helper/aspect.rb +80 -0
  78. data/spec/innate/helper/cgi.rb +37 -0
  79. data/spec/innate/helper/flash.rb +148 -0
  80. data/spec/innate/helper/link.rb +82 -0
  81. data/spec/innate/helper/partial.rb +66 -0
  82. data/spec/innate/helper/redirect.rb +148 -0
  83. data/spec/innate/helper/send_file.rb +21 -0
  84. data/spec/innate/helper/view/aspect_hello.erb +1 -0
  85. data/spec/innate/helper/view/locals.erb +1 -0
  86. data/spec/innate/helper/view/loop.erb +4 -0
  87. data/spec/innate/helper/view/num.erb +1 -0
  88. data/spec/innate/helper/view/partial.erb +1 -0
  89. data/spec/innate/helper/view/recursive.erb +8 -0
  90. data/spec/innate/mock.rb +84 -0
  91. data/spec/innate/node.rb +180 -0
  92. data/spec/innate/node/bar.html +1 -0
  93. data/spec/innate/node/foo.html.erb +1 -0
  94. data/spec/innate/node/with_layout.erb +3 -0
  95. data/spec/innate/options.rb +90 -0
  96. data/spec/innate/parameter.rb +154 -0
  97. data/spec/innate/request.rb +73 -0
  98. data/spec/innate/route.rb +129 -0
  99. data/spec/innate/session.rb +59 -0
  100. data/spec/innate/traited.rb +55 -0
  101. metadata +160 -0
@@ -0,0 +1,91 @@
1
+ require 'innate/options/dsl'
2
+
3
+ module Innate
4
+ @options = Options.new(:innate)
5
+
6
+ def self.options; @options end
7
+
8
+ # This has to speak for itself.
9
+
10
+ options.dsl do
11
+ o "IP address or hostname that Ramaze will respond to - 0.0.0.0 for all",
12
+ :host, "0.0.0.0", :short => :H
13
+
14
+ o "Port for the server",
15
+ :port, 7000, :short => :p
16
+
17
+ o "Indicate that calls Innate::start will be ignored",
18
+ :started, false
19
+
20
+ o "Web server to run on",
21
+ :adapter, :webrick, :short => :a
22
+
23
+ o "Will send ::setup to each element during Innate::start",
24
+ :setup, [Innate::Cache, Innate::Node]
25
+
26
+ o "Headers that will be merged into the response before Node::call",
27
+ :header, {'Content-Type' => 'text/html'}
28
+
29
+ o "Trap this signal to issue shutdown, nil/false to disable trap",
30
+ :trap, 'SIGINT'
31
+
32
+ o "Keep state in Thread or Fiber, fall back to Thread if Fiber not available",
33
+ :state, :Fiber
34
+
35
+ sub :log do
36
+ o "Array of parameters for Logger.new, default to stderr for CGI",
37
+ :params, [$stderr]
38
+ o "Use ANSI colors for logging, nil does auto-detection by checking for #tty?",
39
+ :color, nil
40
+ end
41
+
42
+ sub :redirect do
43
+ o "Default response HTTP status on redirect",
44
+ :status, 302
45
+ end
46
+
47
+ sub :env do
48
+ o "Hostname of this machine",
49
+ :host, ENV['HOSTNAME'] # FIXME: cross-platform
50
+ o "Username executing the application",
51
+ :user, ENV['USER'] # FIXME: cross-platform
52
+ end
53
+
54
+ sub :app do
55
+ o "Unique identifier for this application",
56
+ :name, 'pristine'
57
+ o "Root directory containing the application",
58
+ :root, File.dirname($0)
59
+ o "Root directory for view templates, relative to app subdir",
60
+ :view, '/view'
61
+ o "Root directory for layout templates, relative to app subdir",
62
+ :layout, '/layout'
63
+ end
64
+
65
+ sub :session do
66
+ o "Key for the session cookie",
67
+ :key, 'innate.sid'
68
+ o "Domain the cookie relates to, unspecified if false",
69
+ :domain, false
70
+ o "Path the cookie relates to",
71
+ :path, '/'
72
+ o "Use secure cookie",
73
+ :secure, false
74
+ o "Time of cookie expiration",
75
+ :expires, Time.at(2147483647)
76
+ end
77
+
78
+ sub :cache do
79
+ o "Assign a cache to each of these names on Innate::Cache::setup",
80
+ :names, [:session]
81
+
82
+ default "If no option for the cache name exists, fall back to this",
83
+ Innate::Cache::Memory
84
+ end
85
+
86
+ sub :action do
87
+ o "wish => Action#method",
88
+ :wish, {'json' => :as_json, 'html' => :as_html, 'yaml' => :as_yaml}
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,155 @@
1
+ module Innate
2
+ # Provides a minimal DSL to describe options with defaults and metadata.
3
+ #
4
+ # The example below should demonstrate the major features, note that key
5
+ # lookup wanders up the hierarchy until there is a match found or the parent
6
+ # of the Options class is itself, in which case nil will be returned.
7
+ #
8
+ # Usage:
9
+ #
10
+ # class Calculator
11
+ # @options = Options.new(:foo)
12
+ # def self.options; @options; end
13
+ #
14
+ # options.dsl do
15
+ # o "Which method to use", :method, :plus
16
+ # o "Default arguments", :args, [1, 2]
17
+ # sub(:minus){ o("Default arguments", :args, [4, 3]) }
18
+ # end
19
+ #
20
+ # def self.calculate(method = nil, *args)
21
+ # method ||= options[:method]
22
+ # args = args.empty? ? options[method, :args] : args
23
+ # self.send(method, *args)
24
+ # end
25
+ #
26
+ # def self.plus(n1, n2)
27
+ # n1 + n2
28
+ # end
29
+ #
30
+ # def self.minus(n1, n2)
31
+ # n1 - n2
32
+ # end
33
+ # end
34
+ #
35
+ # Calculator.calculate
36
+ # # => 3
37
+ # Calculator.options[:method] = :minus
38
+ # # => :minus
39
+ # Calculator.calculate
40
+ # # => 1
41
+ # Calculator.calculate(:plus, 4, 5)
42
+ # # => 9
43
+ #
44
+ class Options
45
+ def initialize(name, parent = self, &block)
46
+ @name, @parent, = name, parent
47
+ @hash = {}
48
+ yield(self) if block_given?
49
+ end
50
+
51
+ # Shortcut for instance_eval
52
+ def dsl(&block)
53
+ instance_eval(&block) if block
54
+ self
55
+ end
56
+
57
+ # Create a new Options instance with +name+ and pass +block+ on to its #dsl.
58
+ # Assigns the new instance to the +name+ Symbol on current instance.
59
+ def sub(name, &block)
60
+ name = name.to_sym
61
+
62
+ case found = @hash[name]
63
+ when Options
64
+ found.dsl(&block)
65
+ else
66
+ found = @hash[name] = Options.new(name, self).dsl(&block)
67
+ end
68
+
69
+ found
70
+ end
71
+
72
+ # Store an option in the Options instance.
73
+ #
74
+ # +doc+ should be a String describing the purpose of this option
75
+ # +key+ should be a Symbol used to access
76
+ # +value+ may be any object
77
+ # +other+ optional Hash that may contain meta-data and should not have :doc
78
+ # or :value keys
79
+ def o(doc, key, value, other = {})
80
+ @hash[key.to_sym] = other.merge(:doc => doc, :value => value)
81
+ end
82
+
83
+ # To avoid lookup on the parent, we can set a default to the internal Hash.
84
+ # Parameters as in #o, but without the +key+.
85
+ def default(doc, value, other = {})
86
+ @hash.default = other.merge(:doc => doc, :value => value)
87
+ end
88
+
89
+ # Try to retrieve the corresponding Hash for the passed keys, will try to
90
+ # retrieve the key from a parent if no match is found on the current
91
+ # instance. If multiple keys are passed it will try to find a matching
92
+ # child and pass the request on to it.
93
+ def get(key, *keys)
94
+ if keys.empty?
95
+ if value = @hash[key.to_sym]
96
+ value
97
+ elsif @parent != self
98
+ @parent.get(key)
99
+ else
100
+ nil
101
+ end
102
+ elsif sub_options = get(key)
103
+ sub_options.get(*keys)
104
+ end
105
+ end
106
+
107
+ # Retrieve only the :value from the value hash if found via +keys+.
108
+ def [](*keys)
109
+ if value = get(*keys)
110
+ value.is_a?(Hash) ? value[:value] : value
111
+ end
112
+ end
113
+
114
+ # Assign new :value to the value hash on the current instance.
115
+ #
116
+ # TODO: allow arbitrary assignments
117
+
118
+ def []=(key, value)
119
+ if ns = @hash[key.to_sym]
120
+ ns[:value] = value
121
+ else
122
+ raise(ArgumentError, "No key for %p exists" % [key])
123
+ end
124
+ end
125
+
126
+ def method_missing(meth, *args)
127
+ case meth.to_s
128
+ when /^(.*)=$/
129
+ self[$1] = args.first
130
+ else
131
+ self[meth]
132
+ end
133
+ end
134
+
135
+ def merge!(hash)
136
+ hash.each do |key, value|
137
+ self[key] = value
138
+ end
139
+ end
140
+
141
+ include Enumerable
142
+
143
+ def each(&block)
144
+ @hash.each(&block)
145
+ end
146
+
147
+ def inspect
148
+ @hash.inspect
149
+ end
150
+
151
+ def pretty_print(q)
152
+ q.pp_hash @hash
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,165 @@
1
+ module Innate
2
+
3
+ # Subclass Rack::Request and add some convenient methods.
4
+ #
5
+ # An instance is available via the #request method in your Node.
6
+ #
7
+ # NOTE:
8
+ # Please make sure to read the documentation of Rack::Request together with
9
+ # this, as there are a lot of features available.
10
+ #
11
+ # A list of methods from Rack::Request so you get a gist of it:
12
+ #
13
+ # ## Generally
14
+ #
15
+ # * body
16
+ # * cookies
17
+ # * env
18
+ # * fullpath
19
+ # * host
20
+ # * port
21
+ # * scheme
22
+ # * url
23
+ #
24
+ # ## ENV shortcuts
25
+ #
26
+ # * accept_encoding
27
+ # * content_charset
28
+ # * content_length
29
+ # * content_type
30
+ # * ip
31
+ # * media_type
32
+ # * media_type_params
33
+ # * path_info
34
+ # * path_info=
35
+ # * query_string
36
+ # * referrer
37
+ # * script_name
38
+ # * script_name=
39
+ # * xhr?
40
+ #
41
+ # ## Query request method
42
+ #
43
+ # * delete?
44
+ # * get?
45
+ # * head?
46
+ # * post?
47
+ # * put?
48
+ # * request_method
49
+ #
50
+ # ## parameter handling
51
+ #
52
+ # * []
53
+ # * []=
54
+ # * form_data?
55
+ # * params
56
+ # * values_at
57
+
58
+ class Request < Rack::Request
59
+ # Currently handled request from Innate::STATE[:request]
60
+ # Call it from anywhere via Innate::Request.current
61
+ def self.current
62
+ Current.request
63
+ end
64
+
65
+ # Let's allow #[] to act like #values_at.
66
+ #
67
+ # Usage given a GET request like /hey?foo=duh&bar=doh
68
+ #
69
+ # request[:foo, :bar] # => ['duh', 'doh']
70
+ #
71
+ # Both +value+ and the elements of +keys+ will be turned into String by #to_s.
72
+ def [](value, *keys)
73
+ return super(value) if keys.empty?
74
+ [value, *keys].map{|k| super(k) }
75
+ end
76
+
77
+ # the full request URI provided by Rack::Request
78
+ # e.g. "http://localhost:7000/controller/action?foo=bar.xhtml"
79
+ def request_uri
80
+ env['REQUEST_URI'] || env['PATH_INFO']
81
+ end
82
+
83
+ # Answers with a subset of request.params with only the key/value pairs for
84
+ # which you pass the keys.
85
+ # Valid keys are objects that respond to :to_s
86
+ #
87
+ # Example:
88
+ # request.params
89
+ # # => {'name' => 'jason', 'age' => '45', 'job' => 'lumberjack'}
90
+ # request.subset('name')
91
+ # # => {'name' => 'jason'}
92
+ # request.subset(:name, :job)
93
+ # # => {'name' => 'jason', 'job' => 'lumberjack'}
94
+
95
+ def subset(*keys)
96
+ keys = keys.map{|k| k.to_s }
97
+ params.reject{|k,v| not keys.include?(k) }
98
+ end
99
+
100
+ # Try to figure out the domain we are running on, this might work for some
101
+ # deployments but fail for others, given the combination of servers in
102
+ # front.
103
+
104
+ def domain(path = '/')
105
+ scheme = self.scheme || 'http'
106
+ host = env['HTTP_HOST']
107
+
108
+ URI("#{scheme}://#{host}#{path}")
109
+ end
110
+
111
+ # Try to find out which languages the client would like to have.
112
+ # Returns and array of locales from env['HTTP_ACCEPT_LANGUAGE].
113
+ # e.g. ["fi", "en", "ja", "fr", "de", "es", "it", "nl", "sv"]
114
+
115
+ def accept_language
116
+ env['HTTP_ACCEPT_LANGUAGE'].to_s.split(/(?:,|;q=[\d.,]+)/)
117
+ end
118
+ alias locales accept_language
119
+
120
+ ipv4 = %w[ 127.0.0.1/32 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 169.254.0.0/16 ]
121
+ ipv6 = %w[ fc00::/7 fe80::/10 fec0::/10 ::1 ]
122
+ LOCAL = (ipv4 + ipv6).map{|a| IPAddr.new(a)} unless defined?(LOCAL)
123
+
124
+ # Request is from a local network?
125
+ # Checks both IPv4 and IPv6
126
+ # Answer is true if the IP address making the request is from local network.
127
+ # Optional argument address can be used to check any IP address.
128
+
129
+ def local_net?(address = ip)
130
+ addr = IPAddr.new(address)
131
+ LOCAL.find{|range| range.include?(addr) }
132
+ rescue ArgumentError => ex
133
+ raise ArgumentError, ex unless ex.message == 'invalid address'
134
+ end
135
+
136
+ INTERESTING_HTTP_VARIABLES =
137
+ (/USER|HOST|REQUEST|REMOTE|FORWARD|REFER|PATH|QUERY|VERSION|KEEP|CACHE/)
138
+
139
+ # Interesting HTTP variables from env
140
+ def http_variables
141
+ env.reject{|key, value| key.to_s !~ INTERESTING_HTTP_VARIABLES }
142
+ end
143
+ alias http_vars http_variables
144
+
145
+ REQUEST_STRING_FORMAT = "#<%s params=%p cookies=%p env=%p>"
146
+
147
+ def to_s
148
+ REQUEST_STRING_FORMAT % [self.class, params, cookies, http_variables]
149
+ end
150
+ alias inspect to_s
151
+
152
+ # Pretty prints current action with parameters, cookies and enviroment
153
+ # variables.
154
+ def pretty_print(pp)
155
+ pp.object_group(self){
156
+ group = { 'params' => params, 'cookies' => cookies, 'env' => http_vars }
157
+ group.each do |name, hash|
158
+ pp.breakable
159
+ pp.text " @#{name}="
160
+ pp.nest(name.size + 3){ pp.pp_hash(hash) }
161
+ end
162
+ }
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,18 @@
1
+ module Innate
2
+
3
+ # In order to reset the body contents we also need to reset the length set by
4
+ # Response#write - until I can submit a patch to Rack and the next release we
5
+ # just do this.
6
+
7
+ class Response < Rack::Response
8
+ attr_accessor :length
9
+
10
+ def reset
11
+ self.status = 200
12
+ self.header.delete('Content-Type')
13
+ body.clear
14
+ self.length = 0
15
+ self
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,109 @@
1
+ module Innate
2
+ # Innate support simple routing using string, regex and lambda based routers.
3
+ # Route are stored in a dictionary, which supports hash-like access but
4
+ # preserves order, so routes are evaluated in the order they are added.
5
+ #
6
+ # This middleware should wrap Innate::DynaMap.
7
+ #
8
+ # String routers are the simplest way to route in Innate. One path is
9
+ # translated into another:
10
+ #
11
+ # Innate::Route[ '/foo' ] = '/bar'
12
+ # '/foo' => '/bar'
13
+ #
14
+ # Regex routers allow matching against paths using regex. Matches within
15
+ # your regex using () are substituted in the new path using printf-like
16
+ # syntax.
17
+ #
18
+ # Innate::Route[ %r!^/(\d+)\.te?xt$! ] = "/text/%d"
19
+ # '/123.txt' => '/text/123'
20
+ # '/789.text' => '/text/789'
21
+ #
22
+ # For more complex routing, lambda routers can be used. Lambda routers are
23
+ # passed in the current path and request object, and must return either a new
24
+ # path string, or nil.
25
+ #
26
+ # Innate::Route[ 'name of route' ] = lambda{ |path, request|
27
+ # '/bar' if path == '/foo' and request[:bar] == '1'
28
+ # }
29
+ # '/foo' => '/foo'
30
+ # '/foo?bar=1' => '/bar'
31
+ #
32
+ # Lambda routers can also use this alternative syntax:
33
+ #
34
+ # Innate::Route('name of route') do |path, request|
35
+ # '/bar' if path == '/foo' and request[:bar] == '1'
36
+ # end
37
+ #
38
+ # NOTE: Use self::ROUTES notation in singleton methods to force correct
39
+ # lookup.
40
+
41
+ class Route
42
+ ROUTES = []
43
+
44
+ def self.[](key)
45
+ found = self::ROUTES.assoc(key)
46
+ found[1] if found
47
+ end
48
+
49
+ def self.[]=(key, value)
50
+ self::ROUTES.delete_if{|k,v| k == key }
51
+ self::ROUTES << [key, value]
52
+ end
53
+
54
+ def self.clear
55
+ self::ROUTES.clear
56
+ end
57
+
58
+ def initialize(app = Innate::DynaMap)
59
+ @app = app
60
+ end
61
+
62
+ def call(env)
63
+ path = env['PATH_INFO']
64
+ path << '/' if path.empty?
65
+
66
+ if modified = resolve(path)
67
+ env['PATH_INFO'] = modified
68
+ end
69
+
70
+ @app.call(env)
71
+ end
72
+
73
+ def resolve(path)
74
+ request = Current.request
75
+
76
+ self.class::ROUTES.each do |key, value|
77
+ if key.is_a?(Regexp)
78
+ md = path.match(key)
79
+ return value % md.to_a[1..-1] if md
80
+
81
+ elsif value.respond_to?(:call)
82
+ new_path = value.call(path, Request.current)
83
+ return new_path if new_path
84
+
85
+ elsif value.respond_to?(:to_str)
86
+ return value.to_str if path == key
87
+
88
+ else
89
+ Log.error("Invalid route %p => %p" % [key, value])
90
+ end
91
+ end
92
+
93
+ nil
94
+ end
95
+ end
96
+
97
+ # Identical with Innate::Route, but is called before any Node::call is made
98
+ class Rewrite < Route
99
+ ROUTES = []
100
+ end
101
+
102
+ def self.Route(key, value = nil, &block)
103
+ Route[key] = value || block
104
+ end
105
+
106
+ def self.Rewrite(key, value = nil, &block)
107
+ Rewrite[key] = value || block
108
+ end
109
+ end