trellis 0.0.1 → 0.0.2

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 (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