hypertext_application_language 0.0.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.
- 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:
|