renee 0.3.11 → 0.4.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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