halibut 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -20,4 +20,7 @@ _yardoc
20
20
  doc/
21
21
 
22
22
  # OS X artifacts
23
- .DS_Store
23
+ .DS_Store
24
+
25
+ # Rubinius
26
+ .rbx
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "spec/test-resources"]
2
+ path = spec/test-resources
3
+ url = https://github.com/HalBuilder/halbuilder-test-resources.git
data/.travis.yml CHANGED
@@ -1,11 +1,11 @@
1
+ language: ruby
2
+ gemfile: Gemfile.travis
3
+ bundler_args: --without development
1
4
  rvm:
2
- - ree
3
- - 1.8.7
4
5
  - 1.9.2
5
6
  - 1.9.3
6
7
  - ruby-head
7
- - jruby-18mode
8
8
  - jruby-19mode
9
9
  - jruby-head
10
- - rbx-18mode
11
10
  - rbx-19mode
11
+
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source :rubygems
2
2
 
3
3
  # Specify your gem's dependencies in halibut.gemspec
4
- gemspec
4
+ gemspec
data/Gemfile.travis ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in halibut.gemspec
4
+ gemspec
5
+
6
+ group :travis do
7
+ gem "rake"
8
+ gem "minitest"
9
+
10
+ gem "hash-differ"
11
+ end
data/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Halibut
2
2
 
3
- TODO: Write a gem description
3
+ Halibut is a tiny gem that makes it easier to deal with the [HAL](http://stateless.co/hal_specification.html) format.
4
+
5
+ In providing tools to build, (de)serialize, and manipulate HAL resources,
6
+ Halibut has the following goals:
7
+
8
+ - Provide Ruby abstractions
9
+ - Clean, small API
10
+ - Easily composable, for use in other libraries
4
11
 
5
12
  ## Installation
6
13
 
@@ -18,56 +25,98 @@ Or install it yourself as:
18
25
 
19
26
  ## Usage
20
27
 
21
- TODO: Write usage instructions here
28
+ There are three ways to get a resource with halibut: manual, Builder, and JSON.
22
29
 
23
- ## RDD
30
+ ### Manual
24
31
 
25
32
  ```ruby
26
- # Namespaces
27
- Halibut::JSON
28
- Halibut::XML
29
- Halibut::HAL
30
-
31
- Halibut::JSON::Document
32
- Halibut::JSON::Builder
33
+ require 'halibut'
34
+
35
+ # manually creating a resource
36
+ order = Halibut::HAL::Resource.new "/orders/123"
37
+ order.set_property "total", 30.00
38
+ order.set_property "currency", "USD"
39
+ order.set_property "status", "shipped"
40
+
41
+ resource = Halibut::HAL::Resource.new "/orders"
42
+ resource.add_link "find", "/orders{?id}", templated: true
43
+ resource.add_link "next", "/orders/1", "name" => 'hotdog'
44
+ resource.add_link "next", "/orders/9"
45
+ resource.set_property "currentlyProcessing", 14
46
+ resource.set_property "shippedToday", 20
47
+ resource.embed_resource "orders", order
48
+ ```
33
49
 
34
- Halibut::XML::Document
35
- Halibut::XML::Builder
50
+ ### Halibut::Builder
51
+ ```ruby
52
+ require 'halibut/builder'
36
53
 
37
- Halibut::HAL::Document
38
- Halibut::HAL::Document::LinkSet
39
- Halibut::HAL::Document::ResourceSet
40
- Halibut::HAL::Document::AttributeSet (?)
41
- Halibut::HAL::Link
42
- Halibut::HAL::Resource
54
+ builder = Halibut::Builder.new '/orders' do
55
+ property 'currentlyProcessing', 14
56
+ property 'shippedToday', 20
57
+
58
+ namespace 'th', 'http://things-db.com/{rel}'
59
+
60
+ link 'find', '/orders{?id}', templated: true
61
+ link 'next', '/orders/1', name: 'hotdog'
62
+ link 'next', '/orders/9'
63
+
64
+ link 'th:manufacturer', '/manufacturer/1'
65
+ link 'th:manufacturer', '/manufacturer/2'
66
+ link 'th:manufacturer', '/manufacturer/3'
67
+ end
43
68
 
44
- hal = Halibut::XML::Builder.new "/api/news" do |it|
45
- it.attr "some_attribute", "The Value of the Attribute"
46
- it.attr "another_attribute", "The Value of Another Attribute"
69
+ # alternatively
47
70
 
48
- it.link "search", "/api/news{?search}", :templated => true
71
+ builder = Halibut::Builder.new '/orders' do
72
+ property 'currentlyProcessing', 14
73
+ property 'shippedToday', 20
74
+
75
+ namespace 'th', 'http://things-db.com/{rel}'
49
76
 
50
- it.resource "relation", "/href/etc" do |r|
51
- r.attr "again", "this"
52
- r.attr "this", "again"
53
-
54
- r.link "search", "/href/etc?search={term}", :templated => true, :title => "Embedded Resource"
77
+ link 'find', '/orderes{?id}', templated: true
78
+ relation 'next' do
79
+ link '/orders/1', name: 'hotdog'
80
+ link '/orders/9'
55
81
  end
56
- it.resource "relation", "/href/more" do |r|
57
- r.attr "again", "this"
58
- r.attr "this", "again"
59
-
60
- r.link "search", "/href/more?search={term}", :templated => true, :title => "Embedded Resource"
82
+ relation 'th:manufacturer' do
83
+ link '/manufacturers/1'
84
+ link '/manufacturers/2'
85
+ link '/manufacturers/3'
61
86
  end
62
87
  end
63
- hal.to_xml
64
88
 
65
- news = Halibut::HAL::Link.new "index", "/api/news"
89
+ resource = builder.resource
90
+ ```
91
+
92
+ ### JSON
93
+ ```ruby
94
+ require 'halibut/adapter/json'
95
+
96
+ # converting to JSON
97
+ Halibut::Adapter::JSON.dump resource
98
+
99
+ # creating a resource from JSON
100
+ resource = Halibut::Adapter::JSON.load 'resource.json'
101
+ ```
102
+
103
+ ### XML
104
+ ```ruby
105
+ require 'halibut/adapter/xml'
106
+
107
+ # converting to XML
108
+ # Coming soon…
109
+
110
+ # creating a resource from XML
111
+ resource = Halibut::Adapter::XML.load 'resource.xml'
66
112
  ```
67
113
 
68
114
  ## Contributing
69
115
 
70
116
  1. Fork it
117
+ 1. `git submodule update --init`
118
+ 2. `bundle install`, optionally pass `--without-development` and use your
119
+ own tools
71
120
  2. Create your feature branch (`git checkout -b my-new-feature`)
72
121
  3. Commit your changes (`git commit -am 'Added some feature'`)
73
122
  4. Push to the branch (`git push origin my-new-feature`)
data/Rakefile CHANGED
@@ -3,8 +3,8 @@ require "bundler/gem_tasks"
3
3
 
4
4
  require 'rake/testtask'
5
5
 
6
- task :default => [:test,:spec]
6
+ task :default => [:test, :spec]
7
7
 
8
8
  Rake::TestTask.new do |t|
9
- t.pattern = "spec/*_spec.rb"
9
+ t.pattern = "spec/**/*_spec.rb"
10
10
  end
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ hm, link options could be a Struct. that way I'd get
2
+ link.option, link[:option] and link['option'] IIRC?
data/halibut.gemspec CHANGED
@@ -14,22 +14,27 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "halibut"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Halibut::VERSION
17
-
17
+
18
+ gem.required_ruby_version = '~> 1.9.3'
19
+
18
20
  gem.add_dependency "multi_json"
19
21
  gem.add_dependency "nokogiri"
20
- gem.add_dependency "uri_template"
21
-
22
- gem.add_development_dependency "rake"
23
-
24
- gem.add_development_dependency "pry"
25
- gem.add_development_dependency "pry-full"
26
- gem.add_development_dependency "pry-coolline"
27
- #gem.add_development_dependency "pry-debundle"
28
-
29
- gem.add_development_dependency "minitest"
22
+ gem.add_dependency "addressable"
23
+
24
+ gem.add_development_dependency "minitest", ">= 4.1"
25
+
26
+ gem.add_development_dependency "pry", ">= 0.9.10"
27
+ gem.add_development_dependency "pry-doc"
28
+ gem.add_development_dependency "pry-stack_explorer"
29
+ gem.add_development_dependency "pry-coolline", "0.1.5"
30
+ gem.add_development_dependency "pry-rescue", "0.13"
31
+ gem.add_development_dependency "hash-differ"
32
+
30
33
  gem.add_development_dependency "guard"
31
34
  gem.add_development_dependency "guard-bundler"
32
35
  gem.add_development_dependency "guard-minitest"
33
36
 
37
+ gem.add_development_dependency "rb-fsevent"
34
38
  gem.add_development_dependency "terminal-notifier-guard"
39
+
35
40
  end
data/lib/halibut.rb CHANGED
@@ -2,8 +2,20 @@ require "halibut/version"
2
2
 
3
3
  # Halibut is the main namespace
4
4
  module Halibut
5
+ autoload :Builder, 'halibut/builder'
6
+
7
+ autoload :RelationMap, 'halibut/relation_map'
8
+ end
9
+
10
+ # The Adapter namespace contains classes that aid in the
11
+ # mapping of HAL Resources into a specific format.
12
+ module Halibut::Adapter
13
+ autoload :JSON, 'halibut/adapter/json'
14
+ autoload :XML, 'halibut/adapter/xml'
5
15
  end
6
16
 
7
- require 'halibut/resource'
8
- require 'halibut/link'
9
- require 'halibut/relation_map'
17
+ # Halibut::HAL contains the domain objects that reflect the HAL specs.
18
+ module Halibut::HAL
19
+ autoload :Link, 'halibut/hal/link'
20
+ autoload :Resource, 'halibut/hal/resource'
21
+ end
@@ -0,0 +1,72 @@
1
+ require 'multi_json'
2
+
3
+ module Halibut::Adapter
4
+
5
+ module JSON
6
+ def self.extended(base)
7
+ base.extend InstanceMethods
8
+ end
9
+
10
+ def self.load(json)
11
+ ResourceExtractor.new(json).resource
12
+ end
13
+
14
+ def self.dump(resource)
15
+ MultiJson.dump resource.to_hash
16
+ end
17
+
18
+ private
19
+ module InstanceMethods
20
+ def to_json
21
+ MultiJson.dump self.to_hash
22
+ end
23
+ end
24
+
25
+ class ResourceExtractor
26
+ def initialize(json)
27
+ @halibut = Halibut::HAL::Resource.new
28
+ json = MultiJson.load(json)
29
+
30
+ links = json.delete '_links'
31
+ resources = json.delete '_embedded'
32
+ properties = json
33
+
34
+ properties and extract_properties properties
35
+ links and extract_links links
36
+ resources and extract_resources resources
37
+ end
38
+
39
+ def resource
40
+ @halibut
41
+ end
42
+
43
+ private
44
+ def extract_properties(properties)
45
+ properties.each_pair do |property, value|
46
+ @halibut.set_property(property, value)
47
+ end
48
+ end
49
+
50
+ def extract_links(links)
51
+ links.each do |relation,values|
52
+ links = ([] << values).flatten
53
+
54
+ links.each do |attrs|
55
+ href = attrs.delete 'href'
56
+ @halibut.add_link(relation, href, attrs)
57
+ end
58
+ end
59
+ end
60
+
61
+ def extract_resources(resources)
62
+ resources.each do |relation,values|
63
+ embeds = ([] << values).flatten
64
+
65
+ embeds.map {|embed| MultiJson.dump embed }
66
+ .map {|embed| Halibut::Adapter::JSON.load embed }
67
+ .each {|embed| @halibut.embed_resource(relation, embed) }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,88 @@
1
+ require 'nokogiri'
2
+
3
+ module Halibut::Adapter
4
+
5
+ module XML
6
+ def self.extended(base)
7
+ base.extend InstanceMethods
8
+ end
9
+
10
+ def self.load(xml)
11
+ ResourceExtractor.new(xml).resource
12
+ end
13
+
14
+ def self.dump(resource)
15
+ end
16
+
17
+ private
18
+ module InstanceMethods
19
+ def to_xml
20
+ end
21
+ end
22
+
23
+ class ResourceExtractor
24
+ def initialize(xml)
25
+ xml = Nokogiri::XML(xml)
26
+
27
+ @document = xml.root
28
+ @halibut = Halibut::HAL::Resource.new extract_self_link
29
+
30
+ # binding.pry
31
+
32
+ extract_curie
33
+ extract_properties
34
+ extract_links
35
+ extract_resources
36
+ end
37
+
38
+ def resource
39
+ @halibut
40
+ end
41
+
42
+ private
43
+ def extract_self_link
44
+ @document.attr 'href'
45
+ end
46
+
47
+ def extract_curie
48
+ @document.namespace_scopes
49
+ .reject {|ns| ns.prefix.eql? 'xsi' }
50
+ .each do |ns|
51
+ @halibut.add_link 'curie', ns.href, name: ns.prefix
52
+ end
53
+ end
54
+
55
+ def extract_properties
56
+ properties = @document.xpath '/resource/*[not(self::link) and not(self::resource)]'
57
+
58
+ properties.each do |property|
59
+ @halibut.set_property property.name, property.content
60
+ end
61
+ end
62
+
63
+ def extract_links
64
+ links = @document.xpath('/resource/link')
65
+
66
+ links.each do |link|
67
+ @halibut.add_link link.attribute('rel').value,
68
+ link.attribute('href').value,
69
+ extract_link_options(link)
70
+ end
71
+ end
72
+
73
+ # In case there are no options on the link, it returns an empty hash
74
+ def extract_link_options(link)
75
+ link.attributes.reject {|k,v| k=='rel' || k=='href' }
76
+ .map {|k,v| { k => v.value } }
77
+ .reduce(:merge) || {}
78
+ end
79
+
80
+ def extract_resources
81
+ @document.xpath('/resource/resource')
82
+ .map {|r| [] << r['rel'] << ResourceExtractor.new(r.to_xml).resource }
83
+ .each {|rel,res| @halibut.embed_resource rel, res }
84
+ end
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,75 @@
1
+ require 'halibut/hal/resource'
2
+
3
+ module Halibut
4
+
5
+ # Builder provides a very thin wrapper around creating a HAL resource.
6
+ class Builder
7
+ attr_accessor :resource
8
+
9
+ def initialize(href=nil, &blk)
10
+ @resource = Halibut::HAL::Resource.new href
11
+
12
+ RootContext.new(@resource, &blk)
13
+ end
14
+
15
+ def respond_to?(meth, *args)
16
+ RootContext.new(@resource).respond_to? meth
17
+ end
18
+
19
+ def method_missing meth, *args
20
+ RootContext.new(@resource).send meth, *args
21
+ end
22
+
23
+ private
24
+ class RootContext
25
+ extend Forwardable
26
+
27
+ def_delegator :@resource, :set_property, :property
28
+ def_delegator :@resource, :add_link, :link
29
+
30
+ def initialize(resource, &blk)
31
+ @resource = resource
32
+
33
+ instance_eval(&blk) if block_given?
34
+ end
35
+
36
+ def namespace(name, href)
37
+ @resource.add_link("curie", href, name: name)
38
+ end
39
+
40
+ def resource(rel, href=nil, &blk)
41
+ embedded = Halibut::Builder.new(href, &blk)
42
+
43
+ @resource.embed_resource(rel, embedded.resource)
44
+ end
45
+
46
+ def relation(rel, &blk)
47
+ RelationContext.new(@resource, rel, &blk)
48
+ end
49
+
50
+ end
51
+
52
+ class RelationContext
53
+
54
+ def initialize(resource, rel, &blk)
55
+ @resource = resource
56
+ @rel = rel
57
+
58
+ instance_eval(&blk) if block_given?
59
+ end
60
+
61
+ private
62
+ def link(href, opts={})
63
+ @resource.add_link(@rel, href, opts)
64
+ end
65
+
66
+ def resource(href=nil, &blk)
67
+ embedded = Halibut::Builder.new(href, &blk)
68
+
69
+ @resource.embed_resource(@rel, embedded.resource)
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ end