tokamak 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,27 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ if RUBY_VERSION < "1.9"
14
+ gem "ruby-debug"
15
+ else
16
+ gem "ruby-debug19", :require => "ruby-debug"
17
+ end
18
+ end
19
+
20
+ group :test do
21
+ gem "nokogiri"
22
+ end
23
+
24
+ gem "activesupport", ">= 3.0.0"
25
+ gem "actionpack", ">= 3.0.0"
26
+ gem "libxml-ruby"
27
+ gem "json_pure"
data/Gemfile.lock ADDED
@@ -0,0 +1,77 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ actionpack (3.0.0)
6
+ activemodel (= 3.0.0)
7
+ activesupport (= 3.0.0)
8
+ builder (~> 2.1.2)
9
+ erubis (~> 2.6.6)
10
+ i18n (~> 0.4.1)
11
+ rack (~> 1.2.1)
12
+ rack-mount (~> 0.6.12)
13
+ rack-test (~> 0.5.4)
14
+ tzinfo (~> 0.3.23)
15
+ activemodel (3.0.0)
16
+ activesupport (= 3.0.0)
17
+ builder (~> 2.1.2)
18
+ i18n (~> 0.4.1)
19
+ activesupport (3.0.0)
20
+ archive-tar-minitar (0.5.2)
21
+ builder (2.1.2)
22
+ columnize (0.3.2)
23
+ diff-lcs (1.1.2)
24
+ erubis (2.6.6)
25
+ abstract (>= 1.0.0)
26
+ git (1.2.5)
27
+ i18n (0.4.1)
28
+ jeweler (1.5.2)
29
+ bundler (~> 1.0.0)
30
+ git (>= 1.2.5)
31
+ rake
32
+ json_pure (1.4.6)
33
+ libxml-ruby (1.1.4)
34
+ linecache19 (0.5.11)
35
+ ruby_core_source (>= 0.1.4)
36
+ nokogiri (1.4.3.1)
37
+ rack (1.2.1)
38
+ rack-mount (0.6.12)
39
+ rack (>= 1.0.0)
40
+ rack-test (0.5.4)
41
+ rack (>= 1.0)
42
+ rake (0.8.7)
43
+ rcov (0.9.9)
44
+ rspec (2.3.0)
45
+ rspec-core (~> 2.3.0)
46
+ rspec-expectations (~> 2.3.0)
47
+ rspec-mocks (~> 2.3.0)
48
+ rspec-core (2.3.1)
49
+ rspec-expectations (2.3.0)
50
+ diff-lcs (~> 1.1.2)
51
+ rspec-mocks (2.3.0)
52
+ ruby-debug-base19 (0.11.24)
53
+ columnize (>= 0.3.1)
54
+ linecache19 (>= 0.5.11)
55
+ ruby_core_source (>= 0.1.4)
56
+ ruby-debug19 (0.11.6)
57
+ columnize (>= 0.3.1)
58
+ linecache19 (>= 0.5.11)
59
+ ruby-debug-base19 (>= 0.11.19)
60
+ ruby_core_source (0.1.4)
61
+ archive-tar-minitar (>= 0.5.2)
62
+ tzinfo (0.3.23)
63
+
64
+ PLATFORMS
65
+ ruby
66
+
67
+ DEPENDENCIES
68
+ actionpack (>= 3.0.0)
69
+ activesupport (>= 3.0.0)
70
+ bundler (~> 1.0.0)
71
+ jeweler (~> 1.5.2)
72
+ json_pure
73
+ libxml-ruby
74
+ nokogiri
75
+ rcov
76
+ rspec (~> 2.3.0)
77
+ ruby-debug19
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Guilherme Silveira
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,69 @@
1
+ = tokamak
2
+
3
+ Tokamak is a hypermedia template engine created to enpower Restfulie.
4
+
5
+ == Get it on baby!
6
+
7
+ Based on Cipriani's example on the Restfulie mailing list, this is a simple Tokamak description:
8
+
9
+ collection(@activities) do |collection|
10
+ collection.members do |m, activity|
11
+ member.values { |v| v.id = activity.id }
12
+ member.link "similar", url_for activity.similar
13
+ end
14
+ end
15
+
16
+ There is an entire chapter on tokamak usage on Restfulie's documentation. Go for it! If it still does not show what you want to generate, feel free to ask at Restfulie's mailing list.
17
+
18
+ == A full bodied example
19
+
20
+ collection(@activities) do |collection|
21
+ collection.members do |member, activity|
22
+ member.values { |values|
23
+
24
+ values.id activity.id
25
+ values.title activity.title
26
+
27
+ values.actor {
28
+ values.type activity.actor_type.downcase
29
+ values.id activity.actor_id
30
+ }
31
+
32
+ values.verbs activity.verbs
33
+
34
+ values.object {
35
+ values.type activity.object_type.downcase
36
+ values.id activity.object_id
37
+ values.content activity.object.content if activity.object_content?
38
+ }
39
+
40
+ if activity.indirect_object
41
+ values.indirect_object {
42
+ values.type activity.indirect_object_type.downcase
43
+ values.id activity.indirect_object_id
44
+ }
45
+ end
46
+ }
47
+
48
+ end
49
+ end
50
+
51
+ == Team
52
+
53
+ It was created by the team who contributed to Restfulie.
54
+
55
+ == Contributing to tokamak
56
+
57
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
58
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
59
+ * Fork the project
60
+ * Start a feature/bugfix branch
61
+ * Commit and push until you are happy with your contribution
62
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
63
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
64
+
65
+ == Copyright
66
+
67
+ Copyright (c) 2010 Guilherme Silveira. See LICENSE.txt for
68
+ further details.
69
+
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "tokamak"
16
+ gem.homepage = "http://github.com/caelum/tokamak"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{A template engine for hypermedia resources}
19
+ gem.description = %Q{A template engine for hypermedia resources}
20
+ gem.email = "guilherme.silveira@caelum.com.br"
21
+ gem.authors = ["Guilherme Silveira"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "tokamak #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0.beta2
data/lib/tokamak.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'tokamak/xml'
2
+ require 'tokamak/json'
3
+ require 'tokamak/atom'
4
+ require 'tokamak/values'
5
+ require 'tokamak/representation'
6
+ require 'tokamak/error'
7
+
8
+ module Tokamak
9
+
10
+ # Returns the default root element name for an item or collection
11
+ def self.root_element_for(obj)
12
+ if obj.kind_of?(Hash) && obj.size==1
13
+ obj.keys.first.to_s
14
+ elsif obj.kind_of?(Array) && !obj.empty?
15
+ root_element_for(obj.first).to_s.underscore.pluralize
16
+ else
17
+ obj.class.to_s.underscore
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,8 @@
1
+ module Tokamak
2
+ module Atom
3
+ autoload :Base, 'tokamak/atom/base'
4
+ autoload :Builder, 'tokamak/atom/builder'
5
+ autoload :Helpers, 'tokamak/atom/helpers'
6
+ extend Base::ClassMethods
7
+ end
8
+ end
@@ -0,0 +1,87 @@
1
+ require 'active_support/core_ext/hash/conversions'
2
+
3
+ module Tokamak
4
+ module Atom
5
+ module Base
6
+ module ClassMethods
7
+ mattr_reader :media_type_name
8
+ @@media_type_name = 'application/atom+xml'
9
+
10
+ mattr_reader :headers
11
+ @@headers = {
12
+ :get => { 'Accept' => media_type_name },
13
+ :post => { 'Content-Type' => media_type_name }
14
+ }
15
+
16
+ mattr_reader :recipes
17
+ @@recipes = {}
18
+
19
+ def helper
20
+ Tokamak::Atom::Helpers
21
+ end
22
+
23
+ def describe_recipe(recipe_name, options={}, &block)
24
+ raise 'Undefined recipe' unless block_given?
25
+ raise 'Undefined recipe_name' unless recipe_name
26
+ @@recipes[recipe_name] = block
27
+ end
28
+
29
+ def to_atom(obj = nil, options = {}, &block)
30
+ # just instantiate the string with the atom factory
31
+ return Tokamak::Representation::Atom::Factory.create(obj) if obj.kind_of?(String)
32
+
33
+ if block_given?
34
+ recipe = block
35
+ elsif options[:recipe]
36
+ recipe = @@recipes[options[:recipe]]
37
+ else
38
+ return obj if obj.respond_to?(:atom_type) && (obj.atom_type == "feed" || obj.atom_type == "entry")
39
+ raise Tokamak::ConverterError.new("Recipe required")
40
+ end
41
+
42
+ # execute with the builder if a recipe is set (even if the obj is an atom)
43
+ options[:atom_type] ||= obj.respond_to?(:each) ? :feed : :entry
44
+ raise Tokamak::ConverterError.new("Undefined atom type #{options[:atom_type]}") unless [:entry,:feed].include?(options[:atom_type])
45
+
46
+ # Create representation and proxy
47
+ builder = Builder.new(options[:atom_type], obj)
48
+
49
+ # Check recipe arity size before calling it
50
+ recipe.call(*[builder, obj, options][0,recipe.arity])
51
+
52
+ builder.representation
53
+ end
54
+
55
+ alias_method :unmarshal, :to_atom
56
+
57
+ def to_hash(obj)
58
+ return obj if obj.kind_of?(Hash)
59
+
60
+ xml = nil
61
+
62
+ if obj.kind_of?(::String)
63
+ xml = obj
64
+ elsif obj.respond_to?(:to_xml)
65
+ xml = obj.to_xml
66
+ end
67
+
68
+ Hash.from_xml(xml).with_indifferent_access unless xml.nil?
69
+ end
70
+
71
+
72
+ def to_s(obj)
73
+ return obj if obj.kind_of?(String)
74
+ if obj.respond_to?(:to_xml)
75
+ obj.to_xml.to_s
76
+ else
77
+ obj.to_s
78
+ end
79
+ end
80
+
81
+ def marshal(obj, options = nil)
82
+ to_atom(obj, options).to_xml
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,107 @@
1
+ module Tokamak
2
+ module Atom
3
+ class Builder
4
+ attr_accessor :atom_type
5
+ def initialize(atom_type, obj)
6
+ @doc = Nokogiri::XML::Document.new
7
+ @obj = obj
8
+ @parent = @doc.create_element(atom_type.to_s)
9
+ @parent.add_namespace_definition(nil, "http://www.w3.org/2005/Atom")
10
+ @parent.parent = @doc
11
+ end
12
+
13
+ def values(options = {}, &block)
14
+ options.each do |key,value|
15
+ attr = key.to_s
16
+ if attr =~ /^xmlns(:\w+)?$/
17
+ ns = attr.split(":", 2)[1]
18
+ @parent.add_namespace_definition(ns, value)
19
+ end
20
+ end
21
+ yield Values.new(self)
22
+ end
23
+
24
+ def members(options = {}, &block)
25
+ collection = options[:collection] || @obj
26
+ raise Error::BuilderError.new("Members method require a collection to execute") unless collection.respond_to?(:each)
27
+ collection.each do |member|
28
+ entry = @doc.create_element("entry")
29
+ entry.parent = @parent
30
+ @parent = entry
31
+ block.call(self, member)
32
+ @parent = entry.parent
33
+ end
34
+ end
35
+
36
+ def link(relationship, uri, options = {})
37
+ options["rel"] = relationship.to_s
38
+ options["href"] = uri
39
+ options["type"] ||= "application/atom+xml"
40
+ insert_value("link", nil, options)
41
+ end
42
+
43
+ def insert_value(name, prefix, *args, &block)
44
+ node = create_element(name.to_s, prefix, *args)
45
+ node.parent = @parent
46
+ if block_given?
47
+ @parent = node
48
+ block.call
49
+ @parent = node.parent
50
+ end
51
+ end
52
+
53
+ def representation
54
+ Tokamak::Representation::Atom::Factory.create(@doc)
55
+ end
56
+
57
+ private
58
+
59
+ def create_element(node, prefix, *args)
60
+ node = @doc.create_element(node) do |n|
61
+ if prefix
62
+ if namespace = prefix_valid?(prefix)
63
+ # Adding namespace prefix
64
+ n.namespace = namespace
65
+ namespace = nil
66
+ end
67
+ end
68
+
69
+ args.each do |arg|
70
+ case arg
71
+ # Adding XML attributes
72
+ when Hash
73
+ arg.each { |k,v|
74
+ key = k.to_s
75
+ if key =~ /^xmlns(:\w+)?$/
76
+ ns_name = key.split(":", 2)[1]
77
+ n.add_namespace_definition(ns_name, v)
78
+ next
79
+ end
80
+ n[k.to_s] = v.to_s
81
+ }
82
+ # Adding XML node content
83
+ else
84
+ arg.kind_of?(Time) || arg.kind_of?(DateTime) ? content = arg.xmlschema : content = arg
85
+ n.content = content
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def prefix_valid?(prefix)
92
+ ns = @parent.namespace_definitions.find { |x| x.prefix == prefix.to_s }
93
+
94
+ unless ns
95
+ @parent.ancestors.each do |a|
96
+ next if a == @doc
97
+ ns = a.namespace_definitions.find { |x| x.prefix == prefix.to_s }
98
+ break if ns
99
+ end
100
+ end
101
+
102
+ return ns
103
+ #TODO: raise ArgumentError, "Namespace #{prefix} has not been defined" if wanted
104
+ end
105
+ end
106
+ end
107
+ end