hypertext_application_language 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +0 -0
- data/.rspec +1 -0
- data/.yardopts +6 -0
- data/ChangeLog.md +0 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +43 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/hypertext_application_language.gemspec +21 -0
- data/lib/hypertext_application_language/hash_representation_parser.rb +86 -0
- data/lib/hypertext_application_language/hash_representation_renderer.rb +88 -0
- data/lib/hypertext_application_language/link.rb +119 -0
- data/lib/hypertext_application_language/namespace_manager.rb +72 -0
- data/lib/hypertext_application_language/representation.rb +97 -0
- data/lib/hypertext_application_language/version.rb +6 -0
- data/lib/hypertext_application_language.rb +8 -0
- data/spec/hypertext_application_language/link_spec.rb +30 -0
- data/spec/hypertext_application_language/namespace_manager_spec.rb +38 -0
- data/spec/hypertext_application_language/representation_spec.rb +9 -0
- data/spec/spec_helper.rb +8 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1c9c7296b2e50bdb822d5af41b4ff5b562be1e2f
|
4
|
+
data.tar.gz: e863ec8f7f2ed73ef83d6a2a1fc3dc5f80f2b1fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 035ba935018b647c548641f9005aebb79bc913ee99cde200a240dcd7a0cba64aeea0020c1526d88aeb3618a1a698d08217d7395b1d3e8b0caea8db54e1c318f2
|
7
|
+
data.tar.gz: c3e2785fa7a9cdc226b9c82b0077347114bad4c2b696411c788a1bf1e7bf8358b69f3e345bfad72e9ad45eaa119c70ad6dda86a508810f935b0625d2ffae4c93
|
data/.gitignore
ADDED
File without changes
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.yardopts
ADDED
data/ChangeLog.md
ADDED
File without changes
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
hypertext_application_language (0.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.5)
|
10
|
+
docile (1.1.5)
|
11
|
+
json (1.8.3)
|
12
|
+
json (1.8.3-java)
|
13
|
+
rspec (3.3.0)
|
14
|
+
rspec-core (~> 3.3.0)
|
15
|
+
rspec-expectations (~> 3.3.0)
|
16
|
+
rspec-mocks (~> 3.3.0)
|
17
|
+
rspec-core (3.3.2)
|
18
|
+
rspec-support (~> 3.3.0)
|
19
|
+
rspec-expectations (3.3.1)
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
21
|
+
rspec-support (~> 3.3.0)
|
22
|
+
rspec-mocks (3.3.2)
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
+
rspec-support (~> 3.3.0)
|
25
|
+
rspec-support (3.3.0)
|
26
|
+
simplecov (0.10.0)
|
27
|
+
docile (~> 1.1.0)
|
28
|
+
json (~> 1.8)
|
29
|
+
simplecov-html (~> 0.10.0)
|
30
|
+
simplecov-html (0.10.0)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
java
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
bundler
|
38
|
+
hypertext_application_language!
|
39
|
+
rspec
|
40
|
+
simplecov
|
41
|
+
|
42
|
+
BUNDLED WITH
|
43
|
+
1.10.6
|
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright © 2015, Roy Ratcliffe, Pioneering Software, United Kingdom
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the “Software”), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
|
14
|
+
EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
15
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
|
16
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
17
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
18
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Hypertext Application Language (HAL)
|
2
|
+
|
3
|
+
What HTML does for web browsers, HAL does for applications.
|
4
|
+
|
5
|
+
HTML gives you pages of marked-up information for presentation to users; HAL
|
6
|
+
gives you marked-up representations for application consumption. Applications
|
7
|
+
can easily extract information about remote resources, including relationships
|
8
|
+
between resources. What HAL calls a representation, HTML calls a web page; such
|
9
|
+
pages have links to other pages. HTML was designed for presenting information
|
10
|
+
to humans. HAL was designed for presenting information to applications.
|
11
|
+
|
12
|
+
This gem provides a suite of Ruby classes for rendering and parsing resource
|
13
|
+
representations, including their links, properties and nested representations
|
14
|
+
of embedded resources.
|
15
|
+
|
16
|
+
## Deviations
|
17
|
+
|
18
|
+
The interfaces and implementations largely echo those written in Java, but
|
19
|
+
there are some deliberate deviations.
|
20
|
+
|
21
|
+
The Ruby gem adds some consistency in naming. Representation is the name used
|
22
|
+
to describe an object that represents some remote resource. The term “resource”
|
23
|
+
describes the actual remote resource. Representations represent resources only;
|
24
|
+
they are not the resource themselves.
|
25
|
+
|
26
|
+
The use of “currie” has been replaced as it partially overlaps the idea of
|
27
|
+
currying and curried functions in mathematics and computer science, when in
|
28
|
+
fact the term ‘curie’ only refers to a compact URI. The acronym only
|
29
|
+
coincidentally resembles the word “curry,” a delicious Asian meal.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
File.expand_path('../lib', __FILE__).tap do |path|
|
2
|
+
$LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'hypertext_application_language/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'hypertext_application_language'
|
9
|
+
spec.version = HypertextApplicationLanguage::VERSION
|
10
|
+
spec.summary = %q{Hypertext Application Language}
|
11
|
+
spec.description = %q{}
|
12
|
+
spec.homepage = 'http://stateless.co/hal_specification.html'
|
13
|
+
spec.authors = ['Roy Ratcliffe']
|
14
|
+
spec.email = ['roy@pioneeringsoftware.co.uk']
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.add_development_dependency 'bundler'
|
19
|
+
spec.add_development_dependency 'rspec'
|
20
|
+
spec.add_development_dependency 'simplecov'
|
21
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module HypertextApplicationLanguage
|
3
|
+
# An object that parses a representation.
|
4
|
+
#
|
5
|
+
# The parser does not “parse” the hash, strictly speaking. Nor does it check
|
6
|
+
# the hash for strict compliance. Instead it just picks out pieces of the hash
|
7
|
+
# matching HAL expectations. Nothing more than that.
|
8
|
+
class HashRepresentationParser
|
9
|
+
# Parses a hash, loading the given representation with its
|
10
|
+
# hypertext-application-language content.
|
11
|
+
def parse(representation, object)
|
12
|
+
# Takes compact URI pairs from the links. Looks for the +curies+ sub-hash
|
13
|
+
# within +_links+ root-level hash. Every CURIE is a hash with a name and a
|
14
|
+
# hypertext reference.
|
15
|
+
links_object = object[Representation::LINKS]
|
16
|
+
if links_object
|
17
|
+
curie_objects = links_object[Link::CURIES_REL]
|
18
|
+
if curie_objects
|
19
|
+
curie_objects = [curie_objects] unless curie_objects.is_a?(Array)
|
20
|
+
curie_objects.select { |element| element.is_a?(Hash) }.each do |object|
|
21
|
+
# Both the name and the hypertext reference must have string type;
|
22
|
+
# there is no scope for representing references except using their
|
23
|
+
# string form. Represent with strings, otherwise the parser ignores
|
24
|
+
# it.
|
25
|
+
#
|
26
|
+
# The link's +href+ attribute carries the relative reference, even
|
27
|
+
# though the reference is not a true hypertext reference since it
|
28
|
+
# contains the +{rel}+ token as a placeholder for substitution.
|
29
|
+
name = object[Link::NAME]
|
30
|
+
next unless name.is_a?(String)
|
31
|
+
ref = object[Link::HREF]
|
32
|
+
next unless ref.is_a?(String)
|
33
|
+
representation.with_namespace(name, ref)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# The links object is a hash of strings paired with a hash or an array
|
38
|
+
# of hashes.
|
39
|
+
links_object.each do |rel, link_objects|
|
40
|
+
next if rel == Link::CURIES_REL
|
41
|
+
link_objects = [link_objects] unless link_objects.is_a?(Array)
|
42
|
+
link_objects.each do |link_object|
|
43
|
+
# Makes you wonder. Should the following pass the name? Doing so
|
44
|
+
# allows name-spaces to sneak into the links. Name-spaces should
|
45
|
+
# only appear in the +curies+ hash. They will never function as a
|
46
|
+
# CURIE unless they do.
|
47
|
+
href = object[Link::HREF]
|
48
|
+
next unless href
|
49
|
+
link = Link.new(rel, href)
|
50
|
+
link.name = object[Link::NAME] if object[Link::NAME]
|
51
|
+
link.title = object[Link::TITLE] if object[Link::TITLE]
|
52
|
+
link.hreflang = object[Link::HREFLANG] if object[Link::HREFLANG]
|
53
|
+
link.profile = object[Link::PROFILE] if object[Link::PROFILE]
|
54
|
+
representation.with_link(link)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Properties should only contain primitive types: string, numbers,
|
60
|
+
# booleans or arrays of the same.
|
61
|
+
object.each do |name, value|
|
62
|
+
next if [Representation::LINKS, Representation::EMBEDDED].include?(name)
|
63
|
+
# if value.is_a?(Array)
|
64
|
+
# representation.with_property(name, value.map(&:to_s))
|
65
|
+
# else
|
66
|
+
# representation.with_property(name, value.to_s)
|
67
|
+
# end
|
68
|
+
representation.with_property(name, value)
|
69
|
+
end
|
70
|
+
|
71
|
+
embedded = object[Representation::EMBEDDED]
|
72
|
+
if embedded
|
73
|
+
embedded.each do |rel, objects|
|
74
|
+
# The relation key must be a string. Turn the value into an array of
|
75
|
+
# hashes, parsing an embedded representation from each hash.
|
76
|
+
objects = [objects] unless objects.is_a?(Array)
|
77
|
+
objects.each do |object|
|
78
|
+
embedded_representation = Representation.new
|
79
|
+
parse(embedded_representation, object)
|
80
|
+
representation.with_representation(rel, embedded_representation)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module HypertextApplicationLanguage
|
2
|
+
# Renders a representation as a Hash.
|
3
|
+
class HashRepresentationRenderer
|
4
|
+
# Renders a representation to a Hash.
|
5
|
+
# @return [Hash] The resulting Hash representation.
|
6
|
+
def render(representation)
|
7
|
+
render_representation(representation)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Renders either a top-level representation or an embedded resource.
|
13
|
+
def render_representation(representation, embedded=false)
|
14
|
+
object = {}
|
15
|
+
|
16
|
+
# Render the name-spaces and links but only if there are name-spaces and
|
17
|
+
# links; also render links if there are name-spaces to render, assuming
|
18
|
+
# not embedded. Create a hash representation without links if there are
|
19
|
+
# none. Merge the name-spaces and links.
|
20
|
+
unless representation.links.empty? && (embedded || representation.namespaces.empty?)
|
21
|
+
links_object = object[Representation::LINKS] ||= {}
|
22
|
+
links = []
|
23
|
+
unless embedded
|
24
|
+
representation.namespaces.each do |name, ref|
|
25
|
+
links.push(Link.new(Link::CURIES_REL, ref, Link::NAME => name))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
links.concat(representation.links)
|
29
|
+
links_for_rel = {}
|
30
|
+
links.each do |link|
|
31
|
+
(links_for_rel[link.rel] ||= []).push(link)
|
32
|
+
end
|
33
|
+
links_for_rel.each do |rel, links|
|
34
|
+
link_objects = links.map do |link|
|
35
|
+
# Render a link as a hash. Importantly, the following does not
|
36
|
+
# render the link relation; it only renders the link content. The
|
37
|
+
# relation appears in the rendered output as the key, not as part of
|
38
|
+
# the hash value paired with the key.
|
39
|
+
link_object = {}
|
40
|
+
|
41
|
+
# There is always a relation and a hypertext reference for every
|
42
|
+
# link; no need to check for +nil+. If you set up a link with a
|
43
|
+
# +nil+ reference, the output will contain a blank string, since
|
44
|
+
# +nil.to_s+ answers +""+.
|
45
|
+
link_object[Link::HREF] = link.href.to_s
|
46
|
+
|
47
|
+
link_object[Link::NAME] = link.name.to_s if link.name
|
48
|
+
link_object[Link::TITLE] = link.title.to_s if link.title
|
49
|
+
link_object[Link::HREFLANG] = link.hreflang.to_s if link.hreflang
|
50
|
+
link_object[Link::PROFILE] = link.profile.to_s if link.profile
|
51
|
+
|
52
|
+
link_object
|
53
|
+
end
|
54
|
+
links_object[rel] = link_objects.length == 1 ? link_objects.first : link_objects
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Merge the representation's properties. Properties live at the root of
|
59
|
+
# the hash. Merging is just another way to assign values to their name
|
60
|
+
# keys.
|
61
|
+
#
|
62
|
+
# representation.properties.each do |name, value|
|
63
|
+
# hash[name] = value
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# This makes some assumptions about the representation properties. It
|
67
|
+
# assumes that the property values are primitive types: strings, numbers,
|
68
|
+
# booleans. They should never be hashes or custom classes.
|
69
|
+
object.merge!(representation.properties)
|
70
|
+
|
71
|
+
# Render embedded resource representations. Each representation retains
|
72
|
+
# zero or more sub-representations by their relation. The relation maps to
|
73
|
+
# an array of embedded representations, zero or more for each
|
74
|
+
# relation. Render each one recursively.
|
75
|
+
unless representation.representations.empty?
|
76
|
+
embedded_object = object[Representation::EMBEDDED] ||= {}
|
77
|
+
representation.representations_for_rel.each do |rel, representations|
|
78
|
+
objects = representations.map do |representation|
|
79
|
+
render_representation(representation, true)
|
80
|
+
end
|
81
|
+
embedded_object[rel] = objects.length == 1 ? objects.first : objects
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
object
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module HypertextApplicationLanguage
|
2
|
+
# Links belong to representations; a representation retains zero or more
|
3
|
+
# links. Each link gives a hypertext reference for a relation, at least. Some
|
4
|
+
# links provide more information.
|
5
|
+
#
|
6
|
+
# Links have attributes. The relation and hypertext reference attributes are
|
7
|
+
# primary and are always required for every link. Representations may carry
|
8
|
+
# multiple links with the _same_ relation, just like a HTML page. Links
|
9
|
+
# sharing the same relation refer to the same thing, or things.
|
10
|
+
#
|
11
|
+
# This is the simplest possible implementation of a
|
12
|
+
# hypertext-application-language (HAL) link. It does not support
|
13
|
+
# immutability. All links and their attributes remain mutable, even after
|
14
|
+
# attaching to a representation. This condition continues until you freeze the
|
15
|
+
# instance, according to the Ruby immutability paradigm.
|
16
|
+
class Link
|
17
|
+
# Creates a new mutable link.
|
18
|
+
# @return [Link] Answers a newly initialised link.
|
19
|
+
# @param [String] rel Relation.
|
20
|
+
# @param [String] href Hypertext reference.
|
21
|
+
# @param [Array<String, Hash>] args Array of strings used to initialise the
|
22
|
+
# optional attributes in the following order: +name+, +title+, +hreflang+
|
23
|
+
# and +profile+. If the last element is a Hash, these become keyword
|
24
|
+
# arguments where you can set up the optional link attributes by name,
|
25
|
+
# either symbolic or string.
|
26
|
+
def initialize(rel, href, *args)
|
27
|
+
@rel = rel
|
28
|
+
@href = href
|
29
|
+
|
30
|
+
# Take an array of arguments following the #rel and #href; these arguments
|
31
|
+
# assign to the optional link attributes in order. Pick out keyword
|
32
|
+
# arguments if the last argument is a #Hash. Be indifferent about the
|
33
|
+
# keywords; accept both string and symbols. Do this by converting the
|
34
|
+
# string keys to symbols if they do not otherwise match anything in the
|
35
|
+
# keyword arguments hash. Take care not to re-invoke the hash fetch again
|
36
|
+
# using the subscript operator, otherwise the default #Proc will recurse
|
37
|
+
# indefinitely.
|
38
|
+
keyword_args = args.last.is_a?(Hash) ? args.pop : {}
|
39
|
+
keyword_args.default_proc = proc do |hash, key|
|
40
|
+
hash.fetch(key.to_sym, nil)
|
41
|
+
end
|
42
|
+
@name, @title, @hreflang, @profile = args
|
43
|
+
@name ||= keyword_args[NAME]
|
44
|
+
@title ||= keyword_args[TITLE]
|
45
|
+
@hreflang ||= keyword_args[HREFLANG]
|
46
|
+
@profile ||= keyword_args[PROFILE]
|
47
|
+
end
|
48
|
+
|
49
|
+
# When you freeze the object, also freeze all the instance
|
50
|
+
# variables. Otherwise, you can still modify the existing instance
|
51
|
+
# variables' assigned objects even though you cannot reassign the variables
|
52
|
+
# themselves.
|
53
|
+
def freeze
|
54
|
+
instance_variables.each do |instance_variable|
|
55
|
+
instance_variable_get(instance_variable).freeze
|
56
|
+
end
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
# @!group Required attributes
|
61
|
+
|
62
|
+
attr_accessor :rel
|
63
|
+
|
64
|
+
# Link attribute describing the hypertext reference. The reference can be a
|
65
|
+
# full universal resource location, or some element thereof. It can be just
|
66
|
+
# the path.
|
67
|
+
attr_accessor :href
|
68
|
+
|
69
|
+
# @!group Optional attributes
|
70
|
+
|
71
|
+
attr_accessor :name
|
72
|
+
attr_accessor :title
|
73
|
+
|
74
|
+
# Returns the ISO 639-1 code describing the link's language. You can have
|
75
|
+
# multiple links for the same relation but for different languages.
|
76
|
+
attr_accessor :hreflang
|
77
|
+
|
78
|
+
attr_accessor :profile
|
79
|
+
|
80
|
+
# @!group Required link attribute names
|
81
|
+
|
82
|
+
REL = 'rel'.freeze
|
83
|
+
HREF = 'href'.freeze
|
84
|
+
|
85
|
+
# @!group Optional link attribute names
|
86
|
+
|
87
|
+
NAME = 'name'.freeze
|
88
|
+
TITLE = 'title'.freeze
|
89
|
+
HREFLANG = 'hreflang'.freeze
|
90
|
+
PROFILE = 'profile'.freeze
|
91
|
+
|
92
|
+
# @!endgroup
|
93
|
+
|
94
|
+
# Array of attribute names including those required and those optional.
|
95
|
+
ATTRIBUTE_NAMES = [
|
96
|
+
# required
|
97
|
+
REL,
|
98
|
+
HREF,
|
99
|
+
|
100
|
+
# optional
|
101
|
+
NAME,
|
102
|
+
TITLE,
|
103
|
+
HREFLANG,
|
104
|
+
PROFILE,
|
105
|
+
].freeze
|
106
|
+
|
107
|
+
# @!group Special link relations
|
108
|
+
|
109
|
+
# This special link relation describes the link to the representations own
|
110
|
+
# source, i.e. itself.
|
111
|
+
SELF_REL = 'self'.freeze
|
112
|
+
|
113
|
+
# Special link relation used for name-spaces. Representation name-spaces
|
114
|
+
# appear in rendered links under the "curies" relation; where the link
|
115
|
+
# +name+ corresponds to the name-space name and the link +href+ corresponds
|
116
|
+
# to the name-space reference with its embedded +{rel}+ placeholder.
|
117
|
+
CURIES_REL = 'curies'.freeze
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module HypertextApplicationLanguage
|
2
|
+
# Handles _compact_ URIs, a.k.a. CURIEs. Representations and representation
|
3
|
+
# factories have CURIEs handled by a name-space manager instance.
|
4
|
+
#
|
5
|
+
# @see http://www.w3.org/TR/curie/
|
6
|
+
class NamespaceManager
|
7
|
+
# Defines the relative reference token, the placeholder used in CURIEs.
|
8
|
+
REL = '{rel}'.freeze
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
# @!method namespaces
|
13
|
+
# @return [Hash<String, String>] Answers a hash of relative references by
|
14
|
+
# their name.
|
15
|
+
def_delegator :@ref_for_name, :dup, :namespaces
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
# Retains one relative hypertext reference for one name.
|
19
|
+
@ref_for_name = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Adds a name-space to this manager.
|
23
|
+
# @param [String] name
|
24
|
+
# Names the CURIE. This appears in CURIE references as the prefix before
|
25
|
+
# the colon. The relative reference comes after the colon.
|
26
|
+
# @param [String] ref
|
27
|
+
# Gives the CURIE's relative reference. It must include the +{rel}+
|
28
|
+
# placeholder identifying where to substitute the CURIE argument, the
|
29
|
+
# value that replaces the placeholder.
|
30
|
+
def with_namespace(name, ref)
|
31
|
+
@ref_for_name[name] = ref
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Converts an expanded hypertext reference to a CURIE'd reference based on
|
36
|
+
# the current set of CURIE specifications, the name-spaces.
|
37
|
+
# @return [String] Answers the CURIE'd reference corresponding to the given
|
38
|
+
# hypertext reference, or +nil+ if there is no matching CURIE.
|
39
|
+
def curie(href)
|
40
|
+
@ref_for_name.each do |name, ref|
|
41
|
+
# start_index = ref.index(REL)
|
42
|
+
# end_index = start_index + REL.length
|
43
|
+
# left = ref[0...start_index]
|
44
|
+
# right = ref[end_index..-1]
|
45
|
+
# if href.start_with?(left) && href.end_with?(right)
|
46
|
+
# middle = href[start_index..(end_index - 2)]
|
47
|
+
# return name + ':' + middle
|
48
|
+
# end
|
49
|
+
left, right = ref.split(REL)
|
50
|
+
if href.start_with?(left) && href.end_with?(right)
|
51
|
+
return name + ':' + href[left.length...-right.length]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Converts a CURIE'd reference to a hypertext reference.
|
58
|
+
# @param [String] curie The argument is a string comprising a name prefix
|
59
|
+
# followed by a colon delimiter, followed by a CURIE argument.
|
60
|
+
#
|
61
|
+
# Splits the name at the first colon. The prefix portion before the colon
|
62
|
+
# identifies the name of the CURIE. The portion after the colon replaces the
|
63
|
+
# +{rel}+ placeholder. This is a very basic way to parse a CURIE, but it
|
64
|
+
# works.
|
65
|
+
def href(curie)
|
66
|
+
name, arg = curie.split(':', 2)
|
67
|
+
ref = @ref_for_name[name]
|
68
|
+
return nil unless ref
|
69
|
+
ref.sub(REL, arg)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'hypertext_application_language/namespace_manager'
|
2
|
+
require 'hypertext_application_language/link'
|
3
|
+
|
4
|
+
module HypertextApplicationLanguage
|
5
|
+
# Represents a resource. This includes sub-resources which also have their own
|
6
|
+
# representation. Representations have links, properties and sub-resources.
|
7
|
+
#
|
8
|
+
# Resource is the name of a representation embedded within another
|
9
|
+
# super-representation. Representations have zero or resources. They will
|
10
|
+
# appear in the rendered results as embedded resources.
|
11
|
+
class Representation
|
12
|
+
LINKS = '_links'.freeze
|
13
|
+
EMBEDDED = '_embedded'.freeze
|
14
|
+
|
15
|
+
# Array of links.
|
16
|
+
attr_accessor :links
|
17
|
+
|
18
|
+
attr_accessor :properties
|
19
|
+
|
20
|
+
# Hash of string-array pairs. The arrays contain embedded representations,
|
21
|
+
# zero or more.
|
22
|
+
attr_accessor :representations_for_rel
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@namespace_manager = NamespaceManager.new
|
26
|
+
@links = []
|
27
|
+
@properties = {}
|
28
|
+
@representations_for_rel = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# @!group Namespaces
|
32
|
+
|
33
|
+
def namespaces
|
34
|
+
@namespace_manager.namespaces
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_namespace(name, ref)
|
38
|
+
@namespace_manager.with_namespace(name, ref)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!group Links
|
43
|
+
|
44
|
+
def link
|
45
|
+
link_for(Representation::SELF_REL)
|
46
|
+
end
|
47
|
+
|
48
|
+
def link_for(href_or_rel)
|
49
|
+
links_for(href_or_rel).first
|
50
|
+
end
|
51
|
+
|
52
|
+
# Answers the representation's links selected by either a hypertext
|
53
|
+
# reference or by a relation.
|
54
|
+
def links_for(href_or_rel)
|
55
|
+
rel = @namespace_manager.curie(href_or_rel) || href_or_rel
|
56
|
+
@links.select { |link| link.rel == rel }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Adds a link to this representation.
|
60
|
+
#
|
61
|
+
# Ruby does not support argument overloading. If there is just one argument,
|
62
|
+
# assume that it is a +Link+ instance. If not, if more than one argument,
|
63
|
+
# assume that they are +String+ instances.
|
64
|
+
def with_link(*args)
|
65
|
+
@links.push(args.length == 1 ? args.first : Link.new(*args))
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# @!group Properties
|
70
|
+
|
71
|
+
def value_for(name, default_value=nil)
|
72
|
+
@properties[name] || default_value
|
73
|
+
end
|
74
|
+
|
75
|
+
def with_property(name, value)
|
76
|
+
@properties[name] = value
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# @!group Representations
|
81
|
+
|
82
|
+
# Takes the array values from the representations by relation, then flattens
|
83
|
+
# the array of arrays of representations. The result becomes an array of
|
84
|
+
# representations, all of them but without their relation to the
|
85
|
+
# super-representation that having been stripped away.
|
86
|
+
def representations
|
87
|
+
@representations_for_rel.values.flatten
|
88
|
+
end
|
89
|
+
|
90
|
+
# Associates a given embedded representation with this representation by a
|
91
|
+
# given relation.
|
92
|
+
def with_representation(rel, representation)
|
93
|
+
(@representations_for_rel[rel] ||= []).push(representation)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'hypertext_application_language/version'
|
2
|
+
|
3
|
+
require 'hypertext_application_language/namespace_manager'
|
4
|
+
require 'hypertext_application_language/link'
|
5
|
+
require 'hypertext_application_language/representation'
|
6
|
+
|
7
|
+
require 'hypertext_application_language/hash_representation_parser'
|
8
|
+
require 'hypertext_application_language/hash_representation_renderer'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
describe HypertextApplicationLanguage::Link do
|
2
|
+
let(:link) { described_class.new('rel', '/path') }
|
3
|
+
|
4
|
+
it 'initializes' do
|
5
|
+
expect(link.rel).to eq('rel')
|
6
|
+
expect(link.href).to eq('/path')
|
7
|
+
|
8
|
+
other_link = described_class.new('other_rel', '/other_path', name: 'other_name')
|
9
|
+
expect(other_link.name).to eq('other_name')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'mutates' do
|
13
|
+
link.rel = 'otherRel'
|
14
|
+
expect(link.rel).to eq('otherRel')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'freezes' do
|
18
|
+
link.freeze
|
19
|
+
expect do
|
20
|
+
link.rel = 'frozenRel'
|
21
|
+
end.to raise_error(RuntimeError)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'freezes attributes' do
|
25
|
+
link.freeze
|
26
|
+
expect do
|
27
|
+
link.rel.prepend('other_')
|
28
|
+
end.to raise_error(RuntimeError)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
describe HypertextApplicationLanguage::NamespaceManager do
|
2
|
+
let(:manager) { described_class.new }
|
3
|
+
|
4
|
+
it 'initializes' do
|
5
|
+
expect(manager).not_to be_nil
|
6
|
+
expect(manager.namespaces).to be_empty
|
7
|
+
expect(manager.namespaces.length).to eq(0)
|
8
|
+
|
9
|
+
namespaces = manager.namespaces
|
10
|
+
namespaces['name'] = 'href'
|
11
|
+
expect(manager.namespaces.length).to eq(0)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'loads with namespaces' do
|
15
|
+
expect(manager.namespaces.length).to eq(0)
|
16
|
+
manager.with_namespace('name', 'http://localhost/' + described_class::REL)
|
17
|
+
expect(manager.namespaces['name']).to eq('http://localhost/{rel}')
|
18
|
+
expect(manager.namespaces.length).to eq(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:ns_manager) { manager.with_namespace('ns', 'http://localhost/{rel}/to') }
|
22
|
+
|
23
|
+
it 'answers curie for href' do
|
24
|
+
expect(ns_manager.curie('http://localhost/path/to')).to eq('ns:path')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'answers nil when no matching href' do
|
28
|
+
expect(ns_manager.curie('http://localhost:8080/to')).to be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'answers href for curie' do
|
32
|
+
expect(ns_manager.href('ns:arg')).to eq('http://localhost/arg/to')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'answers nil when no matching curie' do
|
36
|
+
expect(ns_manager.href('n$:arg')).to be_nil
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
describe HypertextApplicationLanguage::Representation do
|
2
|
+
let(:representation) { described_class.new }
|
3
|
+
|
4
|
+
it 'retains links' do
|
5
|
+
expect(representation.links).to be_empty
|
6
|
+
representation.with_link(HypertextApplicationLanguage::Link::SELF_REL, 'http://localhost/rel/1')
|
7
|
+
expect(representation.links).not_to be_empty
|
8
|
+
end
|
9
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hypertext_application_language
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roy Ratcliffe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
name: bundler
|
20
|
+
prerelease: false
|
21
|
+
type: :development
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
name: rspec
|
34
|
+
prerelease: false
|
35
|
+
type: :development
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
name: simplecov
|
48
|
+
prerelease: false
|
49
|
+
type: :development
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: ''
|
56
|
+
email:
|
57
|
+
- roy@pioneeringsoftware.co.uk
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".yardopts"
|
65
|
+
- ChangeLog.md
|
66
|
+
- Gemfile
|
67
|
+
- Gemfile.lock
|
68
|
+
- MIT-LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- hypertext_application_language.gemspec
|
72
|
+
- lib/hypertext_application_language.rb
|
73
|
+
- lib/hypertext_application_language/hash_representation_parser.rb
|
74
|
+
- lib/hypertext_application_language/hash_representation_renderer.rb
|
75
|
+
- lib/hypertext_application_language/link.rb
|
76
|
+
- lib/hypertext_application_language/namespace_manager.rb
|
77
|
+
- lib/hypertext_application_language/representation.rb
|
78
|
+
- lib/hypertext_application_language/version.rb
|
79
|
+
- spec/hypertext_application_language/link_spec.rb
|
80
|
+
- spec/hypertext_application_language/namespace_manager_spec.rb
|
81
|
+
- spec/hypertext_application_language/representation_spec.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
homepage: http://stateless.co/hal_specification.html
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.4.8
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Hypertext Application Language
|
107
|
+
test_files: []
|
108
|
+
has_rdoc:
|