tokamak 1.0.0.beta2

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 (52) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +27 -0
  4. data/Gemfile.lock +77 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +69 -0
  7. data/Rakefile +50 -0
  8. data/VERSION +1 -0
  9. data/lib/tokamak.rb +21 -0
  10. data/lib/tokamak/atom.rb +8 -0
  11. data/lib/tokamak/atom/base.rb +87 -0
  12. data/lib/tokamak/atom/builder.rb +107 -0
  13. data/lib/tokamak/atom/helpers.rb +13 -0
  14. data/lib/tokamak/error.rb +6 -0
  15. data/lib/tokamak/json.rb +10 -0
  16. data/lib/tokamak/json/base.rb +83 -0
  17. data/lib/tokamak/json/builder.rb +98 -0
  18. data/lib/tokamak/json/helpers.rb +13 -0
  19. data/lib/tokamak/representation.rb +3 -0
  20. data/lib/tokamak/representation/atom.rb +18 -0
  21. data/lib/tokamak/representation/atom/atom.rng +597 -0
  22. data/lib/tokamak/representation/atom/base.rb +140 -0
  23. data/lib/tokamak/representation/atom/category.rb +39 -0
  24. data/lib/tokamak/representation/atom/entry.rb +56 -0
  25. data/lib/tokamak/representation/atom/factory.rb +48 -0
  26. data/lib/tokamak/representation/atom/feed.rb +108 -0
  27. data/lib/tokamak/representation/atom/link.rb +66 -0
  28. data/lib/tokamak/representation/atom/person.rb +46 -0
  29. data/lib/tokamak/representation/atom/source.rb +57 -0
  30. data/lib/tokamak/representation/atom/tag_collection.rb +36 -0
  31. data/lib/tokamak/representation/atom/xml.rb +94 -0
  32. data/lib/tokamak/representation/generic.rb +20 -0
  33. data/lib/tokamak/representation/json.rb +11 -0
  34. data/lib/tokamak/representation/json/base.rb +25 -0
  35. data/lib/tokamak/representation/json/keys_as_methods.rb +72 -0
  36. data/lib/tokamak/representation/json/link.rb +27 -0
  37. data/lib/tokamak/representation/json/link_collection.rb +21 -0
  38. data/lib/tokamak/representation/links.rb +9 -0
  39. data/lib/tokamak/values.rb +29 -0
  40. data/lib/tokamak/xml.rb +12 -0
  41. data/lib/tokamak/xml/base.rb +60 -0
  42. data/lib/tokamak/xml/builder.rb +115 -0
  43. data/lib/tokamak/xml/helpers.rb +13 -0
  44. data/lib/tokamak/xml/link.rb +31 -0
  45. data/lib/tokamak/xml/links.rb +35 -0
  46. data/spec/integration/atom/atom_spec.rb +191 -0
  47. data/spec/integration/full_atom.xml +92 -0
  48. data/spec/integration/full_json.js +46 -0
  49. data/spec/integration/json/json_spec.rb +172 -0
  50. data/spec/integration/xml/xml_spec.rb +203 -0
  51. data/spec/spec_helper.rb +12 -0
  52. metadata +248 -0
@@ -0,0 +1,12 @@
1
+ require 'nokogiri'
2
+
3
+ module Tokamak
4
+ module Xml
5
+ autoload :Base, 'tokamak/xml/base'
6
+ autoload :Builder, 'tokamak/xml/builder'
7
+ autoload :Helpers, 'tokamak/xml/helpers'
8
+ autoload :Links, 'tokamak/xml/links'
9
+ autoload :Link, 'tokamak/xml/link'
10
+ extend Base::ClassMethods
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ require 'active_support/core_ext/hash/conversions'
2
+
3
+ module Tokamak
4
+ module Xml
5
+ module Base
6
+ module ClassMethods
7
+ mattr_reader :media_type_name
8
+ @@media_type_name = 'application/xml'
9
+
10
+ mattr_reader :headers
11
+ @@headers = {
12
+ :post => { 'Content-Type' => media_type_name }
13
+ }
14
+
15
+ def marshal(entity, options = {})
16
+ to_xml(entity, options)
17
+ end
18
+
19
+ def unmarshal(string)
20
+ Hash.from_xml string
21
+ end
22
+
23
+ mattr_reader :recipes
24
+ @@recipes = {}
25
+
26
+ def describe_recipe(recipe_name, options={}, &block)
27
+ raise 'Undefined recipe' unless block_given?
28
+ raise 'Undefined recipe_name' unless recipe_name
29
+ @@recipes[recipe_name] = block
30
+ end
31
+
32
+ def to_xml(obj, options = {}, &block)
33
+ return obj if obj.kind_of?(String)
34
+
35
+ if block_given?
36
+ recipe = block
37
+ elsif options[:recipe]
38
+ recipe = @@recipes[options[:recipe]]
39
+ elsif obj.kind_of?(Hash) && obj.size==1
40
+ root = obj.values.first
41
+ return root.to_xml(:root => obj.keys.first)
42
+ else
43
+ return obj.to_xml
44
+ end
45
+
46
+ # Create representation and proxy
47
+ builder = Builder.new(obj, options)
48
+
49
+ # Check recipe arity size before calling it
50
+ recipe.call(*[builder, obj, options][0,recipe.arity])
51
+ builder.doc.to_xml
52
+ end
53
+
54
+ def helper
55
+ Helpers
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,115 @@
1
+ module Tokamak
2
+ module Xml
3
+ # Implements the interface for marshal Xml media type requests (application/xml)
4
+ class Builder
5
+ attr_reader :doc
6
+ def initialize(obj, options = {})
7
+ @doc = Nokogiri::XML::Document.new
8
+ @obj = obj
9
+ root = options[:root] || Tokamak.root_element_for(obj)
10
+ @parent = @doc.create_element(root)
11
+ @parent.parent = @doc
12
+ end
13
+
14
+ def method_missing(sym, *args, &block)
15
+ values do |v|
16
+ v.send sym, *args, &block
17
+ end
18
+ end
19
+
20
+ def values(options = {}, &block)
21
+ options.each do |key,value|
22
+ attr = key.to_s
23
+ if attr =~ /^xmlns(:\w+)?$/
24
+ ns = attr.split(":", 2)[1]
25
+ @parent.add_namespace_definition(ns, value)
26
+ end
27
+ end
28
+ yield Values.new(self)
29
+ end
30
+
31
+ def insert_value(name, prefix, *args, &block)
32
+ node = create_element(name.to_s, prefix, *args)
33
+ node.parent = @parent
34
+
35
+ if block_given?
36
+ @parent = node
37
+ block.call
38
+ @parent = node.parent
39
+ end
40
+ end
41
+
42
+ def link(relationship, uri, options = {})
43
+ options["rel"] = relationship.to_s
44
+ options["href"] = uri
45
+ options["type"] ||= "application/xml"
46
+ insert_value("link", nil, options)
47
+ end
48
+
49
+ def members(a_collection = nil, options = {}, &block)
50
+ collection = a_collection || @obj
51
+ raise Error::BuilderError("Members method require a collection to execute") unless collection.respond_to?(:each)
52
+ collection.each do |member|
53
+ root = options[:root] || Tokamak.root_element_for(member)
54
+ entry = @doc.create_element(root)
55
+ entry.parent = @parent
56
+ @parent = entry
57
+ block.call(self, member)
58
+ @parent = entry.parent
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def create_element(node, prefix, *args)
65
+ node = @doc.create_element(node) do |n|
66
+ if prefix
67
+ if namespace = prefix_valid?(prefix)
68
+ # Adding namespace prefix
69
+ n.namespace = namespace
70
+ namespace = nil
71
+ end
72
+ end
73
+
74
+ args.each do |arg|
75
+ case arg
76
+ # Adding XML attributes
77
+ when Hash
78
+ arg.each { |k,v|
79
+ key = k.to_s
80
+ if key =~ /^xmlns(:\w+)?$/
81
+ ns_name = key.split(":", 2)[1]
82
+ n.add_namespace_definition(ns_name, v)
83
+ next
84
+ end
85
+ n[k.to_s] = v.to_s
86
+ }
87
+ # Adding XML node content
88
+ else
89
+ content = if arg.kind_of?(Time) || arg.kind_of?(DateTime)
90
+ arg.xmlschema
91
+ else
92
+ arg
93
+ end
94
+ n.content = content
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def prefix_valid?(prefix)
101
+ ns = @parent.namespace_definitions.find { |x| x.prefix == prefix.to_s }
102
+
103
+ unless ns
104
+ @parent.ancestors.each do |a|
105
+ next if a == @doc
106
+ ns = a.namespace_definitions.find { |x| x.prefix == prefix.to_s }
107
+ break if ns
108
+ end
109
+ end
110
+
111
+ return ns
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,13 @@
1
+ module Tokamak
2
+ module Xml
3
+ module Helpers
4
+ def collection(obj, opts = {}, &block)
5
+ Xml.to_xml(obj, opts, &block)
6
+ end
7
+
8
+ def member(obj, opts = {}, &block)
9
+ Xml.to_xml(obj, opts, &block)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ module Tokamak
2
+ module Xml
3
+ class Link
4
+ def initialize(options = {})
5
+ @options = options
6
+ end
7
+ def href
8
+ @options["href"]
9
+ end
10
+ def rel
11
+ @options["rel"]
12
+ end
13
+ def content_type
14
+ @options["type"]
15
+ end
16
+ def type
17
+ content_type
18
+ end
19
+ def follow
20
+ r = Restfulie.at(href)
21
+ r = r.as(content_type) if content_type
22
+ r
23
+ end
24
+
25
+ def to_s
26
+ "<link to #{@options}>"
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ module Tokamak
2
+ module Xml
3
+
4
+ # an object to represent a list of links that can be invoked
5
+ class Links
6
+
7
+ def initialize(links)
8
+ @hash = {}
9
+ links = [links] unless links.kind_of? Array
10
+ links = [] unless links
11
+ links.each { |l|
12
+ link = Tokamak::Xml::Link.new(l)
13
+ @hash[link.rel.to_s] = link
14
+ }
15
+ end
16
+
17
+ def [](name)
18
+ @hash[name]
19
+ end
20
+
21
+ def size
22
+ @hash.size
23
+ end
24
+
25
+ def keys
26
+ @hash.keys
27
+ end
28
+
29
+ def method_missing(sym, *args)
30
+ raise "Links can not receive arguments" unless args.empty?
31
+ self[sym.to_s]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,191 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ module Tokamak::Test
4
+ class SimpleClass
5
+ attr_accessor :id, :title, :updated
6
+ def initialize(id,title,updated)
7
+ @id, @title, @updated = id, title, updated
8
+ end
9
+ end
10
+ end
11
+
12
+ describe Tokamak do
13
+ describe 'Atom' do
14
+
15
+ describe "Feed" do
16
+ it "should create a feed from builder DSL" do
17
+ time = Time.now
18
+ some_articles = [
19
+ {:id => 1, :title => "a great article", :updated => time},
20
+ {:id => 2, :title => "another great article", :updated => time}
21
+ ]
22
+
23
+ feed = to_atom(some_articles) do |collection|
24
+ collection.values do |values|
25
+ values.id "http://example.com/feed"
26
+ values.title "Feed"
27
+ values.updated time
28
+
29
+ values.author {
30
+ values.name "John Doe"
31
+ values.email "joedoe@example.com"
32
+ }
33
+
34
+ values.author {
35
+ values.name "Foo Bar"
36
+ values.email "foobar@example.com"
37
+ }
38
+ end
39
+
40
+ collection.link("next", "http://a.link.com/next")
41
+ collection.link("previous", "http://a.link.com/previous")
42
+
43
+ collection.members do |member, article|
44
+ member.values do |values|
45
+ values.id "uri:#{article[:id]}"
46
+ values.title article[:title]
47
+ values.updated article[:updated]
48
+ end
49
+
50
+ member.link("image", "http://example.com/image/1")
51
+ member.link("image", "http://example.com/image/2", :type => "application/atom+xml")
52
+ end
53
+ end
54
+
55
+ feed.atom_type.should == "feed"
56
+ feed.id.should == "http://example.com/feed"
57
+ feed.title.should == "Feed"
58
+ feed.updated.should == DateTime.parse(time.xmlschema)
59
+ feed.authors.first.name.should == "John Doe"
60
+ feed.authors.last.email.should == "foobar@example.com"
61
+
62
+ feed.entries.first.id.should == "uri:1"
63
+ feed.entries.first.title.should == "a great article"
64
+ end
65
+
66
+ it "should create a feed from a string input" do
67
+ full_atom = IO.read(File.dirname(__FILE__) + '/../full_atom.xml')
68
+ feed = to_atom(full_atom)
69
+
70
+ feed.id.should == "http://example.com/albums/1"
71
+ feed.title.should == "Albums feed"
72
+ feed.updated.should be_kind_of(Time)
73
+ feed.updated.should == Time.parse("2010-05-03T16:29:26-03:00")
74
+ end
75
+
76
+ end
77
+
78
+ describe "Entry" do
79
+ it "should create an entry from builder DSL" do
80
+ time = Time.now
81
+ an_article = {:id => 1, :title => "a great article", :updated => time}
82
+
83
+ entry = to_atom(an_article, :atom_type => :entry) do |member, article|
84
+ member.values do |values|
85
+ values.id "uri:#{article[:id]}"
86
+ values.title article[:title]
87
+ values.updated article[:updated]
88
+ end
89
+
90
+ member.link("image", "http://example.com/image/1")
91
+ member.link("image", "http://example.com/image/2", :type => "application/atom+xml")
92
+ end
93
+
94
+ entry.atom_type.should == "entry"
95
+ entry.id.should == "uri:1"
96
+ entry.title.should == "a great article"
97
+ entry.updated.should == DateTime.parse(time.xmlschema)
98
+ end
99
+
100
+ it "should be able to declare links inside values block" do
101
+ time = Time.now
102
+ an_article = {:id => 1, :title => "a great article", :updated => time}
103
+
104
+ entry = to_atom(an_article, :atom_type => :entry) do |member, article|
105
+ member.values do |values|
106
+ values.id "uri:#{article[:id]}"
107
+ values.title article[:title]
108
+ values.updated article[:updated]
109
+
110
+ values.domain("xmlns" => "http://a.namespace.com") {
111
+ member.link("image", "http://example.com/image/1")
112
+ member.link("image", "http://example.com/image/2", :type => "application/atom+xml")
113
+ }
114
+ end
115
+ end
116
+
117
+ entry.atom_type.should == "entry"
118
+ entry.id.should == "uri:1"
119
+ entry.title.should == "a great article"
120
+ entry.updated.should == DateTime.parse(time.xmlschema)
121
+
122
+ entry.doc.xpath("xmlns:domain", "xmlns" => "http://a.namespace.com").children.first.node_name.should == "link"
123
+ end
124
+
125
+ it "should create an entry from an already declared recipe" do
126
+
127
+ describe_recipe(:simple_entry) do |member, article|
128
+ member.values do |values|
129
+ values.id "uri:#{article[:id]}"
130
+ values.title article[:title]
131
+ values.updated article[:updated]
132
+ end
133
+
134
+ member.link("image", "http://example.com/image/1")
135
+ member.link("image", "http://example.com/image/2", :type => "application/atom+xml")
136
+ end
137
+
138
+ time = Time.now
139
+ an_article = {:id => 1, :title => "a great article", :updated => time}
140
+
141
+ entry = to_atom(an_article, :atom_type => :entry, :recipe => :simple_entry)
142
+
143
+ entry.atom_type.should == "entry"
144
+ entry.id.should == "uri:1"
145
+ entry.title.should == "a great article"
146
+ entry.updated.should == DateTime.parse(time.xmlschema)
147
+ end
148
+
149
+ end
150
+
151
+ describe "Errors" do
152
+ it "should raise error for converter without recipe" do
153
+ lambda {
154
+ to_atom
155
+ }.should raise_error(Tokamak::ConverterError, "Recipe required")
156
+ end
157
+
158
+ it "raise error to invalid atom type" do
159
+ lambda {
160
+ obj = Object.new
161
+ describe_recipe(:simple_entry) do |member, article|
162
+ member.values do |values|
163
+ values.id "uri:#{article[:id]}"
164
+ values.title article[:title]
165
+ values.updated article[:updated]
166
+ end
167
+
168
+ member.link("image", "http://example.com/image/1")
169
+ member.link("image", "http://example.com/image/2", :type => "application/atom+xml")
170
+ end
171
+
172
+ Tokamak::Atom.to_atom(obj, :recipe => :simple_entry, :atom_type => :foo)
173
+ }.should raise_error(Tokamak::ConverterError, "Undefined atom type foo")
174
+ end
175
+ end
176
+
177
+ end
178
+
179
+ def to_atom(*args, &recipe)
180
+ Tokamak::Atom.to_atom(*args, &recipe)
181
+ end
182
+
183
+ def describe_recipe(*args, &recipe)
184
+ Tokamak::Atom.describe_recipe(*args, &recipe)
185
+ end
186
+
187
+ def simple_object(*args)
188
+ Tokamak::Test::SimpleClass.new(*args)
189
+ end
190
+
191
+ end