hypertemplate 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/LICENSE +29 -0
  2. data/README.md +134 -0
  3. data/lib/hypertemplate.rb +14 -0
  4. data/lib/hypertemplate/builder.rb +33 -0
  5. data/lib/hypertemplate/builder/base.rb +111 -0
  6. data/lib/hypertemplate/builder/json.rb +132 -0
  7. data/lib/hypertemplate/builder/values.rb +33 -0
  8. data/lib/hypertemplate/builder/xml.rb +119 -0
  9. data/lib/hypertemplate/errors.rb +3 -0
  10. data/lib/hypertemplate/hook.rb +6 -0
  11. data/lib/hypertemplate/hook/rails.rb +112 -0
  12. data/lib/hypertemplate/hook/sinatra.rb +44 -0
  13. data/lib/hypertemplate/hook/tilt.rb +68 -0
  14. data/lib/hypertemplate/recipes.rb +25 -0
  15. data/lib/hypertemplate/registry.rb +24 -0
  16. data/lib/hypertemplate/version.rb +13 -0
  17. data/script/console +7 -0
  18. data/test/hypertemplate/builder/base_test.rb +45 -0
  19. data/test/hypertemplate/builder/json_test.rb +506 -0
  20. data/test/hypertemplate/builder/values_test.rb +12 -0
  21. data/test/hypertemplate/builder/xml_test.rb +479 -0
  22. data/test/hypertemplate/helper_test.rb +116 -0
  23. data/test/hypertemplate/hook/rails_test.rb +77 -0
  24. data/test/hypertemplate/hook/sinatra_test.rb +89 -0
  25. data/test/hypertemplate/hook/tilt_test.rb +48 -0
  26. data/test/hypertemplate/recipes_test.rb +94 -0
  27. data/test/rails2_skel/Rakefile +16 -0
  28. data/test/rails2_skel/app/controllers/application_controller.rb +1 -0
  29. data/test/rails2_skel/app/controllers/test_controller.rb +18 -0
  30. data/test/rails2_skel/app/views/test/_feed_member.tokamak +9 -0
  31. data/test/rails2_skel/app/views/test/feed.tokamak +24 -0
  32. data/test/rails2_skel/app/views/test/show.hyper +31 -0
  33. data/test/rails2_skel/config/boot.rb +110 -0
  34. data/test/rails2_skel/config/environment.rb +20 -0
  35. data/test/rails2_skel/config/environments/development.rb +17 -0
  36. data/test/rails2_skel/config/environments/production.rb +28 -0
  37. data/test/rails2_skel/config/environments/test.rb +28 -0
  38. data/test/rails2_skel/config/initializers/cookie_verification_secret.rb +2 -0
  39. data/test/rails2_skel/config/initializers/mime_types.rb +3 -0
  40. data/test/rails2_skel/config/initializers/new_rails_defaults.rb +10 -0
  41. data/test/rails2_skel/config/initializers/session_store.rb +5 -0
  42. data/test/rails2_skel/config/routes.rb +43 -0
  43. data/test/rails2_skel/log/development.log +10190 -0
  44. data/test/rails2_skel/script/console +3 -0
  45. data/test/test_helper.rb +9 -0
  46. metadata +207 -0
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ Copyright (c) 2010 Caelum
2
+ All rights reserved.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ Copyright applicable to Tokamak's original code:
16
+ Copyright (c) 2010 Abril Midia
17
+ All rights reserved.
18
+
19
+ Licensed under the Apache License, Version 2.0 (the "License");
20
+ you may not use this file except in compliance with the License.
21
+ You may obtain a copy of the License at
22
+
23
+ http://www.apache.org/licenses/LICENSE-2.0
24
+
25
+ Unless required by applicable law or agreed to in writing, software
26
+ distributed under the License is distributed on an "AS IS" BASIS,
27
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28
+ See the License for the specific language governing permissions and
29
+ limitations under the License.
@@ -0,0 +1,134 @@
1
+ # Hypertemplate
2
+
3
+ *Hypertemplate* is a template engine for hypermedia resources.
4
+ It provides a single DSL to generate different representations.
5
+ This version supports json and xml generation (you can add other media types
6
+ easily).
7
+
8
+ ## Configuring
9
+
10
+ If you are using Restfulie, there is no need to configure it, simply check the DSL.
11
+
12
+ The lib provide hooks for:
13
+
14
+ * Rails
15
+ * Sinatra
16
+ * Tilt ([https://github.com/rtomayko/tilt](https://github.com/rtomayko/tilt))
17
+
18
+ Just put `require "hypertemplate/hook/[sinatra|rails|tilt]"` on your app. See
19
+ the integration tests for hook samples.
20
+
21
+ ## Hypertemplate DSL
22
+
23
+ products {
24
+ link "order", orders_url, "type" => "application/xml"
25
+ @products.each do |prod|
26
+ product {
27
+ link :self, product_url(prod)
28
+ id prod.id
29
+ name prod.name
30
+ price prod.price
31
+ }
32
+ end
33
+ }
34
+
35
+
36
+ Generates the following representations:
37
+
38
+ ### JSON
39
+
40
+ {"products":
41
+ {"link":[
42
+ {"type":"application/xml",
43
+ "rel":"order",
44
+ "href":"http://localhost:3000/orders"}],
45
+ "product":[
46
+ {"link":
47
+ [{"rel":"self",
48
+ "href":"http://localhost:3000/products/2",
49
+ "type":"application/json"}],
50
+ "id":2,
51
+ "name":"Rest Training (20h)",
52
+ "price":"800.0"},
53
+ {"link":
54
+ [{"rel":"self",
55
+ "href":"http://localhost:3000/products/3",
56
+ "type":"application/json"}],
57
+ "id":3,
58
+ "name":"Modern Software architecture and Design (20h)",
59
+ "price":"800.0"}
60
+ ]
61
+ }
62
+ }
63
+
64
+ ### XML
65
+
66
+ <?xml version="1.0"?>
67
+ <products>
68
+ <link type="application/xml" rel="order" href="http://localhost:3000/orders"/>
69
+ <product>
70
+ <link rel="self" href="http://localhost:3000/products/2" type="application/xml"/>
71
+ <id>2</id>
72
+ <name>Rest Training (20h)</name>
73
+ <price>800.0</price>
74
+ </product>
75
+ <product>
76
+ <link rel="self" href="http://localhost:3000/products/3" type="application/xml"/>
77
+ <id>3</id>
78
+ <name>Modern Software architecture and Design (20h)</name>
79
+ <price>800.0</price>
80
+ </product>
81
+ </products>
82
+
83
+
84
+
85
+ ## Other features
86
+
87
+ * You can declare recipes once and reuse it later (see `Hypertemplate::Recipes`)
88
+ * You can extend `Hypertemplate::Builder::Base` to support a custom media type.
89
+ * You can customize the DSL entrypoint helpers, used by the hooks (see `Hypertemplate::Builder::HelperTest`)
90
+
91
+ ## Want to know more?
92
+
93
+ Please check the unit tests, you can see a lot of richer samples, including tests for the hooks.
94
+
95
+ *This library was extracted from [Restfulie](https://github.com/caelum/restfulie),
96
+ then forked from [Tokamak](http://github.com/abril/tokamak). See LICENSE.txt*
97
+
98
+ ## A more complex example
99
+
100
+ order {
101
+ link "self", order_url(@order)
102
+ link "payment", order_payments_url(@order), "type" => "application/xml"
103
+ link "calendar", order_calendar_url(@order), "type" => "text/calendar"
104
+ address @order.address
105
+ price @order.price
106
+ state @order.state
107
+ payments {
108
+ @order.payments.each do |p|
109
+ payment do
110
+ value p.value
111
+ state p.state
112
+ end
113
+ end
114
+ }
115
+ items {
116
+ @order.items.each do |i|
117
+ item do
118
+ id i.product.id
119
+ name i.product.name
120
+ price i.product.price
121
+ quantity i.quantity
122
+ end
123
+ end
124
+ }
125
+ }
126
+
127
+ ## Questions? Help?
128
+
129
+ * Join the mailing list restfulie@googlegroups.com
130
+ * Join #restfulie at freenode IRC
131
+
132
+ ## Compatibility
133
+
134
+ Hypertemplate can be used within Restfulie and its DSL is backward compatible with Tokamak 1.1.0 ~ 1.1.5.
@@ -0,0 +1,14 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__))
2
+
3
+ # Dependencies
4
+ require "rubygems"
5
+ require "bundler/setup"
6
+
7
+ module Hypertemplate
8
+ end
9
+
10
+ require "hypertemplate/errors"
11
+ require "hypertemplate/recipes"
12
+ require "hypertemplate/builder"
13
+ require "hypertemplate/registry"
14
+ require "hypertemplate/hook"
@@ -0,0 +1,33 @@
1
+ module Hypertemplate
2
+ module Builder
3
+ require "hypertemplate/builder/base"
4
+ require "hypertemplate/builder/values"
5
+ require "hypertemplate/builder/json"
6
+ require "hypertemplate/builder/xml"
7
+
8
+ def self.helper_module_for(const)
9
+ mod = Module.new
10
+ mod.module_eval <<-EOS
11
+ def collection(obj, *args, &block)
12
+ #{const.name}.build(obj, *args, &block)
13
+ end
14
+
15
+ alias_method :member, :collection
16
+
17
+ def method_missing(sym, *args, &block)
18
+
19
+ # you thought meta-(meta-programming) was nasty?
20
+ # this is nasty... help me! i am an ActionView
21
+ # so coupled to the entire Rails stack that I need
22
+ # this hack.
23
+ super if @first_invocation
24
+ @first_invocation = true
25
+
26
+ #{const.name}.build_dsl(self, :root => sym.to_s, &block)
27
+ end
28
+
29
+ EOS
30
+ mod
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,111 @@
1
+ module Hypertemplate
2
+ module Builder
3
+ class Base
4
+
5
+ # field id is quite common
6
+ undef_method :id if respond_to?(:id)
7
+
8
+ class << self
9
+
10
+ def build_dsl(obj, options = {}, &block)
11
+ recipe = block_given? ? block : options.delete(:recipe)
12
+ raise Hypertemplate::BuilderError.new("Recipe required to build representation.") unless recipe.respond_to?(:call)
13
+
14
+ builder = self.new(nil, options)
15
+ copy_internal_variables(builder, obj)
16
+ builder.instance_variable_set :@view, obj
17
+ def builder.method_missing(name, *args, &block)
18
+ begin
19
+ @view.send name, *args, &block
20
+ rescue
21
+ super
22
+ end
23
+ end
24
+ builder.instance_exec(obj, &recipe)
25
+
26
+ builder.representation
27
+ end
28
+
29
+ def copy_internal_variables(builder, obj)
30
+ # TODO this is nasty. i am sorry.
31
+ obj.instance_variables.each do |name|
32
+ builder.instance_variable_set name, obj.instance_variable_get(name)
33
+ end
34
+ end
35
+
36
+ def build(obj, options = {}, &block)
37
+ recipe = block_given? ? block : options.delete(:recipe)
38
+ raise Hypertemplate::BuilderError.new("Recipe required to build representation.") unless recipe.respond_to?(:call)
39
+
40
+ builder = self.new(obj, options)
41
+
42
+ if recipe.arity==-1
43
+ builder.instance_exec(&recipe)
44
+ else
45
+ recipe.call(*[builder, obj, options][0, recipe.arity])
46
+ end
47
+
48
+ builder.representation
49
+ end
50
+
51
+ def helper
52
+ @helper_module ||= Hypertemplate::Builder.helper_module_for(self)
53
+ end
54
+
55
+ def collection_helper_default_options(options = {}, &block)
56
+ generic_helper(:collection, options, &block)
57
+ end
58
+
59
+ def member_helper_default_options(type, options = {}, &block)
60
+ generic_helper(:member, options, &block)
61
+ end
62
+
63
+ def generic_helper(section, options = {}, &block)
64
+ helper.send(:remove_method, section)
65
+ var_name = "@@more_options_#{section.to_s}".to_sym
66
+ helper.send(:class_variable_set, var_name, options)
67
+ helper.module_eval <<-EOS
68
+ def #{section.to_s}(obj, *args, &block)
69
+ #{var_name}.merge!(args.shift)
70
+ args.unshift(#{var_name})
71
+ #{self.name}.build(obj, *args, &block)
72
+ end
73
+ EOS
74
+ end
75
+ end
76
+
77
+ # adds a key and value pair to the representation
78
+ # example:
79
+ #
80
+ # name 'guilherme'
81
+ def method_missing(sym, *args, &block)
82
+ values do |v|
83
+ v.send sym, *args, &block
84
+ end
85
+ end
86
+
87
+ def ns(*args, &block)
88
+ values do |v|
89
+ v.send(:[], *args, &block)
90
+ end
91
+ end
92
+
93
+ # writes a key and value pair to the representation
94
+ # example:
95
+ #
96
+ # write :name, "guilherme"
97
+ def write(sym, val)
98
+ values do |v|
99
+ v.send sym, val
100
+ end
101
+ end
102
+
103
+
104
+ def each(collection, options = {}, &block)
105
+ options[:collection] = collection
106
+ members(options, &block)
107
+ end
108
+
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,132 @@
1
+ module Hypertemplate
2
+ module Builder
3
+ class Json < Hypertemplate::Builder::Base
4
+
5
+ def self.media_types
6
+ ["application/json"]
7
+ end
8
+
9
+ attr_reader :raw
10
+
11
+ def initialize(obj, options = {})
12
+ initialize_library
13
+ if options[:root]
14
+ @raw = { options[:root] => {} }
15
+ @current = @raw[options[:root]]
16
+ else
17
+ @current = @raw = {}
18
+ end
19
+ @obj = obj
20
+ end
21
+
22
+ def initialize_library
23
+ return if defined?(::JSON)
24
+ require "json/pure"
25
+ end
26
+
27
+ def members(options = {}, &block)
28
+ collection = options[:collection] || @obj
29
+ raise Hypertemplate::BuilderError.new("Members method require a collection to execute") unless collection.respond_to?(:each)
30
+ root = options[:root] || "members"
31
+
32
+ add_to(@current, root, [])
33
+ collection.each do |member|
34
+
35
+ parent = @current
36
+ @current = {}
37
+ if block.arity==1
38
+ # new dsl
39
+ block.call(member)
40
+ else
41
+ # old dsl (deprecate at 1.3?)
42
+ block.call(self, member)
43
+ end
44
+ add_to(parent, root, @current)
45
+ @current = parent
46
+
47
+ end
48
+ end
49
+
50
+ def values(options = {}, &block)
51
+ yield Values.new(self)
52
+ end
53
+
54
+ def link(relationship, uri, options = {})
55
+ # Start link array
56
+ @current["link"] ||= []
57
+ stringify_keys(options)
58
+
59
+ options["rel"] = relationship.to_s
60
+ options["href"] = uri
61
+ options["type"] ||= "application/json"
62
+ insert_value("link", nil, options)
63
+ end
64
+
65
+ def insert_value(name, prefix, *args, &block)
66
+ node = create_element(block_given?, *args)
67
+
68
+ if block_given?
69
+ parent = @current
70
+ @current = node
71
+ block.call
72
+ @current = parent
73
+ end
74
+
75
+ # TODO change to name.to_sym in 1.3?
76
+ add_to(@current, name, node)
77
+ end
78
+
79
+ def representation
80
+ @raw.to_json
81
+ end
82
+
83
+ private
84
+
85
+ def create_element(has_block, *args)
86
+ vals = []
87
+ hashes = []
88
+
89
+ args.each do |arg|
90
+ arg.kind_of?(Hash) ? hashes << arg : vals << arg
91
+ end
92
+
93
+ if hashes.empty?
94
+ if has_block
95
+ {}
96
+ elsif vals.size<=1
97
+ vals.first
98
+ else
99
+ vals
100
+ end
101
+ else
102
+ # yes we have hashes
103
+ node = {}
104
+ hashes.each { |hash| node.merge!(hash) }
105
+ unless vals.empty?
106
+ vals = vals.first if vals.size == 1
107
+ node = has_block ? {} : [node, vals]
108
+ end
109
+ node
110
+ end
111
+ end
112
+
113
+ def add_to(node, name, value)
114
+ if node[name]
115
+ if node[name].kind_of?(Array)
116
+ node[name] << value
117
+ else
118
+ node[name] = [node[name], value]
119
+ end
120
+ else
121
+ node[name] = value
122
+ end
123
+ end
124
+
125
+ def stringify_keys(hash)
126
+ hash.keys.each do |key|
127
+ hash[key.to_s] = hash.delete(key)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end