renee 0.3.11 → 0.4.0.pre1

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 (231) hide show
  1. data/Gemfile +17 -0
  2. data/Gemfile-renee +8 -0
  3. data/Gemfile-renee-core +8 -0
  4. data/Gemfile-renee-render +9 -0
  5. data/Gemfile-renee-session +9 -0
  6. data/Gemfile-renee-url-generation +8 -0
  7. data/MIT-LICENSE.txt +7 -0
  8. data/README-renee-core.md +242 -0
  9. data/README-renee-render.md +38 -0
  10. data/README-renee-session.md +3 -0
  11. data/README-renee-url-generation.md +3 -0
  12. data/README.md +131 -6
  13. data/Rakefile +109 -9
  14. data/TODO.txt +45 -0
  15. data/config.ru +26 -0
  16. data/examples/blog/blog.rb +3 -1
  17. data/examples/blog/config.ru +24 -19
  18. data/examples/blog/views/edit.erb +10 -1
  19. data/examples/blog/views/show.erb +5 -0
  20. data/lib/renee.rb +11 -4
  21. data/lib/renee/core.rb +98 -0
  22. data/lib/renee/core/chaining.rb +66 -0
  23. data/lib/renee/core/env_accessors.rb +72 -0
  24. data/lib/renee/core/exceptions.rb +15 -0
  25. data/lib/renee/core/matcher.rb +61 -0
  26. data/lib/renee/core/plugins.rb +31 -0
  27. data/lib/renee/core/rack_interaction.rb +50 -0
  28. data/lib/renee/core/request_context.rb +56 -0
  29. data/lib/renee/core/responding.rb +112 -0
  30. data/lib/renee/core/response.rb +78 -0
  31. data/lib/renee/core/routing.rb +319 -0
  32. data/lib/renee/core/transform.rb +18 -0
  33. data/lib/renee/render.rb +221 -0
  34. data/lib/renee/session.rb +50 -0
  35. data/lib/renee/url_generation.rb +117 -0
  36. data/lib/renee/util.rb +7 -0
  37. data/lib/renee/version.rb +2 -4
  38. data/plan.txt +19 -0
  39. data/renee-core.gemspec +26 -0
  40. data/renee-render.gemspec +30 -0
  41. data/renee-session.gemspec +28 -0
  42. data/renee-url-generation.gemspec +24 -0
  43. data/renee.gemspec +5 -6
  44. data/site/MIT-LICENSE.txt +7 -0
  45. data/site/public/css/app.css +75 -0
  46. data/site/public/docs/renee-core/Renee.html +208 -0
  47. data/site/public/docs/renee-core/Renee/Core.html +366 -0
  48. data/site/public/docs/renee-core/Renee/Core/Chaining.html +192 -0
  49. data/site/public/docs/renee-core/Renee/Core/ClassMethods.html +725 -0
  50. data/site/public/docs/renee-core/Renee/Core/ClientError.html +317 -0
  51. data/site/public/docs/renee-core/Renee/Core/EnvAccessors.html +152 -0
  52. data/site/public/docs/renee-core/Renee/Core/EnvAccessors/ClassMethods.html +354 -0
  53. data/site/public/docs/renee-core/Renee/Core/Matcher.html +675 -0
  54. data/site/public/docs/renee-core/Renee/Core/Plugins.html +475 -0
  55. data/site/public/docs/renee-core/Renee/Core/RackInteraction.html +488 -0
  56. data/site/public/docs/renee-core/Renee/Core/RequestContext.html +511 -0
  57. data/site/public/docs/renee-core/Renee/Core/Responding.html +877 -0
  58. data/site/public/docs/renee-core/Renee/Core/Response.html +691 -0
  59. data/site/public/docs/renee-core/Renee/Core/Routing.html +1589 -0
  60. data/site/public/docs/renee-core/Renee/Core/Transform.html +249 -0
  61. data/site/public/docs/renee-core/Renee/Core/URLGeneration.html +597 -0
  62. data/site/public/docs/renee-core/_index.html +244 -0
  63. data/site/public/docs/renee-core/class_list.html +47 -0
  64. data/site/public/docs/renee-core/css/common.css +1 -0
  65. data/site/public/docs/renee-core/css/full_list.css +55 -0
  66. data/site/public/docs/renee-core/css/style.css +322 -0
  67. data/site/public/docs/renee-core/file.README-renee-core.html +341 -0
  68. data/site/public/docs/renee-core/file.README.html +212 -0
  69. data/site/public/docs/renee-core/file_list.html +49 -0
  70. data/site/public/docs/renee-core/frames.html +13 -0
  71. data/site/public/docs/renee-core/index.html +341 -0
  72. data/site/public/docs/renee-core/js/app.js +205 -0
  73. data/site/public/docs/renee-core/js/full_list.js +167 -0
  74. data/site/public/docs/renee-core/js/jquery.js +16 -0
  75. data/site/public/docs/renee-core/method_list.html +590 -0
  76. data/site/public/docs/renee-core/top-level-namespace.html +103 -0
  77. data/site/public/docs/renee-render/Renee.html +116 -0
  78. data/site/public/docs/renee-render/Renee/Core.html +346 -0
  79. data/site/public/docs/renee-render/Renee/Core/Chaining.html +125 -0
  80. data/site/public/docs/renee-render/Renee/Core/ClassMethods.html +620 -0
  81. data/site/public/docs/renee-render/Renee/Core/ClientError.html +317 -0
  82. data/site/public/docs/renee-render/Renee/Core/EnvAccessors.html +152 -0
  83. data/site/public/docs/renee-render/Renee/Core/EnvAccessors/ClassMethods.html +354 -0
  84. data/site/public/docs/renee-render/Renee/Core/Matcher.html +675 -0
  85. data/site/public/docs/renee-render/Renee/Core/RackInteraction.html +488 -0
  86. data/site/public/docs/renee-render/Renee/Core/RequestContext.html +421 -0
  87. data/site/public/docs/renee-render/Renee/Core/Responding.html +873 -0
  88. data/site/public/docs/renee-render/Renee/Core/Response.html +691 -0
  89. data/site/public/docs/renee-render/Renee/Core/Routing.html +1682 -0
  90. data/site/public/docs/renee-render/Renee/Core/Transform.html +249 -0
  91. data/site/public/docs/renee-render/Renee/Core/URLGeneration.html +597 -0
  92. data/site/public/docs/renee-render/Renee/Render.html +873 -0
  93. data/site/public/docs/renee-render/Renee/Render/ClassMethods.html +382 -0
  94. data/site/public/docs/renee-render/Renee/Render/TemplateNotFound.html +126 -0
  95. data/site/public/docs/renee-render/_index.html +143 -0
  96. data/site/public/docs/renee-render/class_list.html +47 -0
  97. data/site/public/docs/renee-render/css/common.css +1 -0
  98. data/site/public/docs/renee-render/css/full_list.css +55 -0
  99. data/site/public/docs/renee-render/css/style.css +322 -0
  100. data/site/public/docs/renee-render/file.README-renee-render.html +104 -0
  101. data/site/public/docs/renee-render/file.README.html +212 -0
  102. data/site/public/docs/renee-render/file_list.html +49 -0
  103. data/site/public/docs/renee-render/frames.html +13 -0
  104. data/site/public/docs/renee-render/index.html +104 -0
  105. data/site/public/docs/renee-render/js/app.js +205 -0
  106. data/site/public/docs/renee-render/js/full_list.js +167 -0
  107. data/site/public/docs/renee-render/js/jquery.js +16 -0
  108. data/site/public/docs/renee-render/method_list.html +110 -0
  109. data/site/public/docs/renee-render/top-level-namespace.html +103 -0
  110. data/site/public/docs/renee-session/Renee.html +106 -0
  111. data/site/public/docs/renee-session/Renee/Session.html +173 -0
  112. data/site/public/docs/renee-session/Renee/Session/ClassMethods.html +470 -0
  113. data/site/public/docs/renee-session/_index.html +136 -0
  114. data/site/public/docs/renee-session/class_list.html +47 -0
  115. data/site/public/docs/renee-session/css/common.css +1 -0
  116. data/site/public/docs/renee-session/css/full_list.css +55 -0
  117. data/site/public/docs/renee-session/css/style.css +322 -0
  118. data/site/public/docs/renee-session/file.README-renee-core.html +341 -0
  119. data/site/public/docs/renee-session/file.README-renee-session.html +69 -0
  120. data/site/public/docs/renee-session/file_list.html +49 -0
  121. data/site/public/docs/renee-session/frames.html +13 -0
  122. data/site/public/docs/renee-session/index.html +69 -0
  123. data/site/public/docs/renee-session/js/app.js +205 -0
  124. data/site/public/docs/renee-session/js/full_list.js +167 -0
  125. data/site/public/docs/renee-session/js/jquery.js +16 -0
  126. data/site/public/docs/renee-session/method_list.html +102 -0
  127. data/site/public/docs/renee-session/top-level-namespace.html +103 -0
  128. data/site/public/docs/renee-url-generation/Renee.html +208 -0
  129. data/site/public/docs/renee-url-generation/Renee/Core.html +366 -0
  130. data/site/public/docs/renee-url-generation/Renee/Core/Chaining.html +192 -0
  131. data/site/public/docs/renee-url-generation/Renee/Core/ClassMethods.html +725 -0
  132. data/site/public/docs/renee-url-generation/Renee/Core/ClientError.html +317 -0
  133. data/site/public/docs/renee-url-generation/Renee/Core/EnvAccessors.html +152 -0
  134. data/site/public/docs/renee-url-generation/Renee/Core/EnvAccessors/ClassMethods.html +354 -0
  135. data/site/public/docs/renee-url-generation/Renee/Core/Matcher.html +675 -0
  136. data/site/public/docs/renee-url-generation/Renee/Core/Plugins.html +475 -0
  137. data/site/public/docs/renee-url-generation/Renee/Core/RackInteraction.html +488 -0
  138. data/site/public/docs/renee-url-generation/Renee/Core/RequestContext.html +511 -0
  139. data/site/public/docs/renee-url-generation/Renee/Core/Responding.html +877 -0
  140. data/site/public/docs/renee-url-generation/Renee/Core/Response.html +691 -0
  141. data/site/public/docs/renee-url-generation/Renee/Core/Routing.html +1589 -0
  142. data/site/public/docs/renee-url-generation/Renee/Core/Transform.html +249 -0
  143. data/site/public/docs/renee-url-generation/_index.html +244 -0
  144. data/site/public/docs/renee-url-generation/class_list.html +47 -0
  145. data/site/public/docs/renee-url-generation/css/common.css +1 -0
  146. data/site/public/docs/renee-url-generation/css/full_list.css +55 -0
  147. data/site/public/docs/renee-url-generation/css/style.css +322 -0
  148. data/site/public/docs/renee-url-generation/file.README-renee-url-generation.html +69 -0
  149. data/site/public/docs/renee-url-generation/file_list.html +49 -0
  150. data/site/public/docs/renee-url-generation/frames.html +13 -0
  151. data/site/public/docs/renee-url-generation/index.html +69 -0
  152. data/site/public/docs/renee-url-generation/js/app.js +205 -0
  153. data/site/public/docs/renee-url-generation/js/full_list.js +167 -0
  154. data/site/public/docs/renee-url-generation/js/jquery.js +16 -0
  155. data/site/public/docs/renee-url-generation/method_list.html +590 -0
  156. data/site/public/docs/renee-url-generation/top-level-namespace.html +103 -0
  157. data/site/public/docs/renee/Renee.html +232 -0
  158. data/site/public/docs/renee/Renee/Application.html +367 -0
  159. data/site/public/docs/renee/Renee/Core.html +370 -0
  160. data/site/public/docs/renee/Renee/Core/Chaining.html +192 -0
  161. data/site/public/docs/renee/Renee/Core/ClassMethods.html +725 -0
  162. data/site/public/docs/renee/Renee/Core/ClientError.html +317 -0
  163. data/site/public/docs/renee/Renee/Core/EnvAccessors.html +152 -0
  164. data/site/public/docs/renee/Renee/Core/EnvAccessors/ClassMethods.html +354 -0
  165. data/site/public/docs/renee/Renee/Core/Matcher.html +675 -0
  166. data/site/public/docs/renee/Renee/Core/Plugins.html +475 -0
  167. data/site/public/docs/renee/Renee/Core/RackInteraction.html +488 -0
  168. data/site/public/docs/renee/Renee/Core/RequestContext.html +511 -0
  169. data/site/public/docs/renee/Renee/Core/Responding.html +877 -0
  170. data/site/public/docs/renee/Renee/Core/Response.html +691 -0
  171. data/site/public/docs/renee/Renee/Core/Routing.html +1589 -0
  172. data/site/public/docs/renee/Renee/Core/Transform.html +249 -0
  173. data/site/public/docs/renee/Renee/Core/URLGeneration.html +597 -0
  174. data/site/public/docs/renee/Renee/Render.html +877 -0
  175. data/site/public/docs/renee/Renee/Render/ClassMethods.html +382 -0
  176. data/site/public/docs/renee/Renee/Render/TemplateNotFound.html +126 -0
  177. data/site/public/docs/renee/Renee/Session.html +177 -0
  178. data/site/public/docs/renee/Renee/Session/ClassMethods.html +470 -0
  179. data/site/public/docs/renee/Renee/URLGeneration.html +142 -0
  180. data/site/public/docs/renee/Renee/URLGeneration/ClassMethods.html +593 -0
  181. data/site/public/docs/renee/Renee/Util.html +163 -0
  182. data/site/public/docs/renee/_index.html +336 -0
  183. data/site/public/docs/renee/class_list.html +47 -0
  184. data/site/public/docs/renee/css/common.css +1 -0
  185. data/site/public/docs/renee/css/full_list.css +55 -0
  186. data/site/public/docs/renee/css/style.css +322 -0
  187. data/site/public/docs/renee/file.README.html +212 -0
  188. data/site/public/docs/renee/file_list.html +49 -0
  189. data/site/public/docs/renee/frames.html +13 -0
  190. data/site/public/docs/renee/index.html +212 -0
  191. data/site/public/docs/renee/js/app.js +205 -0
  192. data/site/public/docs/renee/js/full_list.js +167 -0
  193. data/site/public/docs/renee/js/jquery.js +16 -0
  194. data/site/public/docs/renee/method_list.html +758 -0
  195. data/site/public/docs/renee/top-level-namespace.html +202 -0
  196. data/site/public/img/favicon.ico +0 -0
  197. data/site/public/img/reneeclean.png +0 -0
  198. data/site/public/img/russiangithub.png +0 -0
  199. data/site/public/img/stoneposter.png +0 -0
  200. data/site/public/img/vospit.jpeg +0 -0
  201. data/site/views/chaining.md +32 -0
  202. data/site/views/index.md +219 -0
  203. data/site/views/layouts/app.haml +16 -0
  204. data/site/views/rack-integration.md +51 -0
  205. data/site/views/responding.md +103 -0
  206. data/site/views/route-generation.md +82 -0
  207. data/site/views/routing.md +261 -0
  208. data/site/views/settings.md +19 -0
  209. data/site/views/team-renee.md +13 -0
  210. data/site/views/tutorial.md +57 -0
  211. data/site/views/variable-types.md +57 -0
  212. data/test.watchr +61 -0
  213. data/test/renee-core/chaining_test.rb +33 -0
  214. data/test/renee-core/env_accessors_test.rb +43 -0
  215. data/test/renee-core/include_test.rb +14 -0
  216. data/test/renee-core/request_context_test.rb +70 -0
  217. data/test/renee-core/responding_test.rb +128 -0
  218. data/test/renee-core/routing_test.rb +443 -0
  219. data/test/renee-core/test_helper.rb +4 -0
  220. data/test/renee-core/variable_type_test.rb +57 -0
  221. data/test/renee-render/render_test.rb +162 -0
  222. data/test/renee-render/test_helper.rb +9 -0
  223. data/test/renee-session/session_test.rb +31 -0
  224. data/test/renee-session/test_helper.rb +9 -0
  225. data/test/renee-url-generation/test_helper.rb +10 -0
  226. data/test/renee-url-generation/url_generation_test.rb +63 -0
  227. data/test/{blog_test.rb → renee/blog_test.rb} +10 -5
  228. data/test/renee/test_helper.rb +56 -0
  229. data/test/test_helper.rb +23 -10
  230. metadata +333 -156
  231. data/.yardopts +0 -6
@@ -0,0 +1,319 @@
1
+ module Renee
2
+ class Core
3
+ # Collection of useful methods for routing within a {Renee::Core} app.
4
+ module Routing
5
+ include Chaining
6
+
7
+ # Allow continued routing if a routing block fails to match
8
+ #
9
+ # @param [Boolean] val
10
+ # indicate if continued routing should be allowed.
11
+ #
12
+ # @api public
13
+ def continue_routing
14
+ if block_given?
15
+ original_env = @env.dup
16
+ begin
17
+ yield
18
+ rescue NotMatchedError
19
+ @env = original_env
20
+ end
21
+ else
22
+ create_chain_proxy(:continue_routing)
23
+ end
24
+ end
25
+ chainable :continue_routing
26
+
27
+ # Match a path to respond to.
28
+ #
29
+ # @param [String] p
30
+ # path to match.
31
+ # @param [Proc] blk
32
+ # block to yield
33
+ #
34
+ # @example
35
+ # path('/') { ... } #=> '/'
36
+ # path('test') { ... } #=> '/test'
37
+ #
38
+ # path 'foo' do
39
+ # path('bar') { ... } #=> '/foo/bar'
40
+ # end
41
+ #
42
+ # @api public
43
+ def path(p, &blk)
44
+ if blk
45
+ p = p[1, p.size] if p[0] == ?/
46
+ extension_part = detected_extension ? "|\\.#{Regexp.quote(detected_extension)}" : ""
47
+ part(/^\/#{Regexp.quote(p)}(?=\/|$#{extension_part})/, &blk)
48
+ else
49
+ create_chain_proxy(:path, p)
50
+ end
51
+ end
52
+ chainable :path
53
+
54
+ # Like #path, but doesn't look for leading slashes.
55
+ def part(p)
56
+ if block_given?
57
+ p = /^\/?#{Regexp.quote(p)}/ if p.is_a?(String)
58
+ if match = env['PATH_INFO'][p]
59
+ with_path_part(match) { yield }
60
+ end
61
+ else
62
+ create_chain_proxy(:part, p)
63
+ end
64
+ end
65
+
66
+ # Match parts off the path as variables. The parts matcher can conform to either a regular expression, or be an Integer, or
67
+ # simply a String.
68
+ # @param[Object] type the type of object to match for. If you supply Integer, this will only match integers in addition to casting your variable for you.
69
+ # @param[Object] default the default value to use if your param cannot be successfully matched.
70
+ #
71
+ # @example
72
+ # path '/' do
73
+ # variable { |id| halt [200, {}, id] }
74
+ # end
75
+ # GET /hey #=> [200, {}, 'hey']
76
+ #
77
+ # @example
78
+ # path '/' do
79
+ # variable(:integer) { |id| halt [200, {}, "This is a numeric id: #{id}"] }
80
+ # end
81
+ # GET /123 #=> [200, {}, 'This is a numeric id: 123']
82
+ #
83
+ # @example
84
+ # path '/test' do
85
+ # variable { |foo, bar| halt [200, {}, "#{foo}-#{bar}"] }
86
+ # end
87
+ # GET /test/hey/there #=> [200, {}, 'hey-there']
88
+ #
89
+ # @api public
90
+ def variable(type = nil, &blk)
91
+ blk ? complex_variable(type, '/', 1, &blk) : create_chain_proxy(:variable, type)
92
+ end
93
+ alias_method :var, :variable
94
+ chainable :variable, :var
95
+
96
+ def optional_variable(type = nil, &blk)
97
+ blk ? complex_variable(type, '/', 0..1) { |vars| blk[vars.first] } : create_chain_proxy(:variable, type)
98
+ end
99
+ alias_method :optional, :optional_variable
100
+ chainable :optional, :optional_variable
101
+
102
+ # Same as variable except you can match multiple variables with the same type.
103
+ # @param [Range, Integer] count The number of parameters to capture.
104
+ # @param [Symbol] type The type to use for match.
105
+ def multi_variable(count, type = nil, &blk)
106
+ blk ? complex_variable(type, '/', count, &blk) : create_chain_proxy(:multi_variable, count, type)
107
+ end
108
+ alias_method :multi_var, :multi_variable
109
+ alias_method :mvar, :multi_variable
110
+ chainable :multi_variable, :multi_var, :mvar
111
+
112
+ # Same as variable except it matches indefinitely.
113
+ # @param [Symbol] type The type to use for match.
114
+ def repeating_variable(type = nil, &blk)
115
+ blk ? complex_variable(type, '/', nil, &blk) : create_chain_proxy(:repeating_variable, type)
116
+ end
117
+ alias_method :glob, :repeating_variable
118
+ chainable :repeating_variable, :glob
119
+
120
+ # Match parts off the path as variables without a leading slash.
121
+ # @see #variable
122
+ # @api public
123
+ def partial_variable(type = nil, &blk)
124
+ blk ? complex_variable(type, nil, 1, &blk) : create_chain_proxy(:partial_variable, type)
125
+ end
126
+ alias_method :part_var, :partial_variable
127
+ chainable :partial_variable, :part_var
128
+
129
+ # Returns the matched extension. If no extension is present, returns `nil`.
130
+ #
131
+ # @example
132
+ # halt [200, {}, path] if extension == 'html'
133
+ #
134
+ # @api public
135
+ def extension
136
+ detected_extension
137
+ end
138
+ alias_method :ext, :extension
139
+
140
+ # Match no extension.
141
+ #
142
+ # @example
143
+ # no_extension { |path| halt [200, {}, path] }
144
+ #
145
+ # @api public
146
+ def no_extension(&blk)
147
+ blk.call unless detected_extension
148
+ end
149
+
150
+ # Match any remaining path.
151
+ #
152
+ # @example
153
+ # remainder { |path| halt [200, {}, path] }
154
+ #
155
+ # @api public
156
+ def remainder(&blk)
157
+ blk ? with_path_part(env['PATH_INFO']) { |var| blk.call(var) } : create_chain_proxy(:remainder)
158
+ end
159
+ alias_method :catchall, :remainder
160
+ chainable :remainder, :catchall
161
+
162
+ # Respond to a GET request and yield the block.
163
+ #
164
+ # @example
165
+ # get { halt [200, {}, "hello world"] }
166
+ #
167
+ # @api public
168
+ def get(&blk)
169
+ blk ? request_method('GET', &blk) : create_chain_proxy(:get)
170
+ end
171
+ chainable :get
172
+
173
+ # Respond to a POST request and yield the block.
174
+ #
175
+ # @example
176
+ # post { halt [200, {}, "hello world"] }
177
+ #
178
+ # @api public
179
+ def post(&blk)
180
+ blk ? request_method('POST', &blk) : create_chain_proxy(:post)
181
+ end
182
+ chainable :post
183
+
184
+ # Respond to a PUT request and yield the block.
185
+ #
186
+ # @example
187
+ # put { halt [200, {}, "hello world"] }
188
+ #
189
+ # @api public
190
+ def put(&blk)
191
+ blk ? request_method('PUT', &blk) : create_chain_proxy(:put)
192
+ end
193
+ chainable :put
194
+
195
+ # Respond to a DELETE request and yield the block.
196
+ #
197
+ # @example
198
+ # delete { halt [200, {}, "hello world"] }
199
+ #
200
+ # @api public
201
+ def delete(&blk)
202
+ blk ? request_method('DELETE', &blk) : create_chain_proxy(:delete)
203
+ end
204
+ chainable :delete
205
+
206
+ # Match only when the path is either '' or '/'.
207
+ #
208
+ # @example
209
+ # complete { halt [200, {}, "hello world"] }
210
+ #
211
+ # @api public
212
+ def complete(&blk)
213
+ if blk
214
+ with_path_part(env['PATH_INFO']) { blk.call } if complete?
215
+ else
216
+ create_chain_proxy(:complete)
217
+ end
218
+ end
219
+ chainable :complete
220
+
221
+ # Test if the path has been consumed
222
+ #
223
+ # @example
224
+ # if complete?
225
+ # halt "Hey, the path is done"
226
+ # end
227
+ #
228
+ # @api public
229
+ def complete?
230
+ (detected_extension and env['PATH_INFO'] =~ /^\/?(\.#{Regexp.quote(detected_extension)}\/?)?$/) || (detected_extension.nil? and env['PATH_INFO'] =~ /^\/?$/)
231
+ end
232
+
233
+ # Match only when the path is ''.
234
+ #
235
+ # @example
236
+ # empty { halt [200, {}, "hello world"] }
237
+ #
238
+ # @api public
239
+ def empty(&blk)
240
+ if blk
241
+ if env['PATH_INFO'] == ''
242
+ with_path_part(env['PATH_INFO']) { blk.call }
243
+ end
244
+ else
245
+ create_chain_proxy(:empty)
246
+ end
247
+ end
248
+ chainable :empty
249
+
250
+ private
251
+ def complex_variable(type, prefix, count)
252
+ matcher = variable_matcher_for_type(type)
253
+ path = env['PATH_INFO'].dup
254
+ vals = []
255
+ var_index = 0
256
+ variable_matching_loop(count) do
257
+ path.start_with?(prefix) ? path.slice!(0, prefix.size) : break if prefix
258
+ if match = matcher[path]
259
+ path.slice!(0, match.first.size)
260
+ vals << match.last
261
+ end
262
+ end
263
+ return unless count.nil? || count === vals.size
264
+ with_path_part(env['PATH_INFO'][0, env['PATH_INFO'].size - path.size]) do
265
+ if count == 1
266
+ yield(vals.first)
267
+ else
268
+ yield(vals)
269
+ end
270
+ end
271
+ end
272
+
273
+ def variable_matching_loop(count)
274
+ case count
275
+ when Range then count.max.times { break unless yield }
276
+ when nil then loop { break unless yield }
277
+ else count.times { break unless yield }
278
+ end
279
+ end
280
+
281
+ def variable_matcher_for_type(type)
282
+ if self.class.variable_types.key?(type)
283
+ self.class.variable_types[type]
284
+ else
285
+ regexp = case type
286
+ when nil, String
287
+ detected_extension ?
288
+ /(([^\/](?!#{Regexp.quote(detected_extension)}$))+)(?=$|\/|\.#{Regexp.quote(detected_extension)})/ :
289
+ /([^\/]+)(?=$|\/)/
290
+ when Regexp
291
+ type
292
+ else
293
+ raise "Unexpected variable type #{type.inspect}"
294
+ end
295
+ proc do |path|
296
+ if match = /^#{regexp.to_s}/.match(path)
297
+ [match[0]]
298
+ end
299
+ end
300
+ end
301
+ end
302
+
303
+ def with_path_part(part)
304
+ script_part = env['PATH_INFO'][0, part.size]
305
+ env['PATH_INFO'] = env['PATH_INFO'].slice(part.size, env['PATH_INFO'].size)
306
+ env['SCRIPT_NAME'] += script_part
307
+ yield script_part
308
+ raise NotMatchedError
309
+ end
310
+
311
+ def request_method(method)
312
+ if env['REQUEST_METHOD'] == method && complete?
313
+ yield
314
+ raise NotMatchedError
315
+ end
316
+ end
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,18 @@
1
+ module Renee
2
+ class Core
3
+ # Module used for transforming arbitrary values using the registerd variable types.
4
+ # @see #register_variable_name.
5
+ #
6
+ module Transform
7
+ # Transforms a value according to the rules specified by #register_variable_name.
8
+ # @param [Symbol] name The name of the variable type.
9
+ # @param [String] value The value to transform.
10
+ # @return The transformed value or nil.
11
+ def transform(type, value)
12
+ if self.class.variable_types.key?(type) and m = self.class.variable_types[type][value]
13
+ m.first == value ? m.last : nil
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,221 @@
1
+ require 'tilt'
2
+ require 'callsite'
3
+
4
+ require 'renee/version'
5
+
6
+ # Top-level Renee constant
7
+ module Renee
8
+ # This module is responsible for handling the rendering of templates
9
+ # using Tilt supporting all included template engines.
10
+ module Render
11
+ # Current version of Renee::Render
12
+ VERSION = Renee::VERSION
13
+
14
+ def self.included(o)
15
+ o.extend(ClassMethods)
16
+ end
17
+
18
+ # Class-methods included by this module.
19
+ module ClassMethods
20
+ # Gets or sets the views_path for an application.
21
+ #
22
+ # @param [String] path The path to the view files.
23
+ #
24
+ # @example
25
+ # views_path("./views") => nil
26
+ # views_path => "./views"
27
+ #
28
+ # @api public
29
+ def views_path(path = nil)
30
+ path ? @views_path = path : @views_path
31
+ end
32
+
33
+ # Gets or sets the default encoding used.
34
+ #
35
+ # @param [String] encoding The encoding to use. e.g. "utf-8"
36
+ def default_encoding(encoding = nil)
37
+ encoding ? @encoding = encoding : @encoding
38
+ end
39
+
40
+ # Gets or sets the default layout used.
41
+ #
42
+ # @param [String] layout The layout to use. e.g. "application.haml"
43
+ def default_layout(*args)
44
+ case args.size
45
+ when 0 then @layout
46
+ when 1 then @layout = args.first
47
+ else raise
48
+ end
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Exception responsible for when an expected template does not exist.
54
+ #
55
+ class TemplateNotFound < RuntimeError; end
56
+
57
+ # Same as render but automatically halts.
58
+ # @param (see #render)
59
+ # @return (see #render)
60
+ # @see #render
61
+ def render!(file, engine = nil, options = nil, &blk)
62
+ halt render(file, engine, options, &blk)
63
+ end
64
+
65
+ # Same as inline but automatically halts.
66
+ # @param (see #inline)
67
+ # @return (see #inline)
68
+ # @see #inline
69
+ def inline!(data, engine, options = {}, &blk)
70
+ options[:_caller] = Callsite.parse(caller.first)
71
+ halt inline(data, engine, options, &blk)
72
+ end
73
+
74
+ ##
75
+ # Renders a file given the engine and the content.
76
+ #
77
+ # @param [String] file The path to the file to render
78
+ # @param [Symbol] engine The name of the engine to use to render the content. If this isn't specified
79
+ # it will be detected based on the extension of the file.
80
+ # @param [Hash] options The options to pass in for rendering.
81
+ #
82
+ # @return [String] The result of rendering the data with specified engine.
83
+ #
84
+ # @example
85
+ # render "index", :haml # => "<p>test</p>"
86
+ # render "index" # => "<p>test</p>"
87
+ #
88
+ # @api public
89
+ #
90
+ def render(file, engine = nil, options = nil, &block)
91
+ options, engine = engine, nil if engine.is_a?(Hash)
92
+ render_setup(engine, options, block) do |view_options, views|
93
+ template_cache.fetch(engine, file, view_options) do
94
+ file_path, found_engine = find_template(views, file, engine)
95
+ template = Tilt[found_engine]
96
+ raise TemplateNotFound, "Template engine not found: #{found_engine.inspect}" unless template
97
+ raise TemplateNotFound, "Template #{file.inspect} (with engine #{engine.inspect}) not found in #{views.inspect}!" unless file_path
98
+ # TODO suppress errors for layouts?
99
+ template.new(file_path, 1, view_options)
100
+ end
101
+ end
102
+ end # render
103
+
104
+ ##
105
+ # Renders a string given the engine and the content.
106
+ #
107
+ # @param [String] data The string data to render.
108
+ # @param [Symbol] engine The name of the engine to use to render the content. If this isn't specified
109
+ # it will be detected based on the extension of the file.
110
+ # @param [Hash] options The options to pass in for rendering.
111
+ #
112
+ # @return [String] The result of rendering the data with specified engine.
113
+ #
114
+ # @example
115
+ # inline "%p test", :haml # => "<p>test</p>"
116
+ #
117
+ # @api public
118
+ #
119
+ def inline(data, engine, options = nil, &block)
120
+ options, engine = engine, nil if engine.is_a?(Hash)
121
+ call_data = options && options.delete(:_caller) || Callsite.parse(caller.first)
122
+ render_setup(engine, options, block) do |view_options, views|
123
+ body = data.is_a?(Proc) ? data : Proc.new { data }
124
+ template = Tilt[engine]
125
+ raise "Template engine not found: #{engine}" if template.nil?
126
+ template.new(call_data.filename, call_data.line, view_options, &body)
127
+ end
128
+ end
129
+
130
+ ##
131
+ # Render a partials with collections support
132
+ #
133
+ # @return [String] The html generated from this partial.
134
+ #
135
+ # @example
136
+ # partial 'photo/item', :object => @photo
137
+ # partial 'photo/item', :collection => @photos
138
+ # partial 'photo/item', :locals => { :foo => :bar }
139
+ #
140
+ def partial(template, options={})
141
+ options = { :locals => {}, :layout => false }.merge(options)
142
+ path = template.to_s.split(File::SEPARATOR)
143
+ object_name = path[-1].to_sym
144
+ path[-1] = "_#{path[-1]}"
145
+ template_path = File.join(path).to_sym
146
+ raise 'Partial collection specified but is nil' if options.has_key?(:collection) && options[:collection].nil?
147
+ if collection = options.delete(:collection)
148
+ options.delete(:object)
149
+ counter = 0
150
+ collection.map { |member|
151
+ counter += 1
152
+ options[:locals].merge!(object_name => member, "#{object_name}_counter".to_sym => counter)
153
+ render(template_path, options.dup)
154
+ }.join("\n")
155
+ else
156
+ if member = options.delete(:object)
157
+ options[:locals].merge!(object_name => member)
158
+ end
159
+ render(template_path, options.dup)
160
+ end
161
+ end
162
+
163
+ private
164
+
165
+
166
+ def render_setup(engine, options, block)
167
+ options ||= {}
168
+ options[:outvar] ||= '@_out_buf'
169
+ options[:default_encoding] ||= self.class.default_encoding || options[:encoding] || "utf-8"
170
+
171
+ locals = options.delete(:locals) || {}
172
+ views = options.delete(:views) || self.class.views_path || "./views"
173
+ layout = options.key?(:layout) ? options[:layout] : self.class.default_layout
174
+ layout_engine = options.delete(:layout_engine)
175
+ # TODO suppress template errors for layouts?
176
+ # TODO allow content_type to be set with an option to render?
177
+ scope = options.delete(:scope) || self
178
+
179
+ # TODO default layout file convention?
180
+ template = yield(options, views)
181
+ output = template.render(scope, locals, &block)
182
+
183
+ if layout # render layout
184
+ # TODO handle when layout is missing better!
185
+ options = options.merge(:views => views, :layout => false, :scope => scope)
186
+ render(layout, layout_engine, options.merge(:locals => locals)) { output }
187
+ else
188
+ output
189
+ end
190
+ end
191
+
192
+ ##
193
+ # Searches view paths for template based on data and engine with rendering options.
194
+ # Supports finding a template without an engine.
195
+ #
196
+ # @param [String] views The view paths
197
+ # @param [String] name The name of the template
198
+ # @param [Symbol] engine The engine to use for rendering.
199
+ #
200
+ # @return [<String, Symbol>] An array of the file path and the engine.
201
+ #
202
+ # @example
203
+ # find_template("./views", "index", :erb) => ["path/to/index.erb", :erb]
204
+ # find_template("./views", "foo") => ["path/to/index.haml", :haml]
205
+ #
206
+ # @api private
207
+ #
208
+ def find_template(views, name, engine=nil)
209
+ lookup_ext = (engine || File.extname(name.to_s)[1..-1] || "*").to_s
210
+ base_name = name.to_s.chomp(".#{lookup_ext}")
211
+ file_path = Dir[File.expand_path("#{base_name}.#{lookup_ext}", views)].first
212
+ engine ||= File.extname(file_path)[1..-1].to_sym if file_path
213
+ [file_path, engine]
214
+ end # find_template
215
+
216
+ # Maintain Tilt::Cache of the templates.
217
+ def template_cache
218
+ @template_cache ||= Tilt::Cache.new
219
+ end
220
+ end
221
+ end