halibut 0.1.0 → 0.2.0

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