trellis 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. data/History.txt +7 -0
  2. data/Manifest.txt +6 -181
  3. data/examples/crud_components/source/crud_components.rb +0 -1
  4. data/examples/examples.txt +32 -30
  5. data/examples/flickr/source/flickr.rb +0 -4
  6. data/examples/hangman/source/hangman.rb +1 -2
  7. data/examples/hilo/html/guess.xhtml +2 -2
  8. data/examples/hilo/source/hilo.rb +6 -6
  9. data/examples/routing/source/routing.rb +50 -0
  10. data/examples/stateful_counters/source/stateful_counters.rb +9 -4
  11. data/lib/trellis/{core_components.rb → component_library/core_components.rb} +48 -398
  12. data/lib/trellis/component_library/grid.rb +311 -0
  13. data/lib/trellis/{jquery.rb → component_library/jquery.rb} +0 -0
  14. data/lib/trellis/component_library/object_editor.rb +110 -0
  15. data/lib/trellis/trellis.rb +162 -52
  16. data/lib/trellis/utils.rb +9 -3
  17. data/lib/trellis/version.rb +1 -1
  18. data/tasks/rspec.rake +2 -1
  19. data/test/application_spec.rb +55 -30
  20. data/test/component_spec.rb +95 -81
  21. data/test/core_extensions_spec.rb +34 -2
  22. data/test/default_router_spec.rb +91 -0
  23. data/test/fixtures/application_spec_applications.rb +91 -3
  24. data/test/page_spec.rb +18 -8
  25. data/test/router_spec.rb +49 -49
  26. metadata +8 -185
  27. data/.git/COMMIT_EDITMSG +0 -1
  28. data/.git/HEAD +0 -1
  29. data/.git/config +0 -9
  30. data/.git/description +0 -1
  31. data/.git/hooks/applypatch-msg.sample +0 -15
  32. data/.git/hooks/commit-msg.sample +0 -24
  33. data/.git/hooks/post-commit.sample +0 -8
  34. data/.git/hooks/post-receive.sample +0 -15
  35. data/.git/hooks/post-update.sample +0 -8
  36. data/.git/hooks/pre-applypatch.sample +0 -14
  37. data/.git/hooks/pre-commit.sample +0 -18
  38. data/.git/hooks/pre-rebase.sample +0 -169
  39. data/.git/hooks/prepare-commit-msg.sample +0 -36
  40. data/.git/hooks/update.sample +0 -107
  41. data/.git/index +0 -0
  42. data/.git/info/exclude +0 -6
  43. data/.git/logs/HEAD +0 -1
  44. data/.git/logs/refs/heads/master +0 -1
  45. data/.git/logs/refs/remotes/origin/master +0 -1
  46. data/.git/objects/01/2368d4dd3162ed570e6c1423ecb07a6a35f8c0 +0 -0
  47. data/.git/objects/02/238c88c522b88e71529495460cc5c2c8abe110 +0 -0
  48. data/.git/objects/07/03d3b123189f1da72d3889d9340e681841c96d +0 -0
  49. data/.git/objects/0b/3935fdbf3ce8d37e6cb2a6ec94edaccb1c58dc +0 -0
  50. data/.git/objects/0f/ecd1ad35f280c874ce070b7a97a4c1f86ace54 +0 -0
  51. data/.git/objects/12/61911b063b7e6883c5e9db7ab4396183f6f2b5 +0 -0
  52. data/.git/objects/13/0b7e2d214ec198d07aeed5b462597670ce85ad +0 -0
  53. data/.git/objects/13/214d40f02b63f1233fd7881b1df9bb75d9c0a7 +0 -0
  54. data/.git/objects/14/7522bdc96af0b578935d81c1a7347b40573940 +0 -0
  55. data/.git/objects/16/a7fde706fe92b3548254d29722f5ca311ce63b +0 -2
  56. data/.git/objects/16/fe580663cc29d141bf416dff5bf21bf4ea42a3 +0 -0
  57. data/.git/objects/17/2d55806918bafb4e701515edb27147a4ef6498 +0 -0
  58. data/.git/objects/17/686ef1882ed6c3c07b781fab18739b1c5e48bd +0 -0
  59. data/.git/objects/17/824053c41a2cb523303e20cac42f66f9ca2134 +0 -0
  60. data/.git/objects/18/68448c5f88bc73b2fef6b8f3aa75b663013a3a +0 -1
  61. data/.git/objects/1a/6d9051dc4c7db10bdcf5e17c5cb717b072d30a +0 -0
  62. data/.git/objects/1a/8bdb445fb5668dcd1309cb9de5dedf846c822f +0 -0
  63. data/.git/objects/1b/6fa67b70358fdb82eabcd7ef3bc46d518d0426 +0 -0
  64. data/.git/objects/1c/f6510ed08a274fb3292d40557cebb3e7090767 +0 -0
  65. data/.git/objects/1d/e661ce11d5335e5dd7968ef5698e2eb164eb52 +0 -0
  66. data/.git/objects/1e/301ee672d6040e734af6d0a5491185129d7b67 +0 -0
  67. data/.git/objects/21/2201d46200f13d23d8199bd4a7ecc9165205b3 +0 -0
  68. data/.git/objects/22/f687370babdf38d224d2a9ff039707e8d0aac1 +0 -0
  69. data/.git/objects/24/202d2de95dc31f5f1802e629a16ad45b723267 +0 -0
  70. data/.git/objects/25/4c5b6b8d211d8b9f07310fd342fd7b078a81af +0 -0
  71. data/.git/objects/25/5832653fde9e9748855347fe7bce013aa0a6c7 +0 -0
  72. data/.git/objects/25/806945c89b4b5adb8c8676c308e4653361109c +0 -5
  73. data/.git/objects/26/cb8deaca28bf275769cbb71b88e53a44adb9ca +0 -0
  74. data/.git/objects/2e/1c13fb5f154f89cccd4c645540a2ec8ee0f57a +0 -0
  75. data/.git/objects/2f/437425b2c085bb529379e409d6b4bc7f9f9fba +0 -0
  76. data/.git/objects/2f/7cf4a25937b71f3de52e70c7a47cb2f7da07ea +0 -0
  77. data/.git/objects/31/31bdbbac6e6e7e24fd6ea84332461f67e6e5b6 +0 -0
  78. data/.git/objects/32/02e02de478263912b729283eabc7cdab8a8037 +0 -0
  79. data/.git/objects/32/839ed9baa8c800d9033655d1ee995499825fb5 +0 -0
  80. data/.git/objects/34/2e479c61796125b0e025b483401ec12f3c5824 +0 -2
  81. data/.git/objects/36/7acb017aebab3f627a5208c25885c21fa7fdbb +0 -0
  82. data/.git/objects/38/12910d2e2a8e6eeb79a399d20751675d3fbe77 +0 -0
  83. data/.git/objects/38/3fe1e1a07537db6e0f39c6005b7b4e40fa5ccd +0 -0
  84. data/.git/objects/39/5ef328bc94df9191f4d80a77503a5be9f1f673 +0 -0
  85. data/.git/objects/39/dc76dc096fdf737e9f4f7062b78cbf4ab9628f +0 -0
  86. data/.git/objects/3b/09eb196140b471556ed4c3987c0bc739733617 +0 -0
  87. data/.git/objects/3b/9f97f1bf55834832a92bab1ab900918dc49570 +0 -0
  88. data/.git/objects/40/5ffd3925b39dd905638ec24640dd8efa27cc1b +0 -0
  89. data/.git/objects/42/4a5f37c6fe3a7cac54b0f85688c1cce7da9cdf +0 -0
  90. data/.git/objects/44/9e23363d4e20da6a67be7ab78fc610b4e9852a +0 -0
  91. data/.git/objects/48/90bb5d241cecaa30b28148c920b833056c7508 +0 -0
  92. data/.git/objects/4c/558c43676410a2c0a7d5c7b94fca18923a9af1 +0 -0
  93. data/.git/objects/4f/bf572175dd5245104679ff8f676f6a34466c29 +0 -0
  94. data/.git/objects/53/8583edf00c9f656b32149a0e69e030371737c5 +0 -0
  95. data/.git/objects/5b/8a2c4bf7bb56ae199414096dbda21fd4b8cd63 +0 -0
  96. data/.git/objects/5c/9fe8ff998d56dc076657ef88ee9f784ec160f9 +0 -0
  97. data/.git/objects/5f/8441f9d3507587940f31af04dd9fd5911d2812 +0 -0
  98. data/.git/objects/61/4f2752550e63b793e31dcf98b5ea26c32eb0be +0 -0
  99. data/.git/objects/65/801b0fabdfd673861dbb2ac134d438ab2cc5e4 +0 -3
  100. data/.git/objects/66/2746dffaddbcd311f4ac070b5b9bddcd8894a7 +0 -0
  101. data/.git/objects/69/1ed3b65603a0d0dabfb66720e7163664be78f5 +0 -0
  102. data/.git/objects/69/3dc3b6037df8c1c8dfda916d649e7975481124 +0 -0
  103. data/.git/objects/69/67538427b3a371b33c01c72726ac7e67a04c39 +0 -0
  104. data/.git/objects/69/6cd6c3508e2adcc68829497884ff3d4b774dc2 +0 -0
  105. data/.git/objects/69/6ee93917040308ccd0bfc245d63c1cbeb23dae +0 -0
  106. data/.git/objects/6a/3ac317746a8a2cfc88f3580bbd2b03dc0d50b5 +0 -0
  107. data/.git/objects/6d/6ba435839a65b96cb3f7584a40dd3767a0990f +0 -0
  108. data/.git/objects/6f/32c190a651a82caeefeb55eb797fc5daf05426 +0 -0
  109. data/.git/objects/71/fecbce1bec36eaf4de655763d4bb7ceffc991d +0 -0
  110. data/.git/objects/72/01d78a9e90347fd2f7790f9e8665587791d853 +0 -0
  111. data/.git/objects/73/6c97d6a551363ba4a4956408ba228fcab7f91f +0 -0
  112. data/.git/objects/75/3264271e935e18f4eada49aa1818c6fb9e6abb +0 -0
  113. data/.git/objects/75/cc7d390b4a565495cbdc5a08d4479e7e8dbfeb +0 -0
  114. data/.git/objects/76/7c0c96f68b9ccec34ae538045a63bbe4f97d3e +0 -0
  115. data/.git/objects/7b/47fe9b1ab9f2692b9706054de706430089ecd4 +0 -0
  116. data/.git/objects/7c/73144ef6847d1bc2315aee0925c0e5cf5c09d1 +0 -0
  117. data/.git/objects/7d/9c177c155386606aa1024d5cb020a097eea64a +0 -0
  118. data/.git/objects/7d/e9a3296007c09ba9b7bc1802e1c5d9ad1d5dbb +0 -0
  119. data/.git/objects/7e/b73d408147c8d84881109189a9724292abcb8d +0 -0
  120. data/.git/objects/82/0e3bf5449a8f6b8eb637a653dcb2dc387b393c +0 -0
  121. data/.git/objects/83/a10ce96763cb30d0b81425e9372f89c52f4c9f +0 -0
  122. data/.git/objects/83/bdb801e78444d59225f83cf2f336f114cd23cf +0 -0
  123. data/.git/objects/85/24e583e822abc18b1ffdd0aa356c9c079b5548 +0 -0
  124. data/.git/objects/88/b46cc8de5237897d4ba5023d3efdc83fec4f7c +0 -0
  125. data/.git/objects/89/5eea66474f48a2cbf643ad0ef266fd26f9d2a3 +0 -0
  126. data/.git/objects/8a/539c5fc35d5b770d67241d2ccb6c6855df6a43 +0 -0
  127. data/.git/objects/8a/82cdbfb20cc58fdffe4e13b70b55977deb92c6 +0 -0
  128. data/.git/objects/8b/2b537442e4ac8719ffa71b3ae0c707133df06f +0 -0
  129. data/.git/objects/8c/2a45f0c81284a081f8d7980e9a54210a9dd52c +0 -0
  130. data/.git/objects/8e/10355b4ab312450031966107ef6a65b13833ee +0 -0
  131. data/.git/objects/90/f6c76767ae725f15fa6790dcb6afd1719fa0e2 +0 -0
  132. data/.git/objects/92/92b696aba9840c089241c68d4c6ca14be38a64 +0 -2
  133. data/.git/objects/93/e03faa6906892434fa9f2ad230c4ececc64055 +0 -0
  134. data/.git/objects/93/eca2e28d80fda45d327fdfc6c39ae690af41cd +0 -0
  135. data/.git/objects/95/92fe63bd5403654730cdec76cbf116ef83bbff +0 -0
  136. data/.git/objects/96/bc6dd471c1c188a2eb4308bcc3ca4e6c8faac8 +0 -0
  137. data/.git/objects/96/d855221a9ff4fba57d2b70680b57bd5115cfde +0 -0
  138. data/.git/objects/97/4ec92b2b9eb01a73fb57dc78d463cafdda69a7 +0 -0
  139. data/.git/objects/97/b7ec01ebe089832a0972d20b0a40d54456985c +0 -2
  140. data/.git/objects/97/fa6aaae4cfc7654feb39971e7aee5c41ed1679 +0 -1
  141. data/.git/objects/98/18351c6685a1df5d23686e6346d11d261cc6f0 +0 -0
  142. data/.git/objects/98/3d3b7e55cbbce2f2a52c6e6d830a5dca7b4d42 +0 -0
  143. data/.git/objects/a2/e551049a5388112c21e7f3a4a720bf720601fa +0 -0
  144. data/.git/objects/a3/7066bea4fae90d34761a3ebb3f8a348d94eba1 +0 -0
  145. data/.git/objects/a6/c99619145d1b9b75aa15ca04fbe4db0b2ee79b +0 -0
  146. data/.git/objects/aa/e2234d0a8c1485096c7a7de039eceb4e019e13 +0 -0
  147. data/.git/objects/b0/3b52f4269c9719e02ecd6caabd8aff5adf3aac +0 -0
  148. data/.git/objects/b1/50dbbd89f825302a9e82d8fdd66f238b8e6fc4 +0 -0
  149. data/.git/objects/b4/545fbe393aaa18f1c48de4d9f68f64ebf09ddc +0 -0
  150. data/.git/objects/b6/8691404e088692929d40b988b993f815d76756 +0 -0
  151. data/.git/objects/b7/daa3d844ef5f5cd917084e48edd82e67f8f476 +0 -0
  152. data/.git/objects/bf/4232281dba5a0f17e8b5c16b9cbd0f52274fa8 +0 -0
  153. data/.git/objects/bf/9b84d10f19188beffe5ee99e6f5af035ba77d3 +0 -2
  154. data/.git/objects/c0/06e664b306145de804f74ab27550d62a92f58a +0 -1
  155. data/.git/objects/c2/2806b16775f66453012d085d21fb8698521ea9 +0 -0
  156. data/.git/objects/c2/7f6559350f7adb19d43742b55b2f91d07b6550 +0 -0
  157. data/.git/objects/c3/833df21ed674a77374fd40ed931f900134d38b +0 -0
  158. data/.git/objects/c3/b9da021eb76abad0b17ad6c4be75595e41a7e6 +0 -0
  159. data/.git/objects/c5/ca6b7e3561ea88aba1712aabb984d1167787f1 +0 -0
  160. data/.git/objects/c6/39f65e1f43c75b7f57c5ab28623d295dfb2afd +0 -0
  161. data/.git/objects/c8/6f05fa5539f8e69e94850d1cd917919ff581be +0 -0
  162. data/.git/objects/cb/5e499c8dcc6f2901014a08e74f04b7a4bd6596 +0 -0
  163. data/.git/objects/cc/1289d25eeee6ba31b4e406372cb9abf969fc6f +0 -0
  164. data/.git/objects/cc/784e1b49111c42fa7eae10ad0e788186e672d1 +0 -0
  165. data/.git/objects/cc/bcae254dad515d7ef01a461980544e4483c41e +0 -0
  166. data/.git/objects/ce/bfadad3a7c17148998e04275e6d0d1cf1b2e1e +0 -0
  167. data/.git/objects/ce/f3335522abdc47a2c94be70455dcfaa9a51a27 +0 -0
  168. data/.git/objects/cf/6add7ea568d3d90d6a1f8afb0898b0119b14ff +0 -0
  169. data/.git/objects/d0/719abd196033fec0e4cbe2af68a26ca435389c +0 -0
  170. data/.git/objects/d1/ce63ff0eaec4dba419e5547f99195dfb9b5b20 +0 -0
  171. data/.git/objects/d2/c3feb3c74b892cca0049c0aced6ce786a784c8 +0 -0
  172. data/.git/objects/d7/8a91da13413419da71b539a3c6c7df4d8a2458 +0 -0
  173. data/.git/objects/d8/c7bed2b1357d54b6935536a8a51a3c73d535b4 +0 -0
  174. data/.git/objects/da/c3a1277ccb4806011e33eb32c5609cc45c46f2 +0 -0
  175. data/.git/objects/dc/3f920f4b937d0e2ac512f0bb31d477624c46d5 +0 -0
  176. data/.git/objects/dc/6c389abf5bd0d7b9be2a6bb6af90738a410e59 +0 -0
  177. data/.git/objects/df/07c423309f39c4d5570b0fad65dff23af654ae +0 -0
  178. data/.git/objects/e2/93a5c9e2257b9f3fcb8fa5d5c8147a2f07ba1b +0 -0
  179. data/.git/objects/e4/8464df56bf487e96e21ea99487330266dae3c9 +0 -0
  180. data/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
  181. data/.git/objects/e8/0c13a1d1f62756fb9f1e4fb9f7512e5b8c5816 +0 -0
  182. data/.git/objects/e9/b8ade2125b2615ddc4c8043940c40ed36215c9 +0 -0
  183. data/.git/objects/ed/59a7599a81ef7ea7c230c3332345a3484febf8 +0 -0
  184. data/.git/objects/f0/d2a74226877e9a28af814d0d3727e4a3487439 +0 -0
  185. data/.git/objects/f0/f3ba7264f1d8e60eb93a907092af98fdc248e4 +0 -0
  186. data/.git/objects/f1/cdcc7ff2dc6298318523b2c4e425f515a58bf7 +0 -3
  187. data/.git/objects/f4/45c96f406f4d2f9839b3fe2fe8bc7f943ab469 +0 -0
  188. data/.git/objects/f7/eef7ce0a650d6f0c85dc4982f481da7bd44362 +0 -0
  189. data/.git/objects/f9/0abf96c5059ce1ba115f73bc9cf33e4663b3e1 +0 -0
  190. data/.git/objects/f9/cf7ae572eaebbd63f809f16a5ef14b9c679790 +0 -0
  191. data/.git/objects/fc/300df53fe2c9467210e2fd4c2f34e1c9c7c89a +0 -0
  192. data/.git/objects/fc/45546596ab8debc4c9f49a7abc7fc14a7bcc7f +0 -4
  193. data/.git/objects/fc/68d89167f69614eda17bd56dfccb4355d818e6 +0 -0
  194. data/.git/objects/fc/e4c02bb132e73b3cb957da182db7b75a47c242 +0 -0
  195. data/.git/objects/fe/be283da7f326344013f1fc847c12c8f29ab6ef +0 -0
  196. data/.git/refs/heads/master +0 -1
  197. data/.git/refs/remotes/origin/master +0 -1
  198. data/.gitignore +0 -5
  199. data/README.txt +0 -156
  200. data/examples/simplest/html/start.xhtml +0 -0
  201. data/website/index.html +0 -124
  202. data/website/index.txt +0 -119
  203. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  204. data/website/stylesheets/screen.css +0 -138
  205. data/website/template.html.erb +0 -48
@@ -46,12 +46,12 @@ module Trellis
46
46
  # pages and it must define a home page or entry point into the application
47
47
  class Application
48
48
  include Logging
49
+ include Rack::Utils
49
50
 
50
51
  # descendant application classes get a singleton class level instances for
51
52
  # holding homepage, dependent pages, static resource routing paths
52
53
  def self.inherited(child) #:nodoc:
53
- child.meta_attr_reader(:homepage)
54
- child.attr_array(:pages, :create_accessor => false)
54
+ child.class_attr_reader(:homepage)
55
55
  child.attr_array(:static_routes)
56
56
  child.meta_def(:logger) { Application.logger }
57
57
  super
@@ -61,13 +61,7 @@ module Trellis
61
61
  # the entry point is the URL pattern / where the application is mounted
62
62
  def self.home(sym)
63
63
  @homepage = sym
64
- @pages << sym
65
64
  end
66
-
67
- # class method that takes a list of page symbols
68
- def self.pages(*syms)
69
- @pages = @pages | syms
70
- end
71
65
 
72
66
  # define url paths for static resources
73
67
  def self.map_static(urls = [], root = File.expand_path("#{File.dirname($0)}/../html/"))
@@ -86,6 +80,7 @@ module Trellis
86
80
  end
87
81
 
88
82
  def configured_instance
83
+ # configure rack middleware
89
84
  application = Rack::ShowStatus.new(self)
90
85
  application = Rack::ShowExceptions.new(application)
91
86
  application = Rack::CommonLogger.new(application, Application.logger)
@@ -97,38 +92,51 @@ module Trellis
97
92
  end
98
93
  application
99
94
  end
95
+
96
+ # find the first page with a suitable router, if none is found use the default router
97
+ def find_router_for(request)
98
+ match = Page.subclasses.values.find { |page| page.router && page.router.matches?(request) }
99
+ match ? match.router : DefaultRouter.new(:application => self)
100
+ end
100
101
 
101
102
  # implements the rack specification
102
103
  def call(env)
103
104
  response = Rack::Response.new
104
105
  request = Rack::Request.new(env)
105
- session = env["rack.session"]
106
-
107
- route = DefaultRouter.new(self).route(request)
108
-
106
+
109
107
  Application.logger.debug "request received with url_root of #{request.script_name}"
108
+
109
+ session = env["rack.session"]
110
+
111
+ router = find_router_for(request)
112
+ route = router.route(request)
110
113
 
111
114
  page = route.destination.new if route.destination
112
115
  if page
113
116
  page.class.url_root = request.script_name
117
+ page.path = request.path_info.sub(/^\//, '')
114
118
  page.inject_dependent_pages
115
119
  page.call_if_provided(:before_load)
116
120
  page.load_page_session_information(session)
117
121
  page.call_if_provided(:after_load)
118
122
  page.params = request.params.keys_to_symbols
119
- page = page.process_event(route.event, route.value, route.source, session) if route.event
123
+ router.inject_parameters_into_page_instance(page, request)
124
+ result = route.event ? page.process_event(route.event, route.value, route.source, session) : page
120
125
 
121
126
  # prepare the http response
122
- if request.post? && page.kind_of?(Trellis::Page)
123
- # redirect after post
127
+ if (request.post? || route.event) && result.kind_of?(Trellis::Page)
128
+ # for action events of posts then use redirect after post pattern
129
+ # remove the events path and just return to the page
130
+ path = result.path ? result.path.gsub(/\/events\/.*/, '') : result.class.class_to_sym
124
131
  response.status = 302
125
- response.headers["Location"] = "#{request.script_name}/#{page.class.class_to_sym}"
132
+ response.headers["Location"] = "#{request.script_name}/#{path}"
126
133
  else
127
- response.body = page.kind_of?(Trellis::Page) ? page.render : page
134
+ # for render requests simply render the page
135
+ response.body = result.kind_of?(Trellis::Page) ? result.render : result
128
136
  response.status = 200
129
137
  end
130
138
  else
131
- # could find a page with default router, now what?
139
+ response.status = 404
132
140
  end
133
141
  response.finish
134
142
  end
@@ -140,7 +148,7 @@ module Trellis
140
148
  class Route
141
149
  attr_reader :destination, :event, :source, :value
142
150
 
143
- def initialize(destination, event, source, value)
151
+ def initialize(destination, event = nil, source = nil, value = nil)
144
152
  @destination, @event, @source, @value = destination, event, source, value
145
153
  end
146
154
  end
@@ -148,24 +156,111 @@ module Trellis
148
156
  # -- Router --
149
157
  # A Router returns a Route in response to an HTTP request
150
158
  class Router
151
- attr_reader :application
159
+ EVENT_REGEX = %r{^(?:.+)/events/(?:([^/\.]+)(?:\.([^/\.]+)?)?)(?:/(?:([^\.]+)?))?}
160
+
161
+ attr_reader :application, :pattern, :keys, :path, :page
152
162
 
153
- def initialize(application)
154
- @application = application
163
+ def initialize(options={})
164
+ @application = options[:application]
165
+ @path = options[:path]
166
+ @page = options[:page]
167
+ compile_path if @path
168
+ end
169
+
170
+ def route(request = nil)
171
+ # get the event information if any
172
+ value, source, event = request.path_info.match(EVENT_REGEX).to_a.reverse if request
173
+ Route.new(@page, event, source, value)
174
+ end
175
+
176
+ def matches?(request)
177
+ request.path_info.gsub(/\/events\/.*/, '').match(@pattern) != nil
178
+ end
179
+
180
+ def inject_parameters_into_page_instance(page, request)
181
+ # extract parameters and named parameters from request
182
+ if @pattern && @page && match = @pattern.match(request.path_info.gsub(/\/events\/.*/, ''))
183
+ values = match.captures.to_a
184
+ params =
185
+ if @keys.any?
186
+ @keys.zip(values).inject({}) do |hash,(k,v)|
187
+ if k == 'splat'
188
+ (hash[k] ||= []) << v
189
+ else
190
+ hash[k] = v
191
+ end
192
+ hash
193
+ end
194
+ elsif values.any?
195
+ {'captures' => values}
196
+ else
197
+ {}
198
+ end
199
+ params.each_pair { |name, value| page.instance_variable_set("@#{name}".to_sym, value) }
200
+ end
201
+ end
202
+
203
+ private
204
+
205
+ # borrowed (stolen) from Sinatra!
206
+ def compile_path
207
+ @keys = []
208
+ if @path.respond_to? :to_str
209
+ special_chars = %w{. + ( )}
210
+ pattern =
211
+ @path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
212
+ case match
213
+ when "*"
214
+ @keys << 'splat'
215
+ "(.*?)"
216
+ when *special_chars
217
+ Regexp.escape(match)
218
+ else
219
+ @keys << $2[1..-1]
220
+ "([^/?&#]+)"
221
+ end
222
+ end
223
+ @pattern = /^#{pattern}$/
224
+ elsif @path.respond_to?(:keys) && @path.respond_to?(:match)
225
+ @pattern = @path
226
+ @keys = @path.keys
227
+ elsif @path.respond_to? :match
228
+ @pattern = path
229
+ else
230
+ raise TypeError, @path
231
+ end
155
232
  end
156
233
  end
157
234
 
158
235
  # -- DefaultRouter --
159
- # The default routing scheme is in the form /page[.event_[source]][/value][?query_params]
236
+ # The default routing scheme is in the form /page[.event[_source]][/value][?query_params]
160
237
  class DefaultRouter < Router
161
- ROUTE_REGEX = %r{^(?:/)([^.]+)(?:.([^_/]+)(?:_([^/]+))?)?(?:/([\w]+)$)?}
162
-
163
- def route(request)
164
- value, source, event, destination = request.path_info.match(ROUTE_REGEX).to_a.reverse
238
+ ROUTE_REGEX = %r{^/([^/]+)(?:/(?:events/(?:([^/\.]+)(?:\.([^/\.]+)?)?)(?:/(?:([^\.]+)?))?)?)?}
239
+
240
+ def route(request)
241
+ value, source, event, destination = request.path_info.match(ROUTE_REGEX).to_a.reverse
165
242
  destination = @application.class.homepage unless destination
166
243
  page = Page.get_page(destination.to_sym)
167
-
168
- Route.new(page, event, source, value)
244
+
245
+ Route.new(page, event, source, value)
246
+ end
247
+
248
+ def matches?(request)
249
+ request.path_info.match(ROUTE_REGEX) != nil
250
+ end
251
+
252
+ def self.to_uri(options={})
253
+ url_root = options[:url_root]
254
+ page = options[:page]
255
+ event = options[:event]
256
+ source = options[:source]
257
+ value = options[:value]
258
+ destination = page.kind_of?(Trellis::Page) ? (page.path || page.class.class_to_sym) : page
259
+ url_root = page.kind_of?(Trellis::Page) && page.class.url_root ? "/#{page.class.url_root}" : '/' unless url_root
260
+ source = source ? ".#{source}" : ''
261
+ value = value ? "/#{value}" : ''
262
+ event_info = event ? "/events/#{event}#{source}#{value}" : ''
263
+ "#{url_root}#{destination}#{event_info}"
169
264
  end
170
265
  end
171
266
 
@@ -179,7 +274,7 @@ module Trellis
179
274
 
180
275
  @@subclasses = Hash.new
181
276
 
182
- attr_accessor :params, :logger
277
+ attr_accessor :params, :path, :logger
183
278
 
184
279
  def self.inherited(child) #:nodoc:
185
280
  @@subclasses[child.class_to_sym] = child
@@ -189,9 +284,10 @@ module Trellis
189
284
  child.attr_array(:components)
190
285
  child.attr_array(:stateful_components)
191
286
  child.attr_array(:persistents)
192
- child.meta_attr_accessor :url_root
193
- child.meta_attr_accessor :name
194
- child.meta_attr_accessor :layout
287
+ child.class_attr_accessor :url_root
288
+ child.class_attr_accessor :name
289
+ child.class_attr_accessor :router
290
+ child.class_attr_accessor :layout
195
291
  child.meta_def(:add_stateful_component) { |tid,clazz| @stateful_components << [tid,clazz] }
196
292
 
197
293
  locate_template child
@@ -232,15 +328,24 @@ module Trellis
232
328
  def self.pages(*syms)
233
329
  @pages = @pages | syms
234
330
  end
331
+
332
+ def self.route(path)
333
+ router = Router.new(:path => path, :page => self)
334
+ self.instance_variable_set(:@router, router)
335
+ end
235
336
 
236
337
  def self.persistent(*fields)
237
- meta_attr_reader fields
338
+ instance_attr_accessor fields
238
339
  @persistents = @persistents | fields
239
340
  end
240
341
 
241
342
  def self.get_page(sym)
242
343
  @@subclasses[sym]
243
344
  end
345
+
346
+ def self.subclasses
347
+ @@subclasses
348
+ end
244
349
 
245
350
  def initialize # TODO this is Ugly.... should no do it in initialize since it'll require super in child classes
246
351
  self.class.stateful_components.each do |id_component|
@@ -313,7 +418,7 @@ module Trellis
313
418
 
314
419
  def self.locate_template(clazz)
315
420
  begin
316
- if clazz.url_root.nil? || clazz.url_root.blank?
421
+ if clazz.url_root.nil? || clazz.url_root.empty?
317
422
  dir = "#{File.dirname($0)}/../html/"
318
423
  else
319
424
  dir = "#{File.dirname($0)}#{clazz.url_root}/html/".gsub("Rack: ", '')
@@ -439,6 +544,9 @@ module Trellis
439
544
  @context.globals.send(sym, value)
440
545
  end
441
546
  end
547
+
548
+ #TODO add public page methods to the context
549
+
442
550
 
443
551
  # add the page to the context too
444
552
  @context.globals.page = page
@@ -477,8 +585,8 @@ module Trellis
477
585
  # component registration
478
586
  @@components[child.class_to_sym] = child
479
587
 
480
- child.meta_attr_accessor(:body)
481
- child.meta_attr_accessor(:cname)
588
+ child.class_attr_accessor(:body)
589
+ child.class_attr_accessor(:cname)
482
590
  child.cname = child.underscore_class_name
483
591
  child.meta_def(:stateful?) { @stateful }
484
592
 
@@ -531,10 +639,6 @@ module Trellis
531
639
  send("#{sym}=", default_value) if default_value
532
640
  end
533
641
 
534
- def self.dom_contribution(&block) #TODO names don't match!
535
- @document_modifications << block
536
- end
537
-
538
642
  def self.add_style_links_to_page(page, attributes)
539
643
  style_links.each do |href|
540
644
  href = href.replace_ant_style_properties(attributes) if attributes
@@ -603,13 +707,17 @@ module Trellis
603
707
  end
604
708
  end
605
709
 
606
- def self.page_contribution(sym, contribution, options=nil)
607
- # add the contribution to the appropriate array of contributions
608
- # scripts, class_scripts, styles, class_styles, script_links, style_links
609
- scope = options[:scope] || :class if options
610
- receiver = sym.to_s.plural
611
- receiver = "class_#{receiver}" if scope == :class
612
- instance_variable_get("@#{receiver}").send(:<<, contribution)
710
+ def self.page_contribution(sym, contribution=nil, options=nil, &block)
711
+ unless (sym == :dom && block_given?)
712
+ # add the contribution to the appropriate array of contributions
713
+ # scripts, class_scripts, styles, class_styles, script_links, style_links
714
+ scope = options[:scope] || :class if options
715
+ receiver = sym.to_s.plural
716
+ receiver = "class_#{receiver}" if scope == :class
717
+ instance_variable_get("@#{receiver}").send(:<<, contribution)
718
+ else
719
+ @document_modifications << block
720
+ end
613
721
  end
614
722
 
615
723
  def self.get_component(sym)
@@ -646,7 +754,7 @@ module Trellis
646
754
  def save_component_session_information(page, instance_variable_name, session_data)
647
755
  self.class.persistents.each do |field|
648
756
  key = "#{page.class}_#{self.class}_#{instance_variable_name}_#{field}"
649
- session_data[key] = instance_variable_get("@#{field}".to_sym)
757
+ session_data[key] = instance_variable_get("@#{field}".to_sym) if session_data
650
758
  end
651
759
  end
652
760
 
@@ -654,7 +762,7 @@ module Trellis
654
762
  self.class.persistents.each do |field|
655
763
  field_sym = "@#{field}".to_sym
656
764
  current_value = instance_variable_get(field_sym)
657
- new_value = session_data["#{page.class}_#{self.class}_#{instance_variable_name}_#{field}"]
765
+ new_value = session_data["#{page.class}_#{self.class}_#{instance_variable_name}_#{field}"] if session_data
658
766
  if current_value != new_value && new_value != nil
659
767
  instance_variable_set(field_sym, new_value)
660
768
  end
@@ -685,7 +793,7 @@ module Trellis
685
793
  # create pass-through methods for each event handler in the component (on_something methods)
686
794
  self.public_instance_methods.each do |method_name|
687
795
  if method_name.starts_with?('on_')
688
- page.meta_def("#{method_name}_from_#{cname}#{id}") do |*args|
796
+ page.meta_def("#{method_name}_from_#{cname}_#{id}") do |*args|
689
797
  result = page.instance_variable_get("@#{cname}_#{id}".to_sym).send(method_name.to_sym, *args)
690
798
  # if the method returns a page, navigate to that page, otherwise navigate to the source page
691
799
  (result && result.kind_of?(String)) ? result : page
@@ -697,5 +805,7 @@ module Trellis
697
805
  end
698
806
 
699
807
  # load trellis core components
700
- require 'trellis/core_components'
808
+ require 'trellis/component_library/core_components'
809
+ require 'trellis/component_library/grid'
810
+ require 'trellis/component_library/object_editor'
701
811
  end
data/lib/trellis/utils.rb CHANGED
@@ -57,19 +57,25 @@ class Class #:nodoc:
57
57
  downcase.split('/').last
58
58
  end
59
59
 
60
- def meta_attr_accessor(*syms)
60
+ def class_attr_accessor(*syms)
61
61
  syms.flatten.each do |sym|
62
62
  metaclass.instance_eval { attr_accessor(sym) }
63
63
  end
64
64
  end
65
+
66
+ def instance_attr_accessor(*syms)
67
+ syms.flatten.each do |sym|
68
+ instance_eval { attr_accessor(sym) }
69
+ end
70
+ end
65
71
 
66
- def meta_attr_reader(*syms)
72
+ def class_attr_reader(*syms)
67
73
  syms.flatten.each do |sym|
68
74
  metaclass.instance_eval { attr_reader(sym) }
69
75
  end
70
76
  end
71
77
 
72
- def meta_attr_writer(*syms)
78
+ def class_attr_writer(*syms)
73
79
  syms.flatten.each do |sym|
74
80
  metaclass.instance_eval { attr_writer(sym) }
75
81
  end
@@ -28,7 +28,7 @@ module Trellis
28
28
  module VERSION #:nodoc:
29
29
  MAJOR = 0
30
30
  MINOR = 0
31
- TINY = 1
31
+ TINY = 2
32
32
 
33
33
  STRING = [MAJOR, MINOR, TINY].join('.')
34
34
  end
data/tasks/rspec.rake CHANGED
@@ -18,4 +18,5 @@ desc "Run the specs under spec"
18
18
  Spec::Rake::SpecTask.new do |t|
19
19
  t.spec_opts = ['--options', "test/spec.opts"]
20
20
  t.spec_files = FileList['test/**/*_spec.rb']
21
- end
21
+ #t.rcov = true
22
+ end
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/spec_helper.rb'
3
3
  require "rack"
4
4
  require_fixtures 'application_spec_applications'
5
5
 
6
- describe Trellis::Application, " a Trellis application when declared" do
6
+ describe Trellis::Application, " when declared" do
7
7
  before do
8
8
  @homepage = TestApp::MyApp.instance_eval { @homepage }
9
9
  @pages = TestApp::MyApp.instance_eval { @pages }
@@ -14,19 +14,19 @@ describe Trellis::Application, " a Trellis application when declared" do
14
14
  @homepage.should == :home
15
15
  end
16
16
 
17
- it "should contain the home page in its collection of pages" do
18
- @pages.include?(:home).should be(true)
19
- end
20
-
21
17
  it "should contain any declared static routes" do
22
18
  images_route = @static_routes.select { |item| item[:urls].include?('/images') }
23
- images_route.should_not be_empty
19
+ images_route.should_not be_empty
24
20
  style_route = @static_routes.select { |item| item[:urls].include?('/style') }
25
21
  style_route.should_not be_empty
26
22
  favicon_route = @static_routes.select { |item| item[:urls].include?('/favicon.ico') }
27
23
  favicon_route.should_not be_empty
28
- yui_route = @static_routes.select { |item| item[:urls].include?('/yui') && item[:root].include?('./js') }
29
- yui_route.should_not be_empty
24
+ jquery_route = @static_routes.select { |item| item[:urls].include?('/jquery') && item[:root].include?('./js') }
25
+ jquery_route.should_not be_empty
26
+ end
27
+
28
+ it "should include Rack::Utils" do
29
+ TestApp::MyApp.included_modules.should include(Rack::Utils)
30
30
  end
31
31
 
32
32
  end
@@ -47,33 +47,58 @@ describe Trellis::Application, " when requesting the root url with a GET" do
47
47
  end
48
48
  end
49
49
 
50
- describe Trellis::Application, " when sending an event to a page" do
50
+ describe Trellis::Application, " requesting a route" do
51
51
  before(:each) do
52
52
  application = TestApp::MyApp.new
53
- @request = Rack::MockRequest.new(application)
53
+ @request = Rack::MockRequest.new(application)
54
54
  end
55
-
56
- it "if the event handler returns self it should render the receiving page" do
57
- response = @request.get("/home.event1")
58
- response.body.should == "<html><body><h1>Hello World!</h1></body></html>"
55
+
56
+ it "should return a 404 (not found)" do
57
+ response = @request.get("/blowup")
58
+ response.status.should be(404)
59
59
  end
60
-
61
- it "should return a response as a string if the event handler returns a String" do
62
- response = @request.get("/home.event2")
63
- response.body.should == "just some text"
64
- end
65
-
66
- it "should render the injected page as a response if the event handler returns an injected page " do
67
- response = @request.get("/home.event3")
68
- response.body.should == "<html><body><p>Goodbye Cruel World </p></body></html>"
60
+
61
+ it "should return the page contents of the first page matching the route" do
62
+ response = @request.get("/whoa")
63
+ response.body.should == "<html><body>whoa!</body></html>"
64
+ end
65
+
66
+ it "should support a single named parameter" do
67
+ response_brian = @request.get("/hello/brian")
68
+ response_anne = @request.get("/hello/anne")
69
+ response_brian.body.should == "<html><body><h2>Hello</h2>brian</body></html>"
70
+ response_anne.body.should == '<html><body><h2>Hello</h2>anne</body></html>'
71
+ end
72
+
73
+ it "should support multiple named parameters" do
74
+ response = @request.get('/report/2009/05/31')
75
+ response.body.should == "<html><body><h2>Report for</h2>05/31/2009</body></html>"
76
+ end
77
+
78
+ it "should support optional parameters" do
79
+ response_all_params = @request.get('/foobar/hello/world')
80
+ response_one_param = @request.get('/foobar/hello')
81
+ response_no_param = @request.get('/foobar')
82
+ response_all_params.body.should == "<html><body>hello-world</body></html>"
83
+ response_one_param.body.should == "<html><body>hello-</body></html>"
84
+ response_no_param.body.should == "<html><body>-</body></html>"
85
+ end
86
+
87
+ it "should support a wildcard parameters" do
88
+ response = @request.get('/splat/goodbye/cruel/world')
89
+ response.body.should == '<html><body>goodbye/cruel/world</body></html>'
90
+ end
91
+
92
+ it "should supports mixing multiple splat" do
93
+ response = @request.get('/splats/bar/foo/bling/baz/boom')
94
+ response.body.should == '<html><body>barblingbaz/boom</body></html>'
95
+
96
+ no_route_response = @request.get('/splats/bar/foo/baz')
97
+ no_route_response.status.should be(404)
69
98
  end
70
- end
71
99
 
72
- describe Trellis::Page, " when calling inject_dependent_pages on an instance of child class of Page" do
73
- it "should contain instances of the injected pages" do
74
- homepage = TestApp::Home.new
75
- homepage.inject_dependent_pages
76
- injected_page = homepage.instance_eval { @other }
77
- injected_page.class.should == TestApp::Other
100
+ it "should supports mixing named and wildcard params" do
101
+ response = @request.get('/mixed/afoo/bar/baz')
102
+ response.body.should == '<html><body>bar/baz-afoo</body></html>'
78
103
  end
79
104
  end